上一篇文章我们通过一个实例来说明了并发编程为什么要做同步处理,下面我们再来巩固一下。
对象如果拥有可变状态的变量,并且被多线程访问,那么这个时候我们要对可变状态变量的状态改变做原子操作处理。
锁机制是保证这样的操作的一个有效的方法,它可以保证变量的状态在被更新时是在一个原子操作中进行的。
java提供了一种内置锁机制来支持原子性:同步代码块(Synchronized Block)。
同步代码块包括两个部分:一个是作为锁的对象引用,一个是作为由这个锁保护的代码块。
让我们在来回忆上一篇文章最后的问题:
1:我们可以看到上面例子的synchronized是加在了方法上,那么我们还可以怎么写呢?
加在方法上的synchronized锁住的是整个方法体的同步代码块,同步代码块有了,那锁的对象是什么呢?让我们来看这样一段代码:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { int id = 0; @safe public <span style="color:#ff0000;">synchronized </span>int getId(){ <span style="color:#ff0000;">return ++id;//锁代码块</span> } } </span>
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadStart { public static void main(String[] para){ </span>
<span style="font-size:18px;"> <span style="color:#ff0000;">//这里的safe就是锁对象,它是</span><span style="color:#ff0000;">SafeThread()的一个实例的引用 </span> SafeThread safe = new SafeThread(); for(int i=0;i<10;i++){ ThreadRead1 t1 = new ThreadRead1(safe); ThreadRead2 t2 = new ThreadRead2(safe); t1.start(); t2.start(); } } } </span>
我们弄清楚了,锁对象和锁的代码块,那也就可以想出来,锁代码块的写法可以有很多种,只要我们获得了锁对象,那么不管锁代码块是怎么写的都可以实现多线程同步。
下面我们来看一下以下这几种写法:
写法1:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { int id = 0; @safe public int getId(){ synchronized(SafeThread.class){ return ++id; } } } </span>
写法2
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { int id = 0; Object sync = new Object(); @safe public int getId(){ synchronized(sync){ return ++id; } } } </span>
写法3:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { static int id = 0; @safe public static synchronized int getId(){ return ++id; } } </span>
使用static来修饰的方法默认是以Class对象作为锁的。
2:synchronized锁住的是对象还是代码或方法?
通过对上面问题的分析,我可以想象一下,我们锁住的到底是什么?
答案:应该是对象。
让我们使用实例来说明一切!
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { static int id = 0; @safe public static synchronized int getId(){ return ++id; } public synchronized void testPrint(){ System.out.println("Enter testPrint method !"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Exit testPrint method !"); } } </span>
testPrint方法已经加上了synchronized,下面我们来多线访问调用一下这个方法。
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadStart { public static void main(String[] para){ for(int i=0;i<10;i++){ ThreadRead1 t1 = new ThreadRead1(); t1.start(); } } } </span>
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadRead1 extends Thread{ SafeThread safe = null; public ThreadRead1(){ } public ThreadRead1(SafeThread o){ safe = o; } public void run() { safe = new SafeThread(); safe.testPrint(); } }</span>
看一下结果:
<span style="font-size:18px;">Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Enter testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method ! Exit testPrint method !</span>
这样的结果有些意思,我们已经给方法加上了synchronized了,为什么不起作用呢?
原因很简单,我们看一下线程ThreadRead1实现代码,SafeThread对象是在线程中才创建的,那也就是说10个线程是创建了十个SafeThread的实例,所以即使synchronized的锁定的代码块也不起作用。
那我们修改一下代码:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadStart { public static void main(String[] para){ <span style="color:#ff0000;">SafeThread safe = new SafeThread();</span> for(int i=0;i<10;i++){ ThreadRead1 t1 = new <span style="color:#ff6666;">ThreadRead1(safe)</span>; t1.start(); } } } </span>
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadRead1 extends Thread{ SafeThread safe = null; public ThreadRead1(){ } public ThreadRead1(SafeThread o){ safe = o; } public void run() { safe.testPrint(); } } </span>
我们看下运行结果:
<span style="font-size:18px;">Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method ! Enter testPrint method ! Exit testPrint method !</span>
这样的结果才是我们想要的,为什么这次synchronized管事了呢?我们看到ThreadStart类中main方法中红色的部分,我们在启动线程之前创建了SafeThread的实例及其引用,并把它的引用作为参数来初始化线程类ThreadRead1,那这时候SafeThread的实例的引用就是一个香饽饽了,谁得到谁就可以锁定synchronized锁定的代码块并执行完该块代码。
下面还有个例子请大家自己实践一下,看看结果如何?
示例:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadStart { public static void main(String[] para){ for(int i=0;i<3;i++){ ThreadRead1 t1 = new ThreadRead1(); t1.start(); } } } </span>
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class ThreadRead1 extends Thread{ SafeThread safe = null; public ThreadRead1(){ } public ThreadRead1(SafeThread o){ safe = o; } public void run() { safe = new SafeThread(); safe.testPrint(); } } </span>
被调用的类的两种实现如下:
实现1:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { static int id = 0; @safe public static synchronized int getId(){ return ++id; } public void testPrint(){ <span style="color:#ff0000;">synchronized(SafeThread.class)</span>{ System.out.println("Enter testPrint method !"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Exit testPrint method !"); } } } </span>
实现2:
<span style="font-size:18px;">package com.home.thread; /** * @author gaoxu * */ public class SafeThread { static int id = 0; @safe public static synchronized int getId(){ return ++id; } public <span style="color:#ff0000;">static synchronized </span> void testPrint(){ System.out.println("Enter testPrint method !"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Exit testPrint method !"); } } </span>
这个例子也十分明显说明了,synchronized锁定的是对象,而非代码,大家运行一下将会理解的更深刻。
今天的跟着实例学java多线程就到这里,我们来思考下面两个问题。
1:不同的synchronized的写法有什么区别,又该怎么写创建线程的代码呢?
2:死锁、活跃性问题都是怎么产生的。