前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初学者第64节之线程同步&死锁详解(六)

初学者第64节之线程同步&死锁详解(六)

作者头像
用户5224393
发布2019-08-20 15:39:34
2620
发布2019-08-20 15:39:34
举报
文章被收录于专栏:Java研发军团

引言

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

synchronized实现异步处理

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

看个例子。

代码语言:javascript
复制
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();
        }
    }
}

结果:

代码语言:javascript
复制
A,开始时间1524296126538,name=小明,sex=男
B,开始时间1524296126549,name=小花,sex=女
A,结束时间1524296128715,name=小明,sex=男
B,结束时间1524296128725,name=小花,sex=女

静态方法的synchronied使用

synchronied加到非静态方法上使用和静态方法上的使用方式类似,效果也是一样的,但是本质上是不同的,因为synchronied加到static方法上是给Class类上加锁了,而非静态方法上加锁是给对象加锁,

下面用一个小例子验证一下非静态方法和静态方法使用锁,并非是同一个锁 。为了看出效果,我们需要使用2个静态方法和一个非静态方法,使用3个线程来执行。

代码语言:javascript
复制
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,方法。

代码语言:javascript
复制
//第三个非静态方法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");
     }
 }

结果:

代码语言:javascript
复制
线程名称为: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方法的线程,那么就会形成无线等待了,看代码!

代码语言:javascript
复制
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");
     }
}

结果:

代码语言:javascript
复制
线程名称为:A...进入printA
死循环中。。。
死循环中。。。
死循环中。。。
死循环中。。。

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

改造printB方法。

代码语言:javascript
复制
public  void printB() {
    Object object = new Object();
     synchronized (object) {
         System.out.println("线程名称为:" + Thread.currentThread().getName() + "进入printB");
         System.out.println("线程名称为:" + Thread.currentThread().getName() + "离开printB");
     }
 }
代码语言:javascript
复制
线程名称为:B...进入printB
线程名称为:A...进入printA
线程名称为:B...离开printB
死循环中。。。

看看上面的结果是使用了不同的对象锁解决了这个问题。printB方法都打印出来了。

多线程死锁

在开发中有当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞,这个我们都知道。如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。

当一组Java线程发生死锁时,这两个线程就永远不能再使用了,并且由于两个线程分别持有了两个锁,那么这两段同步代码/代码块也无法再运行了----除非终止并重启应用。不过死锁造成的影响很少会立即显现出来,一个类可能发生死锁,并不意味着每次都会发生死锁,这只是表示有可能。当死锁出现时,往往是在最糟糕的情况----高负载的情况下

比如2个人一起吃饭但是只有一双筷子,2人轮流吃(同时拥有2只筷子才能吃)。某一个时候,一个拿了左筷子,一人拿了右筷子,2个人都同时占用一个资源,等待另一个资源,这个时候甲在等待乙吃完并释放它占有的筷子,同理,乙也在等待甲吃完并释放它占有的筷子,这样就陷入了一个死循环,谁也无法继续吃饭。。。

再比如:某计算机系统中只有一台打印机和一台输入设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。

写一个简单的例子吧。

代码语言:javascript
复制
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") ;
            }
        }
    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-04-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java研发军团 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • synchronized实现异步处理
  • 静态方法的synchronied使用
  • synchronied(Class)使用
  • 同步synchronied方法无限等待与解决
  • 多线程死锁
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档