这一篇是继上一篇java线程之后的补充,我们讲的是java多线程的同步机制,主要是对synchronized的用法的细谈。
java多线程的同步依靠的是对象锁机制,synchronized关键字就是利用了封锁来
实现对共享资源的互斥访问。
一.首先对synchronized关键字说明几点:
(1)synchronized用来标识一个普通方法时,表示一个线程要执行该方法,必须取得该方法所在的对象的锁。
(2)synchronized用来标识一个静态方法时,表示一个线程要执行该方法,必须获得该方法所在的类的类锁。
(3)synchronized修饰一个代码块。类似这样:synchronized(obj) { 代码 }。表示一个线程要执行该代码块,必须获得obj的锁。这样做的目的是减小锁的粒度,保证当不同块所需的锁不冲突时不用对整个对象加锁。
二.下面分别用示例说明synchronized是怎样实现多线程锁机制的
下面以一个简单的实例来进行对比分析。实例要完成的工作非常简单,就是创建2(或多于2)个线程,每个线程都打印从0到9这10个数字(为了看得清晰直观可以打印100个数字,这里我们为了节省空间就用10个数字了),我们希望线程之间不会出现交叉乱序打印,而是顺序地打印。
(1)Synchronized修饰方法 和Synchronized语句块this的搭配使用
这里我们在run()方法中加入了synchronized关键字,希望能对run方法进行互斥访问,但结果并不如我们希望那样,这 是因为这里synchronized锁住的是this对象,即当前运行线程对象本身。代码中创建了2个线程,而每个线程都持有this对象的对象锁,这 不能实现线程的同步。
代码如下:
package day17; /* * Synchronized修饰方法 * Synchronized语句块this的搭配使用 */ public class Thread_Syn1 { public static void main(String[] args) { //每个线程在运行时使用的是各自的this也就是各自的锁,当然也就不能实现同步机制了 for(int i=0;i<2;i++){ MyThread1 my=new MyThread1(); Thread t=new Thread(my); t.start(); } } } class MyThread1 implements Runnable{ @Override public/* synchronized */void run() { synchronized (this) { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--"+i); } } } }
运行结果:
Thread-1--0 Thread-0--0 Thread-0--1 Thread-0--2 Thread-0--3 Thread-1--1 Thread-0--4 Thread-1--2 Thread-0--5 Thread-1--3 Thread-0--6 Thread-1--4 Thread-0--7 Thread-1--5 Thread-0--8 Thread-1--6 Thread-0--9 Thread-1--7 Thread-1--8 Thread-1--9
由结果可以看出并未实现同步机制,我们可以稍稍改动一下,使得创建的线程拥有用一个锁,改动的代码如下:
package day17; /* * Synchronized关键字初步 */ public class Thread_Syn { public static void main(String[] args) { MyThread my=new MyThread(); //同一个MyThread类创建的线程t1和t2拥有相同的this即拥有相同的一把锁,也就自然能实现同步机制了 Thread t1=new Thread(my); Thread t2=new Thread(my); t1.start(); t2.start(); } } class MyThread implements Runnable{ @Override public/* synchronized */void run() { synchronized (this) { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--"+i); } } } }
运行结果:
Thread-0--0 Thread-0--1 Thread-0--2 Thread-0--3 Thread-0--4 Thread-0--5 Thread-0--6 Thread-0--7 Thread-0--8 Thread-0--9 Thread-1--0 Thread-1--1 Thread-1--2 Thread-1--3 Thread-1--4 Thread-1--5 Thread-1--6 Thread-1--7 Thread-1--8 Thread-1--9
(2) Synchronized语句块与共享对象锁的搭配使用
(通过外部创建共享资源,然后传递到线程中来实现)
从上述代码段可以得知,要想实现线程的同步,则这些线程必须去竞争一个唯一的共享的对象锁。
基于这种思想,我们将第一段代码修改如下所示,在创建启动线程之前,先创建一个线程之间竞争使用的Object对象,然后将这个Object对象的引用 传递给每一个线程对象的lock成员变量。这样一来,每个线程的lock成员都指向同一个Object对象。我们在run方法中,对lock对象使用 synchronzied块进行局部封锁,这样就可以让线程去竞争这个唯一的共享的对象锁,从而实现同步。
package day17; /* * Synchronized语句块与共享对象锁的搭配使用 * (通过外部创建共享资源,然后传递到线程中来实现) */ public class Thread_Syn2 { public static void main(String[] args) { //在创建启动线程之前,先创建一个线程之间竞争使用的Object对象 Object o=new Object(); //创建两个线程拥有同一个对象锁 for(int i=0;i<2;i++){ MyThread2 my=new MyThread2(o); Thread t=new Thread(my); t.start(); } } } class MyThread2 implements Runnable{ private Object o; public MyThread2(Object o){ this.o=o; } @Override public /*synchronized */void run() { //封装这个共同的对象锁 synchronized (o) { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--"+i); } } } }
运行结果:
Thread-0--0 Thread-0--1 Thread-0--2 Thread-0--3 Thread-0--4 Thread-0--5 Thread-0--6 Thread-0--7 Thread-0--8 Thread-0--9 Thread-1--0 Thread-1--1 Thread-1--2 Thread-1--3 Thread-1--4 Thread-1--5 Thread-1--6 Thread-1--7 Thread-1--8 Thread-1--9
(3)Synchronized语句块与static修饰的共享对象锁
(利用类成员变量被所有类的实例所共享这一特性,因此可以将锁机制用静态成员对象来实现)
我们也可以利用类成员变量被所有类的实例所共享这一特性,因此可以将lock用静态成员对象来实现,代码如下所示:
package day17; /* * Synchronized语句块与static修饰的共享对象锁 * (利用类成员变量被所有类的实例所共享这一特性,因此可以将锁机制用静态成员对象来实现) */ public class Thread_Syn3 { public static void main(String[] args) { for(int i=0;i<2;i++){ MyThread3 my=new MyThread3(); Thread t=new Thread(my); t.start(); } } } class MyThread3 implements Runnable{ //创建静态共享对象 private static Object o=new Object(); @Override public void run() { synchronized (o) { for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--"+i); } } } }
运行结果:
Thread-0--0 Thread-0--1 Thread-0--2 Thread-0--3 Thread-0--4 Thread-0--5 Thread-0--6 Thread-0--7 Thread-0--8 Thread-0--9 Thread-1--0 Thread-1--1 Thread-1--2 Thread-1--3 Thread-1--4 Thread-1--5 Thread-1--6 Thread-1--7 Thread-1--8 Thread-1--9
(4)Synchronized与static来修饰功能类,在run方法中调用功能类
再来看第一段代码,实例方法中加入sychronized关键字封锁的是this对象本身,而在静态方法中加入sychronized关键字封锁的就是 类本身。静态方法是所有类实例对象所共享的,因此线程对象在访问此静态方法时是互斥访问的,从而可以实现线程的同步,代码如下所示:
package day17; /* * Synchronized与static来修饰功能类 * 在run方法中调用功能类 */ public class Thread_Syn4 { public static void main(String[] args) { for(int i=0;i<2;i++){ MyThread4 my=new MyThread4(); Thread t=new Thread(my); t.start(); } } } class MyThread4 implements Runnable{ @Override public void run() { MyThread4.test(); } public static synchronized void test(){ for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--"+i); } } }
运行结果:
Thread-0--0 Thread-0--1 Thread-0--2 Thread-0--3 Thread-0--4 Thread-0--5 Thread-0--6 Thread-0--7 Thread-0--8 Thread-0--9 Thread-1--0 Thread-1--1 Thread-1--2 Thread-1--3 Thread-1--4 Thread-1--5 Thread-1--6 Thread-1--7 Thread-1--8 Thread-1--9
对以上的一点总结:
对于多线程的同步问题主要是依赖锁机制,而且要记住只有在多个线程共同争夺一个共享的锁的时候才能线程同步。