Java 多线程使用以及锁机制

Java开启线程,用来执行异步功能,废话少说,直接上第一种方式:

方式1:new Thread()

new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                Log.d(TAG, "线程名字: " +name);
            }
        }).start();

看下Log:

我们创建Thread不调用start(),直接通过Thread调用run(),会怎么怎样?

代码改成:

Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                Log.d(TAG, "线程名字: " + name);
            }
        });
        thread.run();

看log:

所以要注意,开启线程,必须通过调用start()开启,通过Thread直接调用run()方法,只是单纯的调用一个方法而已,并没有开启一个线程。因为这次调用是在主线程调用的,所以当前线程是main线程。

很简单,开启了一个线程。但是这么开启一个线程,在平常Android开发过程中是不允许的。忽然开启一个线程,他会导致内存会抖动一下。如果内存频繁的抖动,就会导致触发GC频繁的回收垃圾,GC的回收会让所有线程都停止,包括主线程也会停止的,必然会导致页面的卡顿。应该维护一个线程池,用线程池来执行这个异步功能。至于怎么使用线程池,就看我之前写的文章了。

然而,这种开启线程还有弊端,比如:它什么结束,执行结果是什么,我们不知道。

所以,这个时候需要CallableFuture 的出现

方式2:Future

先看一下Future的源码

public interface Future<V>{

            @Override
            public boolean cancel(boolean mayInterruptIfRunning) {
                return false;
            }

            @Override
            public boolean isCancelled() {
                return false;
            }

            @Override
            public boolean isDone() {
                return false;
            }

            @Override
            public V get() throws ExecutionException, InterruptedException {
                return null;
            }

            @Override
            public V get(long timeout, @NonNull TimeUnit unit) throws ExecutionException, InterruptedException, TimeoutException {
                return null;
            }

}

我们可以看到,Future其实是一个接口,其中通过泛型定义 各种返回值。有几个5个方法

方法1:boolean cancel(boolean mayInterruptIfRunning);  

传参数 mayInterruptIfRunning ,传true即代表可以终止正在运行的线程,反之会等执行中的线程执行完。

通过调用cancel()方法来停止一个任务,如果任务被成功停止,则cancel()方法会返回true;

如果任务已经完成或者已经停止了或者这个任务无法停止,则cancel()会返回一个false;

当一个任务被成功停止后,他无法再次执行。

方法2:boolean isCancelled() 

任务是否停止了

方法3:isDone()

任务是否结束了

方法4: get() 

获取任务结束完成后的结果,这个方法阻塞型的。必须等到任务的run()方法执行完,才会拿到结果。

方法5: get(long timeout, TimeUnit unit) 

和方法4一样,只不过传入一个超时时间,超过时间,就会抛出异常。

解析完了这么多个方法,。直接上代码这么用,说明一点,Future一般都配合线程池使用的。不了解线程池怎么用的,看我之前写的文章吧。

