回顾
在上一篇 介绍了怎么处理线程安全问题,主要的思路就是使用线程同步的方法,线程A执行时获取 "锁",然后对共享数据进行操作,操作完毕后退出,线程B获取锁对共享数据进行操作。
我们看到此时此时线程B必须等线程A对共享数据操作完毕后才能对共享数据进行操作。此时虽然性能有所下降,但是保证了线程安全和数据准确性。我们在多线程编程要时刻注意线程安全的问题。且注意:- 操作共享数据代码块的判定
- 锁对象的选择(锁需要是同一个)
死锁
我们先前都是讲的都是单个锁的问题,但是当锁有多个时,会不会出现什么其他问题呢? 例如
public class TestMain { static StringBuffer sb1= new StringBuffer(); static StringBuffer sb2= new StringBuffer(); public static void main(String[] args) { new Thread(){ public void run(){ synchronized (sb1){ try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sb1.append("A"); synchronized (sb2){ sb2.append("B"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); new Thread(){ public void run(){ synchronized (sb2){ try { Thread.currentThread().sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } sb1.append("C"); synchronized (sb1){ sb2.append("D"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); }}复制代码
我们创建了两个线程(匿名方式),且有两个锁,执行这段代码,你会发现控制台卡住了,如下图。
此时因为都在等待对方的锁,故称为死锁。我们在编码实践中因尽量避免以上问题,在现实情况下多个锁不可避免时,我们可以设定锁的优先级别 例如A>B 任何线程都需要先获得A 才能获得B,此时避免死锁。更糟糕都情况下,我们可以kill一个线程使得目标锁释放,使得程序继续运行。
线程通信
在 讲解了多个关于Thread类的方法,回顾下
Thread.currentThread() //获取当前线程//以下都是线程实例上的方法setName(); //设置线程名字getName(); //获取线程名字yield();//显示释放cpu的执行权 join();//在一个线程执行中调用另一个目标线程的join方法,意味着立马执行目标线程,且执行完毕才回到原线程isAlive();//判断当前线程是否还存活sleep();//显示的让线程睡眠setPriority() //设置当前线程的优先级getPriority()//获取当前线程的优先级复制代码
此时我们补充三个Object类下关于线程的三个方法
wait();//令当前线程挂起放弃cpu且放弃同步资源notify();//唤醒正在排队等待同步资源的线程,其中优先级最高的结束等待notifyAll();//唤醒正在排队的等待资源的所有线程。复制代码
可见wait方法与notify notifyAll是对应的操作:那么我们可以利用这三个方法使得线程交替执行,即线程相互通信。
注:Object中在三个方法只能在synchronized方法或者同步代码块中执行,否则会抛异常
案例
使两个线程打印1到100 且两个线程交替打印 代码实现:(两个线程打印1到100 没有交替打印版本)
class PrintNum implements Runnable{ int num=1; @Override public void run() { while (true){ synchronized (this) { if(num<=100){ try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+num); num++; }else { break; } } } }}PrintNum printNum=new PrintNum();Thread t1=new Thread(printNum);Thread t2=new Thread(printNum);t1.setName("A");t2.setName("B");t1.start();t2.start();复制代码
利用notify await 实现交替打印代码
class PrintNum implements Runnable{ int num=1; @Override public void run() { while (true){ synchronized (this) { notify(); if(num<=100){ try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":"+num); num++; }else { break; } try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }}复制代码
控制台打印如下
解释下:当第一个线程A进入时notify执行,但此时没有等待状的但线程,打印1后,await(),线程A进入等待状态且释放同步锁,线程B开始执行,进入synchronized代码块内,此时已经获取了锁,notify()后,唤醒了线程A,但是锁没释放,打印2后,await()进入等待状态,然后线程A获得锁资源,又进入同步代码块执行,依次下去边交替打印。仿佛两个线程之间实现了相互通信。
总结
本篇主要讲解了 死锁和线程通信两个概念
- 什么是死锁问题,应该如何避免
- 线程通信中Object类中notify notifyAll await的准确含义和使用,应特别区分sleep与await区别,其中sleep不释放锁资源,而await释放锁资源。
喜欢本文的朋友们,欢迎长按下图关注订阅号"我的编程笔记",收看更多精彩内容~~