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的回收会让所有线程都停止,包括主线程也会停止的,必然会导致页面的卡顿。应该维护一个线程池,用线程池来执行这个异步功能。至于怎么使用线程池,就看我之前写的文章了。
然而,这种开启线程还有弊端,比如:它什么结束,执行结果是什么,我们不知道。
所以,这个时候需要Callable 和 Future 的出现
方式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> 所以看日志可以看出来:
delete() 并且锁住所有非静态方法
(2)线程 1 后进入 run(),尝试进入printAll() ,发现所有非静态方法被锁住,就会转成阻塞状态,等所有非静态方法被释放
delete() ,所有非静态方法被释放
(4)线程 1发现 所有非静态方法被释放,从阻塞状态转成工作状态,尝试进入printAll()
还有,上面都是说了非静态方法,那么静态方法呢?其实和非静态方法原理一样,一锁就锁住所有静态方法。一释放就释放所有静态方法。具体跑Demo验证,就不贴代码了。。。。。自个儿去验证,,,哈哈哈,,,,举一反三嘛!!!
=============【Priority 线程优先级】==========
说道这里,其实锁机制,还可以为线程 设置 优先级的,接口是:
Thread.setPriority() :
(最大的优先级是10 ,最小的1 , 默认是5)。
这个线程优先级,只是在CPU切换选择 线程执行时,尽可能 高概率 地选择优先级高的线程,注意关键字,高概率!!!
=============【线程的等待,唤醒,中断】==========
wait() 让当前线程进入 锁对象监听器 建立的线程池中去等待 并释放锁对象
notify()
notifyAll()
Thread.interrupt() 如果一个线程处在wait()或者 sleep状态,interrupt()可以把线程拉回到 运行状态
wait() notify()、 notifyA11() 这些方法全部都是属于Object类
原因:因为这些方法都要由锁对象调用,而锁对象可以是任意的对象。
好吧,我们来一个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 关键字进行分析
以上代码亲测试运行没问题,如有问题请留言,谢谢!