public void createFuture() {
        //创建一个线程池, 这里用 SingleThreadExecutor
        ExecutorService es = Executors.newSingleThreadExecutor();
        //向线程池提交一个任务 返回一个 Future 实例
        Future<?> future = es.submit(runnable);
        //通过 future 获取 结果
        try {
            Log.d(TAG, "获取结果开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) );
            Object o = future.get();
            Log.d(TAG, "获取结果结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  结果是: " + o);
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }



private Runnable runnable = new Runnable() {
        @Override
        public void run() {

            try {
                Log.d(TAG, "任务开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) );
                //睡眠 5S
                TimeUnit.MILLISECONDS.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };

看log:

分析:

1> 创建线程池,把runnable任务放进线程池。放进去就会立马执行任务的run()的功能,打印开始时间。然后睡眠5秒

2> 通过Future 的 get() 获取任务结果,但是任务耗时5秒,所以一直阻塞在get()方法那里。

3> 等5秒后,runnable的任务run()执行完,Future.get()才能获取结果。所以时间相差5秒。

4> 但是,你可以看到结果是 null。因为Runnable 的 run()本来就没有返回值。搞毛线啊。。。没有返回值。。。。

所以。。。。。。。。。。别急。。。。这个时候,该是 Callable 出场啦!!!!!!

方式3:Callable

先看Callable源码:简单明了,就一个call方法。call()里面执行任务功能,执行完返回结果

@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

废话少说,直接上实例代码  。。。。。。。。。。。。。。

public void createCallable() {
        //创建一个线程池, 这里用 SingleThreadExecutor
        ExecutorService es = Executors.newSingleThreadExecutor();
        //向线程池提交一个任务 返回一个 Callable 实例
        Future<Integer> submit = es.submit(callable);
        try {
            Log.d(TAG, "获取结果开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            Integer integer = submit.get();
            Log.d(TAG, "获取结果结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  结果是: " + integer);

            if (submit.isDone()) {
                Log.d(TAG, "任务结束了:");
            }
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


private Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            Log.d(TAG, "任务开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            int num = 0;
            for (int x = 0; x < 10; x++) {
                num++;
                //睡眠1秒
                TimeUnit.MILLISECONDS.sleep(1000);
            }
            return num;
        }
    };

分析过程就不说了,Future.get()的结果,就是Callable返回的结果。

这里,我们在看下cancle() 和 isCancle() 方法。

    public void createCallable() {
        //创建一个线程池, 这里用 SingleThreadExecutor
        ExecutorService es = Executors.newSingleThreadExecutor();
        //向线程池提交一个任务 返回一个 Callable 实例
        Future<Integer> submit = es.submit(callable);
        try {
            //先睡眠  再去停止
            TimeUnit.MILLISECONDS.sleep(2000);

            //停止 任务,传进true 代表终止 执行中的任务
            submit.cancel(true);

            Log.d(TAG, " 任务是否结束了:" + submit.isCancelled());


            if (submit.isDone()) {
                Log.d(TAG, "任务结束了:");
            }
        }  catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


private Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            Log.d(TAG, "任务开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            int num = 0;
            for (int x = 0; x < 10; x++) {
                num++;
                //睡眠1秒
                TimeUnit.MILLISECONDS.sleep(1000);
            }
            Log.d(TAG, "任务结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            return num;
        }
    };

看下log:

很明显,任务耗时是10秒的,2秒后去终止任务,是在执行当中被中了了。因为没有输出 “任务结束时间”这个log.

好,我们在构建一个新场景,任务被终止后,我们Future.get() 去读 任务结果,会怎么样?

上代码:

    public void createCallable() {
        //创建一个线程池, 这里用 SingleThreadExecutor
        ExecutorService es = Executors.newSingleThreadExecutor();
        //向线程池提交一个任务 返回一个 Callable 实例
        Future<Integer> submit = es.submit(callable);
        try {
            //先睡眠  再去停止
            TimeUnit.MILLISECONDS.sleep(2000);

            //停止 任务,传进true 代表终止 执行中的任务
            submit.cancel(true);

            Log.d(TAG, " 任务是否结束了:" + submit.isCancelled());

            Log.d(TAG, "获取结果开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            Integer integer = submit.get();
            Log.d(TAG, "获取结果结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  结果是: " + integer);

            if (submit.isDone()) {
                Log.d(TAG, "任务结束了:");
            }
        }  catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }


private Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            Log.d(TAG, "任务开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            int num = 0;
            for (int x = 0; x < 10; x++) {
                num++;
                //睡眠1秒
                TimeUnit.MILLISECONDS.sleep(1000);
            }
            Log.d(TAG, "任务结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            return num;
        }
    };

看下log,擦,出异常了,一个已经终止了的任务,是不能再通过get()去读取的。所以在读取结果是,必须在前面加是否已经取消判断。

方式4:FutureTask

啥都不说,先看下FutureTask源码再说  实现 RunnableFuture 接口

再看下 RunnableFuture  简单明了了。。。。。

不解析,直接上实例代码吧:

private FutureTask<Integer> futureTask;

    public void createFutureTask() {
        //创建一个线程池, 这里用 SingleThreadExecutor
        ExecutorService es = Executors.newSingleThreadExecutor();
        futureTask = new FutureTask<Integer>(callable) {
            @Override
            protected void done() {
                super.done();
                try {
                    Integer integer = futureTask.get();
                    Log.d(TAG, "获取结果时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "  结果:" + integer);
                } catch (ExecutionException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        es.submit(futureTask);
    }


    private Callable<Integer> callable = new Callable<Integer>() {
        @Override
        public Integer call() throws Exception {
            Log.d(TAG, "任务开始时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            int num = 0;
            for (int x = 0; x < 10; x++) {
                num++;
                //睡眠1秒
                TimeUnit.MILLISECONDS.sleep(1000);
            }
            Log.d(TAG, "任务结束时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            return num;
        }
    };

看下运行结果:

分析:

1> 其实FutureTask就是多了一个接口,等Callable执行完了,会回调 done(),在done里面获取任务的结果

============【锁机制】===============

多线程对  变量进行操作,会导致 线程安全问题。比如 下面:

        final ArrayList<Integer> list = new ArrayList<Integer>();
        for (int x = 0; x < 1000; x++) {
            list.add(x);
        }
        //遍历 数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (Integer integer : list) {
                    Log.d(TAG, "integer:" + integer.toString());
                }
            }
        }).start();
        // 删除元素
        new Thread(new Runnable() {
            @Override
            public void run() {
                list.remove(4);
            }
        }).start();

很明显,会抛出一个crash:

这个Crash 的意思就是 集合在遍历过程中不能用集合对象 增删元素

分析下,就是上面线程在遍历集合,下面的线程在对集合进行删减。。。

有啥什么办法呢?加个锁就可以。这个时候,synchronized  出场:

========【synchronized】==================

来,直接上代码,改一下上面的 代码,,让他不 crash:

        final Object obj = new Object();
        final ArrayList<Integer> list = new ArrayList<Integer>();
        for (int x = 0; x < 10; x++) {
            list.add(x);
        }
        //遍历 数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 以obj 建立一个线程池,后面线程 碰到
                // 这个 obj的线程,会进入 这个线程池中等待,
                // 这个线程 run()执行完 释放 这个锁对象
                synchronized (obj) {//第 24行代码
                    for (Integer integer : list) {
                        //睡眠 1秒
                        Thread.sleep(1000);
                        Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "    遍历 List integer:" + integer.toString());
                    }
                }
            }
        }, "线程1").start();
        //睡眠 2秒 再去开启线程2
        Thread.sleep(2000);
        // 删除元素
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + " 进来run()了 ");
                synchronized (obj) {//第 39行代码
                    Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + " 删除 ");
                    list.remove(4);
                }
            }
        }, "线程2").start();

看下log:

分析一下:

1> 线程1 启动后,会先用 synchronized  锁住 一个 Object  实例 obj,执行synchronized   里面的代码。

这个 synchronized   的作用是:其他线程尝试锁住 obj,会先判断这个obj是否被锁住,如果被锁住,当前线程就进入这个obj监听器建立的线程池去等待。就是线程执行会停止在 synchronized (obj) (看注释:即第 24行 或者 第38 行) 这一行代码这里,如果obj没有被锁住,就会直接执行里面的代码。。。

2> 后面的 线程2 启动后,也会 synchronized  尝试锁住 obj。但很明显,obj已经被线程1锁住了,所以必须等待线程1释放obj的锁。什么时候释放呢?就是 synchronized (obj){}  花括号里面的代码执行 。

3> 所以,看日志就可以看出来。线程1先开启线程,锁住obj,然后执行synchronized (obj) 里面功能。但是线程1还没跑出run(),线程2开启了,进来了run(),但是线程1还在锁住obj,所以线程2并没有执行synchronized (obj) {} 花括号里面的功能,必须等 线程1执行完了,释放 obj的锁了,再交由 线程2所致 obj,进而执行synchronized (obj) {} 花括号里面的功能。

4> 至于 synchronized (),传进去什么,就锁住什么,只要是Object以及其子类,换句话说就是任何类以及实例。因为Java默认类都是继承Object。因为Object底层是隐式的维护了一个监听器,这个监听器 可以理解为一个整形变量。被锁住就是1,没被锁住就是0,。所以Java所有类都有这个监听器。

========【Lock】==================

其实,如果是代码块会出现线程安全问题,还可以用Lock,Java提供了 几种:

ReentrantLock

ReadLock

WriteLock

一般用的都是 ReentrantLock ,现在就只讲 ReentrantLock 。如果用 ReentrantLock  代替 synchronized  ,怎么解决上面代码的线程安全问题呢?直接上代码:

        final ArrayList<Integer> list = new ArrayList<Integer>();
        for (int x = 0; x < 10; x++) {
            list.add(x);
        }
        //建立 一个 Lock
        final ReentrantLock reentrantLock = new ReentrantLock();
        //遍历 数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 当前线程1 锁住 reentrantLock
                reentrantLock.lock();
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + " 进来run()了 ");
                for (Integer integer : list) {
                    //睡眠 1秒
                    Thread.sleep(1000);
                    Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "    遍历 List integer:" + integer.toString());
                }
                reentrantLock.unlock();
            }
        }, "线程1").start();
        //睡眠 2秒 再去开启线程2
        Thread.sleep(2000);
        // 删除元素
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + " 进来run()了 ");
                // 当前线程2 尝试锁住 reentrantLock
                // 如果 reentrantLock 已经被其他线程锁住
                // 会阻塞在这里 知道  reentrantLock.unlock() 解锁为止
                reentrantLock.lock();
                list.remove(4);
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + " 删除 ");
                reentrantLock.unlock();
            }
        }, "线程2").start();

