Java - On the "Sync Lock" and "deadlock"

First, the three ways to achieve multi-threaded

 

        Bloggers in this long-winded it, the Internet is also able to search, many people may know what Multithreading is, how to open a multi-threaded, but if you want to ask you achieve multi-threaded approach, which has several, perhaps you may Dayton look to want to accurately answer that question, really is not able to remember by rote, and I would again reiterate, Bowen also mentioned the implementation of the first two, as the third herein, this does not involved, interested can try yourself down;

 

(1) Thread class inheritance

 



 

 

(2) implement Runnable

 

 

 


 

 

 

(3) use ExecutorService, Callable, Future returns the results achieved have multithreading

 

   Here a little ................ ..................

 


 

Second, what Synchronize yes?

 

Easy to understand explanation:

Keyword Java language, when it used to modify a method or a block of time, to ensure that at most one thread executes the code in the same time period

Taken know almost

 


 

Third, the role of Synchronize

 

       According to the above concise explanation, we already knew this "guy" purposes, say the point of abstraction, is to give the modified objects (may be a method, object, or a section of code segments) added a " lock " and what is lock, I have an analogy it: if you go to the bathroom, toilet and only one, one only a pit toilet on the people not only you, how to do, do not we all crowded in, on this pit it? Of course not, we are most comfortable way is a person exclusive toilet, in order to do exclusive, we need to line up (the first person to acquire a lock has priority pit right ), in order to prevent other people broke into the pit in their own time , we need to put a lock on the door to the toilet when the other person "lock" on the outside, to prevent their own people do not abide by the rules when the pit "hard" breaking in; As a result, there is only waiting for us after it finished, the lock opens (releasing the lock), the next person to come in, exclusive pit of his own time;

 


   

        当然,程序中锁的释放不是由我们自己写代码手动控制的(区别于Lock接口中的unlock方法),而是由JVM说的算的,如果同步块中的代码异常的话,JVM会主动释放当前线程获得的锁,如果线程顺利执行完毕后,JVM也会主动释放锁,反之,如果线程持有对象的锁却始终处于dosomething状态时,那么其他想要获得该对象锁的线程则会一直处于wait状态,即阻塞在那;

       


        就好比,你一个人占用厕所一直不出来,结果就是,后面的人都进不来,于是乎,一个小时,两个小时过去了,当排队的人都等不及的时候,他们可能会踹门而入,也有可能会报警;当然程序中,如果线程阻塞在那的话,我们除了祈求他只是处理慢了点外只能等了,要是后面的线程处理的任务很重要的话,那这个等就要命了,怎么办,只能kill掉整个进程了,接下来就是排查代码到底是哪个环节出错了;这就是synchronize的弊端,如果线程拿到锁不作为一直阻塞在那的话,其他线程只能等待了,等到地老天荒、海枯石烂,我去,我要是排在后面那个wait他的线程,说什么我都要知道是谁TM给我堵那了!我要Kill他!

     


        当然,还有一种锁叫ReentrantLock,区别于synchronize,这个锁是一个类,实现了Lock接口,我们看下Lock接口中有哪些方法:

 

 

       这个锁就很厉害了,我们完全可以做到自主控制锁;比如每次养成好的习惯在finally块中写unlock();比如线程执行时,先tryLock(),看看锁有没有被占用,如果占用的话,线程也不能闲着,可以去干其他事情,而且还提供了带参数的tryLock(Long,TimeUnit),可以在多少时间内拿不到锁的话去执行其他事情;再比如调用lockInterruptibly()可以中断阻塞的线程而不必让线程一直在那等等等,等到海枯石烂....本篇不对这个锁做详细的案列分析,后面有时间再单开博文说明吧;

 


 

四、知识扩充(start() 和 run() 的区别)

 

 

  /**
     * 一定要区别于start()和run()方法
     * (1)run()为Runnable接口中的方法
     * (2)Thread线程类实现了Runnable接口
     * (3)Thread线程类中包含了一个Runnable的实例target,run()方法真正是由target实现的【target.run()】
     * (4)start()方法为Thread类中的方法,其加了synchronized关键字,防止一个同步操作被多次启动
     * (5)start()方法内部实现机制调用的是本地库方法(native)
     * (6)start是开启一个线程,可以达到多线程执行的效果,start后并不会立马执行线程,而是
     *      交给cpu来调度
     * (7)run只是一个普通的方法,执行它,程序依然只有一个主线程
     *      且run方法中的代码块执行完后,才能执行下面的代码,没有真正达到多线程执行的效果
     */

 

  我们基于上面的注释说明,来看一段demo:

 

