打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
初学者第64节之线程同步&死锁详解(六)
Java自学者聚集地
500G 免费视频大赠送

公告通知

本公众专注于为广大Java初学者和爱好者以及热爱编程的菜鸟提供学习交流平台,共同成长。 如果您发现什么错误、遗漏或不明的地方,可以通过文章下留言、群内讨论或私撩小编提出意见反馈,当然小编会给这些奉献的朋友分享Java 大型互联网项目实战视频资料(不定期更新视频资料) ,希望与大家一同成长!!!


引言

我们再谈 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占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。


写一个简单的例子吧。

public 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') ;
           }
       }
   }
}
 最近文章 

  


线程安全详解(五)


线程优先级和守护详解(四)


线程停止详解(三)


多线程Runnable(二)


Java之多线程(一)

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
androidhandler多线程demo
Java总结篇系列:Java多线程(二)
一道关于java线程的面试题
10 张图聊聊线程的生命周期和常用 APIs
java并发(四)如何创建并运行java线程
多线程实现方式
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服