线程通信:
-
面试题:两个线程,一个线程打印1-52,另一个打印字母A-Z打印顺序为12A34B…5152Z
- 上面的问题方式有非常多种 但是万变不离其宗 就是多线程顺序输出 我们都知道 多线程的启动不是按照start方法在代码中的先后顺序进行执行的 线程启动的顺序是不确定的 多线程编程就是要将不确定的 变为确定 (这也是多线程的魅力)这必然需要一套线程之间的通信机制 – 也就是通知等待唤醒机制
- 上面的题目的实现 使用synchronized实现:
class ThreadTest{ // 高内聚 低耦合 操作资源类的方法 就在类中 不要再类外面进行操作 // 线程操作资源类 // 多线程操作就是三点 判断 干活 通知 // 进行标志位的设定 private boolean flag = false; private int num = 1; private char aChar ='a'; public synchronized void work1() throws InterruptedException { // 判断 这里为什么使用while 而不是使用if 先保留一下 (后面会回来的) while (flag != false){ this.wait(); } // 干活 for (int i = 0; i < 2; i++) { System.out.println(num ++); } // 通知 // 进行标志位的修改 flag = true; this.notifyAll(); } public synchronized void work2() throws InterruptedException { while (flag != true){ this.wait(); } System.out.println(aChar); aChar += 1; // 进行通知 flag = false; this.notifyAll(); } }
public class TestThreadTest { public static void main(String[] args) { ThreadTest threadTest = new ThreadTest(); new Thread(() -> { for (int i = 0; i < 26; i++) { try { threadTest.work1(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { for (int i = 0; i < 26; i++) { try { threadTest.work2(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
-
多线程编程模板:
- 多线程操作就是三点 判断 干活 通知
-
线程虚假唤醒问题:
- 线程虚假唤醒问题怎么出现:直接上代码:四个线程 两个线程 将num + 1 两个线程将 num - 1 理想的状况就是最终num 为0 中间也没有可能会出现2甚至是以上的数字
public class PacFalseNotify { public static void main(String[] args) { //1、虚假唤醒 MyResourcesFalseNotify falseNotify = new MyResourcesFalseNotify(); new Thread(()-> {for (int i = 0; i < 10; i++) { falseNotify.increment(); }},"A").start(); new Thread(()-> {for (int i = 0; i < 10; i++) { falseNotify.increment(); }},"B").start(); new Thread(()-> {for (int i = 0; i < 10; i++) { falseNotify.decrement(); }},"C").start(); new Thread(()-> {for (int i = 0; i < 10; i++) { falseNotify.decrement(); }},"D").start(); } } class MyResourcesFalseNotify { private int num = 0; //加1操作 public synchronized void increment() { //1、判断 try { if(num != 0) this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //2、干活 num ++; System.out.print("线程" + Thread.currentThread().getName() + ":" + num + ";\t"); //3、通知 this.notifyAll(); } //减1操作 public synchronized void decrement(){ //1、判断 try { if(num == 0) this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //2、干活 num --; System.out.println("线程" + Thread.currentThread().getName() + ":" + num + ";\t"); //3、通知 this.notifyAll(); } }
- 运行上述的 结果: 不仅仅是和预想的不一样 还出现了 死锁问题 这个问题就是线程的虚假唤醒的问题
- 运行上述的 结果: 不仅仅是和预想的不一样 还出现了 死锁问题 这个问题就是线程的虚假唤醒的问题
- 线程虚假唤醒问题怎么出现:直接上代码:四个线程 两个线程 将num + 1 两个线程将 num - 1 理想的状况就是最终num 为0 中间也没有可能会出现2甚至是以上的数字
-
上述问题的解决方式:JDK中已经给出了 这也就是面试题中使用while进行判断的原因
- 上述问题使用while进行判断 问题解决
- 上述问题使用while进行判断 问题解决
-
JUC中新的方法实现线程通信:
-
ConditionLock提供了一个接口Condition,通过Lock类对象获取Condition实现类对象。通过Condition,可以指定唤醒哪个进程 这就是它的强大之处
-
针对Condition官方给出的api如下:
-
public interface ConditionCondition因素出Object监视器方法( wait , notify和notifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。
-
条件(也称为条件队列或条件变量 )为一个线程暂停执行(“等待”)提供了一种方法,直到另一个线程通知某些状态现在可能为真。 因为访问此共享状态信息发生在不同的线程中,所以它必须被保护,因此某种形式的锁与该条件相关联。 等待条件的关键属性是它原子地释放相关的锁并挂起当前线程,就像Object.wait 。
-
一个Condition实例本质上绑定到一个锁。 要获得特定Condition实例的Condition实例,请使用其newCondition()方法。例如,假设我们有一个有限的缓冲区,它支持put和take方法。 如果在一个空的缓冲区尝试一个take ,则线程将阻塞直到一个项目可用; 如果put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。 我们希望在单独的等待集中等待put线程和take线程,以便我们可以在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。 这可以使用两个Condition实例来实现
-
-
Condition对象的获取: 通过Lock进行获取
Lock lock = new ReentrantLock(); Condition condition = lock.newCondition();
-
上述案例改造:
class TestThreadDemo3{ Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); Condition condition2 = lock.newCondition(); private int number = 0; public void increase() throws InterruptedException { lock.lock(); if (number != 0){ condition.await(); } System.out.println(Thread.currentThread().getName() +"\t" +number); number ++; // 实现精准通知 condition2.signalAll(); lock.unlock(); } public void decrease() throws InterruptedException { lock.lock(); if (number != 1){ condition2.await(); } System.out.println(Thread.currentThread().getName() +"\t" +number); number --; condition.signalAll(); lock.unlock(); } }
-