synchronized关键字的使用

主要讲synchronized关键字的用法和不同之处

开启多线程

有三个线程ThreadTest1ThreadTest2ThreadTest3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class ThreadTest1 extends Thread {
SynchronizedTest synchronizedTest;
public ThreadTest1(SynchronizedTest synchronizedTest) {
this.synchronizedTest = synchronizedTest;
}
@Override
public void run() {
synchronizedTest.test();
}
}
public class ThreadTest2 extends Thread {
SynchronizedTest synchronizedTest;
public ThreadTest2(SynchronizedTest synchronizedTest) {
this.synchronizedTest = synchronizedTest;
}
@Override
public void run() {
synchronizedTest.test();
}
}
public class ThreadTest3 extends Thread {
SynchronizedTest synchronizedTest;
public ThreadTest3(SynchronizedTest synchronizedTest) {
this.synchronizedTest = synchronizedTest;
}
@Override
public void run() {
synchronizedTest.test();
}
}

调用一个对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SynchronizedTest {
String anyString = "";
public SynchronizedTest(String anyString) {
this.anyString = anyString;
}
public void test() {
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

启动线程:

1
2
3
4
5
6
7
8
9
10
11
12
public void threadStart() {
SynchronizedTest synchronizedTest = new SynchronizedTest("maintel");
ThreadTest1 threadTest1 = new ThreadTest1(synchronizedTest);
threadTest1.setName("thread1");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(synchronizedTest);
threadTest2.setName("thread2");
threadTest2.start();
ThreadTest3 threadTest3 = new ThreadTest3(synchronizedTest);
threadTest3.setName("thread3");
threadTest3.start();
}

先看看非安全线程的实例

即上面代码的输出:

I/System.out: 线程名称为:thread1在1490859330653进入同步块
I/System.out: 线程名称为:thread2在1490859330654进入同步块
I/System.out: 线程名称为:thread3在1490859330657进入同步块
I/System.out: 线程名称为:thread1在1490859333654离开同步块
I/System.out: 线程名称为:thread2在1490859333654离开同步块
I/System.out: 线程名称为:thread3在1490859333657离开同步块

可以看到方法同时被调用,是线程不安全的。

使用synchronized关键字修饰方法

修改如下:

1
2
3
4
5
6
7
8
9
10
11
synchronized public void test() { // <<========
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

再看看输入:

I/System.out: 线程名称为:thread1在1490859832827进入同步块
I/System.out: 线程名称为:thread1在1490859835827离开同步块
I/System.out: 线程名称为:thread3在1490859835828进入同步块
I/System.out: 线程名称为:thread3在1490859838828离开同步块
I/System.out: 线程名称为:thread2在1490859838828进入同步块
I/System.out: 线程名称为:thread2在1490859841829离开同步块

可以看到线程顺序执行,当一个线程执行完毕下一个线程才会进入,因此是线程安全的。

这里获取的锁是SynchronizedTest的对象实例的锁—对象锁。

多个对象多个锁

threadTest()方法修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void threadStart() {
SynchronizedTest synchronizedTest = new SynchronizedTest("maintel");
SynchronizedTest synchronizedTest2 = new SynchronizedTest("maintel2"); //<<==========
ThreadTest1 threadTest1 = new ThreadTest1(synchronizedTest);
threadTest1.setName("thread1");
threadTest1.start();
ThreadTest2 threadTest2 = new ThreadTest2(synchronizedTest2); //<<=============
threadTest2.setName("thread2");
threadTest2.start();
ThreadTest3 threadTest3 = new ThreadTest3(synchronizedTest);
threadTest3.setName("thread3");
threadTest3.start();
}

输出:

I/System.out: 线程名称为:thread2在1490860191946进入同步块
I/System.out: 线程名称为:thread1在1490860191950进入同步块
I/System.out: 线程名称为:thread2在1490860194947离开同步块
I/System.out: 线程名称为:thread1在1490860194957离开同步块
I/System.out: 线程名称为:thread3在1490860194957进入同步块
I/System.out: 线程名称为:thread3在1490860197958离开同步块

可以看到线程2和线程1同时进入方法,因为thread1thread3获取的是synchronizedTest的对象锁,而thread2获取的是synchronizedTest2的对象锁,两者不存在同步问题。

同步块synchronized (this)

test()方法修改如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test() {
try {
synchronized (this) { //<<==========
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

输入为:

I/System.out: 线程名称为:thread1在1490860807697进入同步块
I/System.out: 线程名称为:thread1在1490860810698离开同步块
I/System.out: 线程名称为:thread2在1490860810699进入同步块
I/System.out: 线程名称为:thread2在1490860813699离开同步块
I/System.out: 线程名称为:thread3在1490860813700进入同步块
I/System.out: 线程名称为:thread3在1490860816700离开同步块

可以看到同样是同步执行的,这里线程获取的是synchronized (this)括号中的对象锁,这里就是synchronizedTest的对象锁。

同步块synchronized (非this对象)

修改test()方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void test() {
try {
synchronized (anyString) { //<<==================
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

输出:

I/System.out: 线程名称为:thread1在1490861540167进入同步块
I/System.out: 线程名称为:thread1在1490861543168离开同步块
I/System.out: 线程名称为:thread2在1490861543168进入同步块
I/System.out: 线程名称为:thread2在1490861546169离开同步块
I/System.out: 线程名称为:thread3在1490861546169进入同步块
I/System.out: 线程名称为:thread3在1490861549169离开同步块

可以看到同样是同步执行的,这里线程获取的是synchronized (anyString)括号中的对象锁,这里就是anyString的对象锁。

类中存在两个不同的同步方法会怎么样?

增加一个方法test2():

1
2
3
4
5
6
7
8
9
10
11
synchronized public void test2() {
try {
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

修改线程2为:

1
2
3
4
@Override
public void run() {
synchronizedTest.test2();
}

此时运行输出为:

I/System.out: test2  线程名称为:thread2在1490862104089进入同步块
I/System.out: 线程名称为:thread3在1490862104091进入同步块
I/System.out: test2  线程名称为:thread2在1490862107090离开同步块
I/System.out: 线程名称为:thread3在1490862107092离开同步块
I/System.out: 线程名称为:thread1在1490862107092进入同步块
I/System.out: 线程名称为:thread1在1490862110092离开同步块

可以看到线程1和3是顺序执行的,线程2却是同时执行的,因为上面test2()方法被修饰以后,线程2获取到对象锁是synchronizedTest的,而线程1和3调用的是test()方法,获取到的是anyString的对象锁,两者并不冲突。

同步块synchronized (xxx.class)

SynchronizedTesttest()test2()方法分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public void test() {
try {
synchronized (this) { //<<=============
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void test2() {
try {
synchronized (SynchronizedTest.class) { //<<==============
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

此时线程2调用test2()方法,线程1和3调用test()方法,来看看输出如下:

I/System.out: test2  线程名称为:thread2在1490867370183进入同步块
I/System.out: 线程名称为:thread1在1490867370184进入同步块
I/System.out: test2  线程名称为:thread2在1490867373184离开同步块
I/System.out: 线程名称为:thread1在1490867373185离开同步块
I/System.out: 线程名称为:thread3在1490867373185进入同步块
I/System.out: 线程名称为:thread3在1490867376185离开同步块

可以看到1和3顺序执行,2却同时执行了,为什么呢?原因在于:

直接xxx.class锁是属于直接类的,而this是属于对象锁

这里关系到JVM虚拟机的一些机制,以后再做讨论。

对静态方法使用锁和普通锁的不同之处

SynchronizedTesttest()test2()方法分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
synchronized public void test() {
try {
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public static void test2() {
try {
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
Thread.sleep(3000);
System.out.println("test2 线程名称为:" + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步块");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

同样线程2调用test2()方法,线程1和3调用test()方法,来看看输出如下:

I/System.out: test2  线程名称为:thread2在1490866857936进入同步块
I/System.out: 线程名称为:thread3在1490866857938进入同步块
I/System.out: test2  线程名称为:thread2在1490866860936离开同步块
I/System.out: 线程名称为:thread3在1490866860939离开同步块
I/System.out: 线程名称为:thread1在1490866860939进入同步块
I/System.out: 线程名称为:thread1在1490866863939离开同步块

可以看到1和3线程顺序执行,线程2却同时执行了,这里就是锁普通方法和静态方法的区别,如同上面使用直接类和对象的区别:

静态方法的锁和xxx.class锁是一样的直接属于类

到此结束,自己学习使用过程中的一些练习和理解,不对之处还请指出。还有一些更深入的关于获取锁对象的机制等问题,以后再做研究。