package com.appleyk.dbinit.MyLock;

/**
 * <p>start()和run()的区别</p>
 *
 * @author appleyk
 * @version V.1.0.1
 * @blob https://blog.csdn.net/appleyk
 * @date created on 下午 6:04 2019-7-1
 */
public class DeadLock0 {

    public static void main(String[] args) {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " -- 我执行了!");
            }
        });

        long start = System.currentTimeMillis();
        thread.start(); // 异步的,开启一个线程后,并不会立马执行线程,而是扔给cpu进行调度,因此下面的内容继续执行,不受影响
        System.out.println("耗时:"+( System.currentTimeMillis()-start));
        start = System.currentTimeMillis();
        thread.run();  // 同步的,必须执行完run方法块中的代码才能继续执行下面的内容(阻塞)
        System.out.println("耗时:"+( System.currentTimeMillis()-start));

    }

}

 

  直接来看输出结果:

 

 

 


 

五、什么是死锁

 

 

(1)产生死锁的四个必要条件

 

A、互斥使用(资源独占) 


      一个资源每次只能给一个线程使用 

      说明:对象锁只有一把,同一时间,只能有一个线程持有,其他线程需等待



B、不可强占(不可剥夺) 


      资源申请者不能强行的从资源占有者手中夺取资源,资源只能由占有者自愿释放 

      说明:当线程A拿到对象锁时,线程B除了等待线程A主动释放对象锁时,什么都干不了(想都别想



C、请求和保持
   

      一个线程在申请新的资源的同时保持对原有资源的占有 

     说明:线程A原本持有对象M的锁,但又想要申请获取对象N的锁,这时候,如果获得对象N的锁遇到阻塞时,就会导致线程A

     原本持有的对象M的锁无法得到释放,这就导致其他想要获取对象M锁的线程陷入无限的等待中



D、循环等待 


     存在一个线程等待队列  {T1 , T2 , … , Tn},,其中T1等待T2释放占有的资源,T2等待T3释放占有的资源,…,Tn等待T1释放       占有的资源,形成一个线程等待环路

     说明:A说B写的模块代码有问题,B说C写的模块代码有问题,C又反过来说是A写的不对,卧槽,这..... egg疼!


 

(2)如何避免死锁

 

       上述产生死锁的四个必要条件只要有一个不成立,就可以推翻或者排除出现死锁的可能,因此,我们在使用多线程开发程序之前,一定要好好设计和斟酌一下,防止写出来的程序在线程调度上出岔子,造成死锁就麻烦了,慎重


 

(3)小结

 

        什么是死锁:两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去 (摘自知乎)

 


 

六、手写"死锁"

 

前言:知道了死锁是什么后,接下来我们就针对死锁的特性,手写一段代码,模拟一下两个线程互相抢夺资源无果造成无限等待的场景;

 


 

(1) 代码1 -- 继承Thread类

 

package com.appleyk.dbinit.MyLock;

/**
 * <p>手写死锁 -- 方式1</p>
 *
 * @author appleyk
 * @version V.1.0.1
 * @blob https://blog.csdn.net/appleyk
 * @date created on 上午 8:14 2019-7-1
 */
public class DeadLock1 {

    public static void main(String[] args) {

        final Object obj1 = new Object();
        final Object obj2 = new Object();

        MyThread1 thread1 = new MyThread1(obj1, obj2);
        MyThread2 thread2 = new MyThread2(obj1, obj2);

        // 开启多线程,模拟死锁
        thread1.start();
        thread2.start();

    }
}

class MyThread1 extends Thread{

    private final Object obj1 ;
    private final Object obj2 ;

    public MyThread1(Object obj1 , Object obj2) {
        this.obj1 = obj1 ;
        this.obj2 = obj2;
    }

    @Override
    public void run() {

        // 线程1获得obj1对象的锁(其他线程等待arr1对象锁释放)
        synchronized(obj1){

            System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            /**
             * 1、线程1获得obj2对象的锁(其他线程等待obj2对象锁释放)
             * 2、如果其他线程在线程1之前先拿到obj2对象锁的话,线程1需等待
             */
            synchronized(obj2){
                System.out.println(Thread.currentThread().getName()+"--获得对象obj2的锁");
            }
        }
    }
}


class MyThread2 extends Thread {

    private final Object obj1;
    private final Object obj2;

    public MyThread2(Object obj1, Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    @Override
    public void run() {

        /**
         * 1、线程2获得obj2对象的锁(其他线程等待obj2对象锁释放)
         * 2、如果其他线程在线程2之前先拿到obj2对象锁的话,线程2需等待
         */
        synchronized (obj2) {

            System.out.println(Thread.currentThread().getName() + "--已获得对象obj2的锁,准备获得对象obj1的锁");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            /**
             * 线程2获得obj1对象的锁(其他线程等待obj1对象锁释放)
             */
            synchronized (obj1) {
                System.out.println(Thread.currentThread().getName() + "--获得对象obj1的锁");
            }
        }
    }
}

 


 

执行效果:

 

 


 

补充:这两个哥们也是挺逗的,我等你,你等我,谁都不肯把自己手里处理完的对象锁释放了给对方用,索性,咱俩就一直干等着呗,等到海枯石烂..... 等等,这代码可是我写的啊,为什么会这样呢? 

 

 


 

(2) 代码2 -- 继承Thread类,通过Int标识控制获取对象锁的顺序

 

package com.appleyk.dbinit.MyLock;

/**
 * <p>手写死锁 -- 方式2</p>
 *
 * @author appleyk
 * @version V.1.0.1
 * @blob https://blog.csdn.net/appleyk
 * @date created on 下午 5:18 2019-7-1
 */
public class DeadLock2 {

    public static void main(String[] args) {

        Thread thread1 = new MyThread( 1 );
        Thread thread2 = new MyThread( 0 );
        thread1.start();
        thread2.start();

    }
}

class MyThread extends Thread{

    private static final Object obj1 = new Object() ;
    private static final Object obj2 = new Object();
    private Integer flag = 1;

    MyThread( Integer flag){
        this.flag = flag;
    }

    @Override
    public void run() {

        if(1 == flag){
            synchronized (obj1){
                System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2){
                    // dosomething
                    System.out.println(Thread.currentThread().getName()+"-- 获得了对象obj2的锁");
                }
            }
        }else {
            synchronized (obj2){
                System.out.println(Thread.currentThread().getName()+"--已获得对象obj2的锁,准备获得对象obj1的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1){
                    // dosomething
                    System.out.println(Thread.currentThread().getName()+"-- 获得了对象obj1的锁");
                }
            }
        }
    }
}

 


 

效果(和第一种方式一样,这不废话吗):

 

 


 

(3) 代码3 -- 实现Runnable接口

 

package com.appleyk.dbinit.MyLock;

/**
 * <p>手写死锁 -- 方式3</p>
 *
 * @author appleyk
 * @version V.1.0.1
 * @blob https://blog.csdn.net/appleyk
 * @date created on 下午 4:30 2019-7-1
 */
public class DeadLock3 {

    static final Object obj1 = new Object();
    static final Object obj2 = new Object();

    public static void main(String[] args) {
        deadLock();
    }

    private static void deadLock(){

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(obj1){
                    System.out.println(Thread.currentThread().getName()+"--已获得对象obj1的锁,准备获得对象obj2的锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj2){
                        System.out.println(Thread.currentThread().getName()+"--获得对象obj2的锁");
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(obj2){
                    System.out.println(Thread.currentThread().getName()+"--已获得对象obj2的锁,准备获得对象obj1的锁");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj1){
                        System.out.println(Thread.currentThread().getName()+"-获得对象obj1的锁");
                    }
                }
            }
        }).start();
    }
}

 


 

效果同上(还是贴图吧,省得有些人有强迫症,说我没测试过就贴代码):

 

 


 

 

七、如何知道你的程序出现了"死锁"

 

 

A、使用Java安装路径下的../bin/jconsole.exe

 

 


 

(1)连接主进程

 

 

 


 

 


 

(2)查看&检测

 

 


 

 

B、利用jstack命令

 

(1)JPS查看当前运行着的Java进程

 

 


 

(2)jstack检测指定pid是否发生死锁

 

 


 

 


 

 

        上图中,我们可以很明显的看出,Thread-1在等待获得对象<0x00...76b27dc78>的lock,但是对象<0x00...76b27dc78>的锁却被Thread-2所持有(被locked了),因为此情况满足产生死锁的条件,所以,我们最后可以看到检测的结果:Found 1 deadlock.

 


 

 

八、写在最后

 

        关于并发,必然要提到锁,关于锁,还是有很多要讲的,本篇只是浅显的介绍了同步锁和死锁,关于锁的设计思想和类型,还有很多很多要说道的地方,由于博主也是在不断的充电中,所以后续的内容博主会慢慢补充,比如什么是CAS啊,什么是AQS啊....等等等

      

Guess you like

Origin blog.csdn.net/Appleyk/article/details/94402475