看下log:

原理就不分析了,,,,代码里面的注释和log已经很清晰。。。

不知大家有没有注意到,上面只是说了代码块出现线程安全问题,那如果整个方法都可能出现线程安全问题呢?

那 ReentrantLock 就不适用,因为Lock 同步不了方法,必须用 synchronized  去同步方法。哈哈,专业术语改了,看到没?

用 ReentrantLock  和 synchronized  锁住的代码块叫 :同步代码块

用 synchronized  修饰的方法叫:同步方法

先上可能会出现 线程安全问题的代码,

    ArrayList<Integer> list = new ArrayList<Integer>();
    public void testSynchMethod() {
        for (int x = 0; x < 10; x++) {
            list.add(x);
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                printAll();
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                delete();
            }
        }, "线程2").start();

    }

    public void printAll() {
        Log.d(TAG, "进来方法 printAll()");
        for (Integer integer : list) {
            Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "    遍历 List integer:" + integer.toString());
        }
    }

    public void delete() {
        Log.d(TAG, "进来方法 delete()");
        list.remove(4);
    }

很明显,线程1 对 List 进行 遍历,线程2 对 List 进行 删除元素

很容易会出现:java.util.ConcurrentModificationException

所以,这个时候,我们锁住整个整个方法,不对,应该改口为:同步整个方法,上代码:

   ArrayList<Integer> list = new ArrayList<Integer>();
    public void testSynchMethod() {
        for (int x = 0; x < 10; x++) {
            list.add(x);
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "进来方法 run() 尝试进入  printAll()");
                printAll();
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "进来方法  run() 尝试进入  delete()");
                delete();
            }
        }, "线程2").start();

    }

    public synchronized void printAll(){
        Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "进来方法 printAll()");
        for (Integer integer : list) {
            //睡眠 1秒
            Thread.sleep(1000);
            Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "    遍历 List integer:" + integer.toString());
        }
        Log.d(TAG, "遍历完成时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis())));
    }

    public synchronized void delete() {
        Log.d(TAG, "当前线程: " + Thread.currentThread().getName() + "进来方法 delete()");
        list.remove(4);
        //睡眠 3秒
        Thread.sleep(3000);
        Log.d(TAG, "删除完成时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(System.currentTimeMillis())));
    }

看下log:

分析一下:

分析一下:

1>  开启了两个线程1和2,先执行 线程2的 Run()方法,所以 先开启的线程,run()不一定先执行。

2>  线程 2先进入 先进入 delete(),注意此方法是 synchronized 修饰的,也就是说,delete() 是被锁住的,

那所得对象的是什么呢?当然是当前  非方法所属的类的实例,有点拗口吧?换个角度说:现在这3个非静态方法  testSynchMethod(),printAll()delete()都是在 一个叫:ThreadDemo.class这个类里面,锁住这个类的非静态方法,就是锁住这个类的实例,就是锁住ThreadDemo td = new ThreadDemo() 这个 td,属于这个 td 里面所有非静态方法都会被锁住

所以线程2 锁住 delete() ,其实是把 printAll() 也锁住。因为这两个方法都是非静态方法且用  synchronized 修饰的。

3> 因为线程2锁住 所有非静态属性,所以线程1 就算早早的进入 run()方法,也不会执行进入 printAll() ,只有等待线程2 执行完

delete() 释放了 类的实例,线程1 才能 进入  printAll()。

4> 所以看日志可以看出来:

 (1)线程 2 先进入 run(),尝试进入 delete()   并且锁住所有非静态方法

 (2)线程 1 后进入 run(),尝试进入printAll() ,发现所有非静态方法被锁住,就会转成阻塞状态,等所有非静态方法被释放

 (3)线程 2 执行完delete() ,所有非静态方法被释放

 (4)线程 1发现 所有非静态方法被释放,从阻塞状态转成工作状态,尝试进入printAll()  ,锁住所有非静态方法,知道执行完功能后释放。。。

还有,上面都是说了非静态方法,那么静态方法呢?其实和非静态方法原理一样,一锁就锁住所有静态方法。一释放就释放所有静态方法。具体跑Demo验证,就不贴代码了。。。。。自个儿去验证,,,哈哈哈,,,,举一反三嘛!!!

=============【Priority 线程优先级】==========

说道这里,其实锁机制,还可以为线程 设置 优先级的,接口是:

Thread.setPriority()  :

(最大的优先级是10 ,最小的1 , 默认是5)。

这个线程优先级,只是在CPU切换选择 线程执行时,尽可能 高概率 地选择优先级高的线程,注意关键字,高概率!!!

=============【线程的等待,唤醒,中断】==========

wait()          让当前线程进入 锁对象监听器  建立的线程池中去等待  并释放锁对象

notify()        让 锁对象监听器  建立的线程池中的 某一个等待中的线程

notifyAll()    让 锁对象监听器  建立的线程池中的 所有等待的线程

Thread.interrupt()   如果一个线程处在wait()或者 sleep状态,interrupt()可以把线程拉回到 运行状态

好吧,我们来一个Demo,生产者与消费者:

生产者生产一个产品后进入等待状态,然后等待消费者消费,

消费者消费完产品后进入等待状态,通知生产者生产

这个时候就需要用到 线程等待和唤醒了

上代码:

先创建一个 产品类 Production

public class Production {
    public boolean isProduct = false;
}

再创建一个生产线程

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (production) {
                        if (production.isProduct) {
                            try {
                                production.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            production.isProduct = true;
                            Log.d(TAG, "生产了一台车");
                            production.notify();
                        }
                    }
                }
            }
        },,"生产者线程").start();

