走出多线程和锁理解的误区
?
???? 本人很少原创技术文章(其他文章页很少),一般都是看别人文章写得就转发一下,但是关于多线程这块我一直都没有找到能说中要害又通俗易懂的文章,本人不是什么大牛,但在java线程锁这一块觉得自己理解的还算正确吧,本着知识共享的精神,今天我就试着把这一块说说,用的都是最通俗的语言,最简单的代码(命名没有做到见文识意,应该批评),希望能帮助困惑中的人解脱出来,如果我理解错了,那么也请大牛们帮我解脱出来,无论如何,要么我受益,要么大家受益。
??? ?首先我说一下一部分人的错误理解,网上讲多线程的文章很多,但多数文章看完之后会留下这么一个印象:一个synchronized 关键字会锁住一段代码,被一个synchronized关键字控制的代码块同时只允许一个线程进入;
???? 其实在逻辑上这句话是对的,但他把重点放在了代码块上,这就不对了,这样容易让人进入一个误区,那就是一个synchronized关键字控制区内同时只允许进入一个线程的。哪里错了呢?错就错在一个线程进入一个synchronized区后锁住的不只是那一个synchronized之后的代码块,其他synchronized之后的代码也可能已经被锁住了,因为线程锁的重点不在synchronized关键字和其后的{}的范围,而是synchronized(obj)括号里面的参数obj,这个参数才是应该关注的焦点。
?
???? 我们可以把synchronized看成一个门,synchronized(obj)里面的obj就是钥匙,线程就是要进门的人,人进门前需要先拿钥匙开门,然后带着钥匙进去,进去后锁门不让别人进,出来后再锁上门,然后把钥匙放下,之后另一个人才能再拿上钥匙进出。多个synchronized(obj)就是多扇门,如果它们需要的是同一把钥匙,那么一旦一个人拿着钥匙进入其中一个门,就会导致其他人无法拿到钥匙,也就无法打开所有需要这把钥匙的门,而不只是刚才进入的那个门,拿着钥匙的人可以在多个门里来回进出,其他人就只能在外面等着,千万不要认为拿着钥匙的人进入了一个门就只锁住了那一个门,总结一句话:只要他们用的锁一样,不管代码块在什么位置,都会互相影响。如果这个比方还不够清楚下面我们直接用代码测试吧。
?
???? 先创建一个测试类,有两个加锁的静态方法:
public class Test1 { /** 注意这样的静态方法默认是以Test1.class作为钥匙的 相当于synchronized(Test1.class)**/ public synchronized static void staticMethod1(){ //如果我在这里睡觉,那么Test1的staticMethod2别想睡觉,当然如果他先拿到钥匙的话就是他说了算了 System.out.println("Test1.staticMethod1 要睡觉了"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Test1.staticMethod1 睡醒了。。。"); }/** 注意这样的静态方法默认是以Test1.class作为钥匙的 相当于synchronized(Test1.class)**/ public synchronized static void staticMethod2(){ //如果我在这里睡觉,那么Test1的staticMethod1别想睡觉,当然如果他先拿到钥匙的话就是他说了算了 System.out.println("Test1.staticMethod2 要睡觉了"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Test1.staticMethod2 睡醒了。。。"); }}
?
下面再写两个线程类,分别调用上面类的两个方法:
public class MyThread1 extends Thread{ public void run(){ Test1.staticMethod1(); }}
?
public class MyThread2 extends Thread{ public void run(){ Test1.staticMethod2(); }}
?
好了测试一下吧,下面是测试代码,看官也可以先预测一下结果
?
public class ThreadTester { public static void main(String[] args) { //这里线程启动顺序无所谓,只要一个线程启动了另一线程就必须等待,这是要关注的 new MyThread1().start(); new MyThread2().start(); }}
?
???? 通过运行结果大家可以得出一个结论,那就是调用任意一个synchronized(Test1.class)的方法,会锁住所有带synchronized(Test1.class)的方法,因为他们需要的钥匙Test1.class被拿走了。
上面测试了synchronized方法,下面我们再测试一下synchronized(Test1.class)代码块,现在我们新建一个类:
?
public class Test2 { public static void staticMethod1(){ synchronized(Test1.class){ //这里的钥匙是Test1.class //如果我在这里睡觉,那么Test1的方法staticMethod1和staticMethod2都别想睡觉,当然如果他们先拿到钥匙的话就是你们说了算了 System.out.println("Test2.staticMethod1 要睡觉了"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Test2.staticMethod1 睡醒了"); } }}
?
??? ?这里,在Test1的外面有一个类Test2竟然有一个方法里面有一段代码用Test1.class作为钥匙,可怕的事情总是这样发生,一定要明白如果这里有什么阻塞的话其他线程调用Test1方法的线程也会被阻塞的
? 另外再补充一点
public class Test3 { /** 注意这样的静态方法默认是以this作为钥匙的 相当于synchronized(this)**/ public synchronized void method1(){ }}
?
??? 这样的对象方法只有多个线程同时操作同一个对象时才会相互排斥,注意是“同一个对象”,多发生于单例模式;把一个对象传给两个线程做参数的情况也会形成锁竞争,但这种写法比较少见,例如下面情况:
?
public class MyThread4 extends Thread{ private Test3 t3; public MyThread4(Test3 t3){ this.t3 = t3; } public void run(){ this.t3.method1(); }}
?
?? 可以使用同一个Test3 实例构造多个多个MyThread4 线程并依次启动,就可以测出锁竞争
??? 本人才疏学浅,一点心得与大家分享,如有错误,欢迎指教,如果觉得不错,也欢迎转载