实现两个线程交替运行(二)
接上文:实现两个线程交替运行(一)
4,Semaphore信号量
详情见随笔;
https://www.cnblogs.com/CccccNun/p/15798454.html
5,volatile+非线程安全引用变量
笑不活了家人们,今天我发现其实不使用很复杂的锁相关内容,其实也能实现两个线程的交替运行,之前仿佛陷入了一个大大的思维定式。
使用List。此处一定要使用 volatile,否则A线程可能感应不到B线程的remove(0);,从而A线程一直处于while{};而此时B线程也处于while{};
程序一直运行,进入假死状态。
public class ThreadCommunicate_4 { public static void main(String[] args) { List<String> arrayList = new ArrayList<>(); Thread A_Th = new A_Th_4(arrayList); Thread B_Th = new B_Th_4(arrayList); A_Th.start(); B_Th.start(); } } /** * 奇数线程 */ class A_Th_4 extends Thread{ //此处一定要volatile private volatile List<String> arrayList; public A_Th_4(List<String> arrayList) { this.arrayList = arrayList; } @Override public void run() { for(int i = 1; i < 100;){ try { while (arrayList.size() == 1){ } System.out.println("线程A:==>" + i); i = i + 2; arrayList.add(i + ""); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 偶数线程 */ class B_Th_4 extends Thread{ //此处一定要volatile private volatile List<String> arrayList; public B_Th_4(List<String> arrayList) { this.arrayList = arrayList; } @Override public void run() { for(int i = 2; i <= 100;){ try { while (arrayList.isEmpty()){ } System.out.println("线程B:==>" + i); i = i + 2; arrayList.remove(0); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }
假死状态如图:

但是某个瞬间我又疑惑了,既然可以不使用锁相关(包含synchorined以及Lock),那么完全可以把使用的锁都去掉呀。
本着事事躬行的态度,我将之前的锁去掉之后运行,发现了程序会进入上图显示假死状态。
突然间我悟了,使用锁就是为了保证线程间共享变量之间的可见性(当然其实不仅仅保证了可见性),所以一般使用锁的时候不需要使用volatile。
6,使用阻塞队列BlockingQueue
思路类似于Semaphore,实现如下:
/** * @program: * @description: * * 阻塞队列 BlockingQueue */ public class ThreadCommunicate_5 { public static void main(String[] args) { BlockingQueue<String> queue = new LinkedBlockingQueue<String>(1); BlockingQueue<String> queue1 = new LinkedBlockingQueue<String>(1); new A_Th_5(queue, queue1).start(); new B_Th_5(queue, queue1).start(); } } /** * 奇数线程 */ class A_Th_5 extends Thread { private final BlockingQueue<String> queue; private final BlockingQueue<String> queue2; public A_Th_5(BlockingQueue<String> queue, BlockingQueue<String> queue2) { this.queue = queue; this.queue2 = queue2; } @Override public void run() { for(int i = 1; i < 100; ){ try { queue.put(i+""); System.out.println("线程A:==>" + i); i = i + 2; TimeUnit.SECONDS.sleep(1); queue2.put(i+""); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * 偶数线程 */ class B_Th_5 extends Thread { private final BlockingQueue<String> queue; private final BlockingQueue<String> queue2; public B_Th_5(BlockingQueue<String> queue, BlockingQueue<String> queue2) { this.queue = queue; this.queue2 = queue2; } @Override public void run() { for(int i = 2; i <= 100; ){ try { queue2.take(); System.out.println("线程B:==>" + i); i = i + 2; TimeUnit.SECONDS.sleep(1); queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
此代码的思路和Semaphore有点类似,利用长度为1的阻塞队列 put(),take()的单向执行顺序;否则会阻塞。
具体运行流程:
* A B线程肯定是 A线程先执行,B线程由于 queue2 是空的,take()阻塞。
*
* A 线程新增 queue1后,执行打印等逻辑后休眠;此时 B线程仍然阻塞;
* A 线程休眠完成后,queue2 队列新增;A 进入下一个循环 新增 queue1 阻塞(queue1 上个循环已经新增了);
* B 线程只有在 A线程运行到最后一步新增完queue2 之后,queue2.take();才能成功,后执行打印等具体逻辑,然后进入休眠;
* 此时 A线程仍旧阻塞,因为 queue1 此时长度仍为 1;
* B 线程休眠完成后,执行queue1.take();B 进入下一个循环,queue2.take();阻塞(queue2 上个循环已经take了);
* 此时A 线程 queue1.put();成功(由于B 线程结束时执行queue1.take();),执行打印等逻辑后休眠。。。
* 以此类推。
7,使用LockSupport.park();unpark();
首先了解LockSupport的基本使用:LockSupport相当于一个工具类,其构造方法为私有,所有方法都为static
主要方法为LockSupport.park();,LockSupport.unpark(Thread thread);
其实有点类似于信号量Semaphore,但是也有非常明显的区别。
1,LockSupprot.park();会阻塞线程【类似于new Semaphore().acquire();的效果】,而unpark();并不会阻塞线程【类似于new Semaphore().release();的效果】;
2,连续多次unpark();的效果与一次unpark();的效果一致,只会释放一个资源,
【这是与Semaphore明显的区别,信号量release();N次会释放N个资源,随后也可以acquire(); (N+permits) 个资源】。
3,LockSupport一定需要在unpark(Thread xx);之后才能成功park(); ,直接调用park();会阻塞当前线程。
4,连续多次调用park();的情况,最多只有 第一个park();成功,其余都会阻塞。
5,只有在 T线程 线程启动之后,unpark(Thread t);才会生效,否则效果未知。
代码实现如下:
思路类似于Semaphore;
个人觉得没必要使用此方法,此处仅提供一种思路。
/** * @program: * @description: * * LockSupport park ; unpark * */ public class ThreadCommunicate_6 { public static void main(String[] args) { A_Th_6 a_Th = new A_Th_6(); B_Th_6 b_Th = new B_Th_6(); a_Th.setB_Th(b_Th); b_Th.setA_Th(a_Th); // 注意,此处开启并没有效果,一定要在 A线程 开启之后unpark // 只需要 A线程开启之后,不一定在所有线程开启之后。 //LockSupport.unpark(a_Th); a_Th.start(); b_Th.start(); // 开启 LockSupport.unpark(a_Th); } } class A_Th_6 extends Thread { private Thread b_Th; public void setB_Th(Thread b_Th) { this.b_Th = b_Th; } @Override public void run() { for (int i = 1; i < 100; ){ try { LockSupport.park(); System.out.println("线程A:==>" + i); i = i + 2; TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }finally { LockSupport.unpark(b_Th); } } } } class B_Th_6 extends Thread { private Thread a_Th; public void setA_Th(Thread a_Th) { this.a_Th = a_Th; } @Override public void run() { for (int i = 2; i <= 100; ){ try { LockSupport.park(); System.out.println("线程B:==>" + i); i = i + 2; TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { LockSupport.unpark(a_Th); } } } }
注意:
此处main();的 A线程 unpark();的时机很重要,一定要在 A线程 开启之后,否则会无效。

浙公网安备 33010602011771号