再创建一个消费者线程

new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (production) {
                        if (production.isProduct) {
                            Log.d(TAG, "消费一台车 " );
                            production.isProduct = false;
                            production.notify();
                        } else {
                            try {
                                production.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                    }
                }
            }
        },,"消费者线程").start();

然后看下log:

看到结果是:生产一个,消费一个。

分析一下原理:

1>  两个线程 都必须用 synchronized  锁住 production

2> 当生产线程发现 产品还没生产(isProduct = false),就去生产一个产品(isProduct 赋值为 true),然后通知 production

     建立的线程池中  等待的线程(即:消费者线程),如果发现产品已经生产好,就production.wait()    生产者线程入production

     建立的线程池中 等待

3> 当消费者线程发现 产品还没生产(isProduct = false),就会production.notify() 通知 等待中的 生产线程 去生产产品

4> 然后回到2>,循环生产一个,消费一个.........

至于:Thread.interrupt()   我就演示Demo,就是可以把处在wait()或者 sleep状态的线程拉回到 运行状态

=====================【死锁】=========================

死锁出现场景

比如,两个线程,闭环交叉锁住两个对象,上代码:

private Object object1 = new Object();
    private Object object2 = new Object();

    public void dieLock() {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 synchronized (object1){
                     Log.d(TAG, Thread.currentThread().getName() + "锁住 object1");
                     Log.d(TAG, Thread.currentThread().getName() + "尝试锁住 object2");
                     synchronized (object2){
                         Log.d(TAG, Thread.currentThread().getName() + "执行功能");
                     }
                 }
             }
         },"线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object2){
                    Log.d(TAG, Thread.currentThread().getName() + "锁住 object2");
                    Log.d(TAG, Thread.currentThread().getName() + "尝试锁住 object1");
                    synchronized (object1){
                        Log.d(TAG, Thread.currentThread().getName() + "执行功能");
                    }
                }
            }
        },"线程2").start();
    }

