java面试题:多线程交替输出偶数和奇数 (2)

当一个线程修改了这个变量的值,新的值对其他线程来说是立即可见的。当其他线程读取自己被volatile修饰的该变量时,会直接从主存中读取数据从而刷新自己工作内存中的数据,保证读取到最新的值。

修改后的实现方法:

方案2:使用volatile型变量和自旋检查来实现交替输出: /* 定义两个线程A和B,让两个线程按顺序交替输出偶数和奇数(A输出偶数,B输出奇数) */ public class ThreadNum { public static volatile int flag = 0; //使用volatile 来保证flag对两个线程的可见性 private static final int N = 200; public static void main(String[] args) { //AtomicInteger flag = new AtomicInteger(0); Thread r1 = new Thread( //线程A用来输出偶数 ()->{ while(flag<=N){ while(flag%2==1&&flag<=N); //循环判断当前flag是否是偶数 //lock.lock(); //先获取锁 System.out.println(Thread.currentThread().getName()+"打印:"+flag); flag++;//自增1 //lock.unlock();//释放锁 } } ); Thread r2 = new Thread(//线程B用来输出奇数 ()->{ while(flag<N){ while(flag%2==0&&flag<N); //lock.lock(); System.out.println(Thread.currentThread().getName()+"打印:"+flag); flag++; // lock.unlock(); } } ); r1.setName("线程A"); r2.setName("线程B"); r1.start(); r2.start(); } } /** 可以成功实现输出 线程A打印:0 线程B打印:1 线程A打印:2 线程B打印:3 线程A打印:4 线程B打印:5 线程A打印:6 线程B打印:7 线程A打印:8 线程B打印:9 线程A打印:10 ... 线程A打印:194 线程B打印:195 线程A打印:196 线程B打印:197 线程A打印:198 线程B打印:199 线程A打印:200 **/

在上面的方案中,线程之间通过自旋检查来保证并发性,也就是当过某个线程发现当前自己无法进行输出时,他会循环检查对应的条件,知道条件满足,线程执行输出操作。

在这种方案中,线程没有被阻塞,时钟在占用CPU执行循环。

另一种实现方案是利用互斥锁来保证线程之间的并发性(有序执行),同一时刻,只有获取到锁的线程才能对变量进行操作(主要是修改)。而无法获得锁的线程会堵塞,知道锁被释放,他们才有机会获取锁。

同时,采用条件变量(Condition)的await()和signal()方法来实现实现两个线程的交替输出

对于获取到锁lock的线程,如果当前无法满足输出要求(比如flag不是奇数),该线程会被挂起(await()),同时将锁释放并等待。

而其他可以进行输出的线程,在操作完之后,会调用signal()方法或者signalAll()方法,来唤醒被挂起的线程,同时自己释放锁,使得被唤醒的线程可以再次尝试获取锁,并错上次被挂起的位置继续执行。

采用volatile 型变量和ReentrantLock锁以及Condition条件变量的实现方案 import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /* 定义两个线程A和B,让两个线程按顺序交替输出偶数和奇数(A输出偶数,B输出奇数) */ public class ThreadPrint2 { public static volatile int flag = 0; //volatile修饰变量保证对线程的可见性 private static final int N = 50; public static void main(String[] args) { ReentrantLock lock = new ReentrantLock();//声明一个锁对象, Condition c = lock.newCondition();//创建这个锁对应的一个条件变量 Thread r1 = new Thread( //线程A用来输出偶数 ()->{ while(flag<=N){ try{ lock.lock();//首先获取锁 if(flag%2==1){//如果当前值为奇数,就将线程阻塞挂起 c.await();//将当前线程挂起 } System.out.println(Thread.currentThread().getName()+"打印:"+flag); flag++;//自增1 c.signal(); //唤醒其他因为这个条件而被被挂起的线程 }catch(InterruptedException e){ e.printStackTrace(); }finally{ //这里必须在finally代码块中来释放锁,防止应其他异常导致线程中断,但是锁 //却没有释放,导致出现死锁 lock.unlock(); } } } ); Thread r2 = new Thread(//线程B用来输出奇数 ()->{ while(flag<N){ try{ lock.lock();//首先获取锁 if(flag%2==0){//如果当前值为偶数,就将线程阻塞挂起 c.await(); } System.out.println(Thread.currentThread().getName()+"打印:"+flag); flag++;//自增1 c.signal(); }catch(InterruptedException e){ e.printStackTrace(); }finally{ lock.unlock(); } } } ); r1.setName("线程A"); r2.setName("线程B"); r1.start(); r2.start(); } }

参考书籍:java并发编程的艺术,深入理解Java虚拟机

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zwyyyp.html