1. 首页
  2. 网络编程教程

005-五、网络编程之线程同步&死锁详解

引言

我们再谈 synchronied 同步,现在我们都知道了 synchronied 是同步代码了,它默认是的对象锁是 this,往往效率都不高,因为每次执行到 synchronized 代码块时,其它的 synchronized 代码块都会被阻塞,那么怎么能提高效率呢?当然能实现同步就可以实现异步了。
synchronized 实现异步处理

前面一直使用的 synchronied(this)锁,但是这样会有让效率很低,因为当线程运行到这个对象锁,就会阻塞这个对象下的其它所有的锁了,所以这样将会让运行效率大大降低,可以弄一个异步处理一段代码块,处理方法就是不要使用对象监视器(this)了,可以在同步代码块的前面定义一个临时对象Object obj = new Object();然后使用synchronied(obj),那么这样就是锁的对象监视器不同了,也就达到异步的效果了。这样效率也大大提高了。
看个例子。


public class TestThread6 { public static void main(String[] args) throws InterruptedException { ThreadEntity threadEntity = new ThreadEntity(); Runnable myRunnable1 = new MyRunnable(threadEntity,"小明","男"); Thread thread1 = new Thread(myRunnable1); thread1.setName("A"); Runnable myRunnable2 = new MyRunnable(threadEntity,"小花","女"); Thread thread2 = new Thread(myRunnable2); thread2.setName("B"); thread1.start(); thread2.start(); } } class MyRunnable implements Runnable { private ThreadEntity threadEntity; private String name; private String sex; public MyRunnable(ThreadEntity threadEntity,String name,String sex) { this.threadEntity = threadEntity; this.name = name; this.sex = sex; } @Override public void run() { this.threadEntity.show(this.name,this.sex); } } class ThreadEntity { /** * 大家都知道局部变量是不会出现脏读的 * @param name * @param sex */ public void show(String name,String sex) { try { /** * 实现异步处理就需要同步代码块使用各自的对象锁就可以了 */ Object obj = new Object(); synchronized (obj) { System.out.println(Thread.currentThread().getName()+",开始时间"+System.currentTimeMillis() + ",name=" + name + ",sex=" + sex); //为了演示效果这里休眠2秒 Thread.sleep(2000); System.out.println(Thread.currentThread().getName()+",结束时间"+System.currentTimeMillis() + ",name=" + name + ",sex=" + sex); } } catch (InterruptedException e) { e.printStackTrace(); } } } //结果: A,开始时间1524296126538,name=小明,sex=男 B,开始时间1524296126549,name=小花,sex=女 A,结束时间1524296128715,name=小明,sex=男 B,结束时间1524296128725,name=小花,sex=女

静态方法的synchronied使用

synchronied 加到非静态方法上使用和静态方法上的使用方式类似,效果也是一样的,但是本质上是不同的,因为 synchronied 加到 static 方法上是给 Class 类上加锁了,而非静态方法上加锁是给对象加锁,
下面用一个小例子验证一下非静态方法和静态方法使用锁,并非是同一个锁 。为了看出效果,我们需要使用2个静态方法和一个非静态方法,使用3个线程来执行。


public class TestThread601 { public static void main(String[] args) throws InterruptedException { //共享类 ThreadEntity601 threadEntity601 = new ThreadEntity601(); //执行第一个静态方法的线程 Runnable myRunnable1 = new MyRunnable601(threadEntity601,"printA"); Thread thread1 = new Thread(myRunnable1); thread1.setName("A..."); //执行第二个静态方法的线程 Runnable myRunnable2 = new MyRunnable601(threadEntity601,"printB"); Thread thread2 = new Thread(myRunnable2); thread2.setName("B..."); //执行第三个非静态方法的线程 Runnable myRunnable3 = new MyRunnable601(threadEntity601,"printC"); Thread thread3 = new Thread(myRunnable3); thread3.setName("C..."); thread1.start(); thread2.start(); thread3.start(); } } class MyRunnable601 implements Runnable { private String flag; private ThreadEntity601 threadEntity601; public MyRunnable601(ThreadEntity601 threadEntity601,String flag) { this.flag = flag; this.threadEntity601 = threadEntity601; } @Override public void run() { if (this.flag == "printA") { this.threadEntity601.printA(); } else if (this.flag == "printB") { this.threadEntity601.printB(); } else { this.threadEntity601.printC(); } } } class ThreadEntity601 { //第一个静态方法synchronized public synchronized static void printA() { try { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在 " + System.currentTimeMillis() + "进入printAA"); //为了演示效果在此处加一个休眠3秒钟 Thread.sleep(3000); System.out.println("线程名称为:" + Thread.currentThread().getName() + " 在 " + System.currentTimeMillis() + "离开printA"); } catch (InterruptedException e) { e.printStackTrace(); } } //第二个静态方法synchronized public synchronized static void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB"); } //第三个非静态方法synchronized public synchronized void printC() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC"); } } //结果显示效果和异步很像 线程名称为:C...在1524303493953进入printC 线程名称为:A...在 1524303493950进入printAA 线程名称为:C...在1524303494083离开printC 线程名称为:A... 在 1524303497087离开printA 线程名称为:B...在1524303497089进入printB 线程名称为:B...在1524303497091离开printB

以上结果显示成异步的效果的原因,是它们持有的不是同一把锁,printC持有的是一把对象锁,printA,printB持有的是同一把锁(Class锁),Class锁可以对该类的所有Class锁都有效。

synchronied(Class)使用

synchronied(Class) 代码块锁也是 Class 锁。它与 synchronied static 方法的作用一致,也是对整个类加锁。
改造刚刚那个printA,printB,printC,方法。


//第三个非静态方法synchronized public static void printC() { synchronized (ThreadEntity601.class) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printC"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printC"); } } 结果: 线程名称为:A...在 1524305002343进入printAA 线程名称为:A... 在 1524305005480离开printA 线程名称为:C...在1524305005483进入printC 线程名称为:C...在1524305005484离开printC 线程名称为:B...在1524305005486进入printB 线程名称为:B...在1524305005488离开printB

这样就完全实现同步了。

同步synchronied方法无限等待与解决

创建一个 ThreadEntity601 类,类中定一个定义2个方法,2个printA,printB同步方法,在 printA 方法中定一个死循环,printB 中打印2句话。然后创建2个线程执行不同的方法。先启动调用 printA 方法的线程,那么就会形成无线等待了,看代码!


public class TestThread601 { public static void main(String[] args) throws InterruptedException { //共享类 ThreadEntity601 threadEntity601 = new ThreadEntity601(); //执行第一个静态方法的线程 Runnable myRunnable1 = new MyRunnable601(threadEntity601,"printA"); Thread thread1 = new Thread(myRunnable1); thread1.setName("A..."); //执行第二个静态方法的线程 Runnable myRunnable2 = new MyRunnable601(threadEntity601,"printB"); Thread thread2 = new Thread(myRunnable2); thread2.setName("B..."); thread1.start(); thread2.start(); } } class MyRunnable601 implements Runnable { private String flag; private ThreadEntity601 threadEntity601; public MyRunnable601(ThreadEntity601 threadEntity601,String flag) { this.flag = flag; this.threadEntity601 = threadEntity601; } @Override public void run() { if (this.flag == "printA") { try { this.threadEntity601.printA(); } catch (InterruptedException e) { e.printStackTrace(); } } else if (this.flag == "printB") { this.threadEntity601.printB(); } } } class ThreadEntity601 { public synchronized void printA() throws InterruptedException { System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printA"); boolean flag = true; while (flag) { //这里休眠一下让打印执行减慢 Thread.sleep(2000); System.out.println("死循环中。。。"); } System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printA"); } public synchronized void printB() { System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printB"); } } 结果: 线程名称为:A...进入printA 死循环中。。。 死循环中。。。 死循环中。。。 死循环中。。。

这种情况下,A线程一直在执行,B线程无法拿到锁就一直,这样就锁死了,现在为了解决这个问题,可以使用同步代码块synchronied()来解决。
改造printB方法。


public void printB() { Object object = new Object(); synchronized (object) { System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printB"); System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printB"); } } 线程名称为:B...进入printB 线程名称为:A...进入printA 线程名称为:B...离开printB ```` 死循环中。。。 看看上面的结果是使用了不同的对象锁解决了这个问题。`printB`方法都打印出来了。 ## 多线程死锁 在开发中有当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。 当一组`Java`线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了----除非终止并重启应用。 不过死锁造成的影响很少会立即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,往往是在最糟糕的情况----高负载的情况下。 比如2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。。。 再比如:某计算机系统中只有一台打印机和一台输入设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程`P2` 所占用,而P2在未释放打印机之前,又提出请求使用正被`P1`占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。 写一个简单的例子吧。 ```java ublic class TestThread601 { public static void main(String[] args) throws InterruptedException { //共享类 ThreadEntity601 threadEntity601 = new ThreadEntity601(); //执行第一个静态方法的线程 Runnable myRunnable1 = new MyRunnable601(threadEntity601,"method1"); Thread thread1 = new Thread(myRunnable1); //执行第二个静态方法的线程 Runnable myRunnable2 = new MyRunnable601(threadEntity601,"method2"); Thread thread2 = new Thread(myRunnable2); thread1.start(); thread2.start(); } } class MyRunnable601 implements Runnable { private String flag; private ThreadEntity601 threadEntity601; public MyRunnable601(ThreadEntity601 threadEntity601,String flag) { this.flag = flag; this.threadEntity601 = threadEntity601; } @Override public void run() { if (this.flag == "method1") { try { this.threadEntity601.method1(); } catch (Exception e) { e.printStackTrace(); } } else if (this.flag == "method2") { try { this.threadEntity601.method2(); } catch (Exception e) { e.printStackTrace(); } } } } class ThreadEntity601 { private final Object lock1 = new Object() ; private final Object lock2 = new Object() ; public void method1() throws Exception { synchronized (lock1) { System.out.println("mothod1执行了lock1"); //为了演示效果明显这里休眠2秒 Thread.sleep(2000) ; //这里拿到了method2方法中的lock2对象锁 synchronized (lock2) { System.out.println("mothod1执行了lock2") ; } } } public void method2() throws Exception { synchronized (lock2) { System.out.println("mothod2执行了lock2"); //为了演示效果明显这里休眠2秒 Thread.sleep(2000) ; //这里拿到了method1方法中的lock1对象锁 synchronized (lock1) { System.out.println("mothod2执行了lock1") ; } } } }

写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:005-五、网络编程之线程同步&死锁详解

    链接:https://www.javajike.com/article/1228.html

« 006-六、网络编程之生产者消费者(一)
004-四、网络编程之线程安全详解»

相关推荐

QR code