看下log:

看log结果,两个线程都没有执行 功能代码块。两个线程都阻塞在尝试锁 功能代码前的那个所对象

分析一下

1:线程1 run()先执行,锁住了 object1,就在此时。CPU执行切换到了线程2去,线程2锁住了object2

2:   这个时候CPU 有切换到执行 线程1来,线程1尝试锁住object2,因为此时object2已经被线程2锁住了,所以线程1等待线程2的对object2释放。

3:这个时候,CPU切换到执行线程2了,线程2尝试锁住object1,因为此时object1已经被线程1锁住了,所以线程2等待线程1的对object1释放。

4:然后,此时线程 1和2,都互相等待 对方的已锁对象的释放,都阻塞这等待那里,造成死锁

死锁出现怎么解决?????

其实换一下所得顺序即可,比如不交叉锁,按顺序来锁,比如:

线程1尝试锁住object1,接着尝试锁住object2(Thread1 >> Lock Object1  >> Lock Object2)

然后,线程也是按这个顺序去  锁即:

线程2尝试锁住object1,接着尝试锁住object2,(Thread2 >> Lock Object1  >> Lock Object2)

上代码:

    private Object object1 = new Object();
    private Object object2 = new Object();

    public void dieLock() {
         new Thread(new Runnable() {
             @Override
             public void run() {
                 Log.d(TAG, Thread.currentThread().getName() + "尝试锁住object1");
                 synchronized (object1){
                     Log.d(TAG, Thread.currentThread().getName() + "已经锁住object1 进而 尝试锁住object2");
                     synchronized (object2){
                         Log.d(TAG, Thread.currentThread().getName() + "已经锁住object2 执行功能");
                     }
                 }
             }
         },"线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, Thread.currentThread().getName() + "尝试锁住object1");
                synchronized (object1){
                    Log.d(TAG, Thread.currentThread().getName() + "已经锁住object1 进而 尝试锁住object2");
                    synchronized (object2){
                        Log.d(TAG, Thread.currentThread().getName() + "已经锁住object2 执行功能");
                    }
                }
            }
        },"线程2").start();
    }

看日志:

解决办法是,所有线程都按照  既定顺序 去 锁确定的锁对象,可以避免死锁

比如:可以把所有所对象按照 哈希值大小排序,按照这个排序由大到小 锁下去。

缺点

但是,你有没有考虑到,这个方法是有个明显缺点,就是先启动run()方法的线程,会先锁住所有对象,后开启run()方法的线程,一般都会在前面线程执行完所有功能释放所有锁,才能执行其功能。

所以,如果非要交叉闭环锁,可以用Lock的 Lock.tryLock(long timeout, TimeUnit unit)   这个功能

timeout 就是 对Lock进行尝试加锁的超时时间

unit        就是单位,可以是秒,分,时.......

它有个返回值,如果在限定时间到了还没成功锁住,就会返回false,反之返回true

直接上代码:

        final ReentrantLock reentrantLock1 = new ReentrantLock();
        final ReentrantLock reentrantLock2 = new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!reentrantLock1.tryLock(1, TimeUnit.SECONDS)) {
                    return;//尝试 锁住reentrantLock1 超时1秒 获取不到锁,退出
                }
                if (!reentrantLock2.tryLock(1, TimeUnit.SECONDS)) {
                    return;//尝试 锁住reentrantLock2 超时1秒 获取不到锁,退出
                }
                reentrantLock2.unlock();
                reentrantLock1.unlock();
            }
        }, "线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                if (!reentrantLock2.tryLock(1, TimeUnit.SECONDS)) {
                    return;//尝试 锁住reentrantLock2 超时1秒 获取不到锁,退出
                }
                if (!reentrantLock1.tryLock(1, TimeUnit.SECONDS)) {
                    return;//尝试 锁住reentrantLock1 超时1秒 获取不到锁,退出
                }
                reentrantLock1.unlock();
                reentrantLock2.unlock();
            }
        }, "线程2").start();

这个上面代码就是 交叉闭环 锁,但是没尝试锁一个Lock,都有设置一个超时时间,超过时间没成功锁住,就退出。

好,我们在每个线程 加个线程睡眠,模拟每个线程都有耗时操作。然后看下线程锁的运行情况

final ReentrantLock reentrantLock1 = new ReentrantLock();
        final ReentrantLock reentrantLock2 = new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1");
                try {
                    boolean isOK = reentrantLock1.tryLock(1, TimeUnit.SECONDS);
                    if (!isOK){
                        Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1 超时 退出");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1 超时 退出");
                    return;
                }
                Log.d(TAG, Thread.currentThread().getName() + "已经锁住reentrantLock1 进而 尝试锁住reentrantLock2");
                try {
                    boolean isOK = reentrantLock2.tryLock(1, TimeUnit.SECONDS);
                    if (!isOK){
                        Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock2 超时 退出");
                        return;
                    }
                } catch (InterruptedException e) {
                    Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock2 超时 退出");
                    return;
                }
                Log.d(TAG, Thread.currentThread().getName() + "已经锁住reentrantLock2 执行功能 耗时5秒");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock2.unlock();
                Log.d(TAG, Thread.currentThread().getName() + "解锁 reentrantLock2 ");
                reentrantLock1.unlock();
                Log.d(TAG, Thread.currentThread().getName() + "解锁 reentrantLock1 ");

            }
        },"线程1").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1");
                try {
                    boolean isOK = reentrantLock1.tryLock(1, TimeUnit.SECONDS);
                    if (!isOK){
                        Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1 超时 退出");
                        return;
                    }
                } catch (InterruptedException e) {
                    Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock1 超时 退出");
                    return;
                }
                Log.d(TAG, Thread.currentThread().getName() + "已经锁住reentrantLock1 进而 尝试锁住reentrantLock2");
                try {
                    boolean isOk = reentrantLock2.tryLock(1, TimeUnit.SECONDS);
                    if (!isOk){
                        Log.d(TAG, Thread.currentThread().getName() + "尝试锁住reentrantLock2 超时 退出");
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return;
                }
                Log.d(TAG, Thread.currentThread().getName() + "已经锁住reentrantLock2 执行功能 耗时5秒");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock2.unlock();
                Log.d(TAG, Thread.currentThread().getName() + "解锁 reentrantLock2 ");
                reentrantLock1.unlock();
                Log.d(TAG, Thread.currentThread().getName() + "解锁 reentrantLock1 ");
            }
        },"线程2").start();

看下log:

看下log,流程正好验证了上面所说。所以,死锁是有办法解决的,我们现在总结死锁的解决办法:

1>   改变锁的顺序,每个线程对锁对象的锁顺序一样的,

2>  用Lock.tryLock(long timeout, TimeUnit unit)  设置超时时间,超过限定时间获取没锁住Lock就退出

其实在正常开发过程中,一般会设定一个阈值,比如10次去尝试锁住Lock,每超时1次累加1,继续去尝试锁住Lock,知道达到阈值后再退出。当然可以不同功能的业务需求做不同的业务处理。这里只是给出个建议。

好了,先写到这里,这篇文章后面还会继续写。会对线程的原子性,可见性,有序性进行 原理分析 以及使用方法。还会对少为人知的 volatile 关键字进行分析

以上代码亲测试运行没问题,如有问题请留言,谢谢!

猜你喜欢

转载自blog.csdn.net/Leo_Liang_jie/article/details/90669459
今日推荐