线程交替执行的几种方法(递加求余判断/环形链表步进/LockSupport/synchronized/ReentrantLock/CountDownLatch/循环屏障/信号量)

41 篇文章 0 订阅
24 篇文章 0 订阅

思考1:关键在于如何确保交替执行的顺序,只要能确保交替执行顺序,加不加锁其实并不重要(线程任务里需要修改共享资源的另说),这个想明白,一切就都水到渠成~
思考2:累加取余判断打印后看起来不像是交替执行,但仔细看下其实是交替执行,有没有办法优化下呢?原理是啥?详见这篇文章的第4节:13行代码实现两个线程交替打印
思考3:使用CountDownLatch实现时,取余判断后面为啥还要跟 countDownLatch.getCount()>0的判断?
思考4:CountDownLatch和CyclicBarrier的区别在哪?

一、递加取余判断(无锁,存在线程安全问题,在此仅作为一种思路分享)

  1. 首先定义一个整型变量;
  2. 每次执行时加一;
  3. 然后对【取余后的余数】做判断,从而实现线程的交替执行;
  4. 执行代码如下:
public class AlternatePrintOdevity1 {
	private static int step = 0;
	//【1】通过递加求余判断,确保线程交替执行
	public static void task(int max,int remainder){
		while(step<=max) {
			if(step%2==remainder) {		
				System.out.println(Thread.currentThread().getName() + step++);			
			}
		}
	}	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->{
			task(10,0);
		},"T1#");
		Thread t2 = new Thread(()->{
			task(10,1);
		},"T2#");
		t1.start();t2.start();	
	}
}

二、环形链表步进(无锁,存在线程安全问题,在此仅作为一种思路分享)

  1. 首先定义一个有两个节点的环形链表,两个节点互为对方的下一节点;
  2. 每次执行后步进到next下一节点;
  3. 判断【当前节点是否是想要的节点】,从而实现线程的交替执行;
  4. 执行代码如下:
class Node{ 
	Node next;
}
public class AlternatePrintOdevity2 {
	private static int step = 0;
	private static Node node1 = new Node();
	private static Node node2 = new Node();
	private static Node curr = node1;
	static{
		node1.next = node2;
		node2.next = node1;
	}
	public static void task(int max,Node wantedNode){
		while(step<=max){	
			if(wantedNode==curr) {
				System.out.println(Thread.currentThread().getName() + step++);
				curr = curr.next;					
			}	
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(()->{
			task(10,node1);
		},"T1#");
		Thread t2 = new Thread(()->{
			task(10,node2);
		},"T2#");
		t1.start();t2.start();	
	}
}

三、LockSupport锁支持工具类

  1. 通过LockSupport.park()先阻塞一个线程t2,确保另一个线程t1先执行;
  2. t1执行逻辑:先执行任务,再LockSupport.unpark(t2)放行t2,最后LockSupport.park()阻塞自己;
  3. t2执行逻辑:先LockSupport.park()阻塞自己,得到放行后执行任务,最后LockSupport.unpark(t1)放行t1;
  4. 执行代码如下:
import java.util.concurrent.locks.LockSupport;
public class AlternatePrintOdevity3
{  
	static Thread t1,t2=null;
	static int step = 0;
	public static void task() {
		while(step<10) {
			if(t1 == Thread.currentThread()) {
				System.out.println(t1.getName() + step++);//t1先执行打印
				LockSupport.unpark(t2);//打印后放行t2
				LockSupport.park();//阻塞t1,直到被t2放行
			}else {
				LockSupport.park();//阻塞t2,直到被t1放行
				//能走到这里说明t2已经被t1放行,且目前t1处于阻塞等待方向的状态,接着轮到t2执行打印
				System.out.println(t2.getName() + step++);
				LockSupport.unpark(t1);//打印后放行t1
			}
		}
	}
	public static void main(String[] args) {  
		t1 = new Thread(()->{task();},"#T1#");
		t2 = new Thread(()->{task();},"#T2#");
		t1.start();t2.start();
    }
} 

四、synchronized关键字+Object.wait+Object.notify

  1. 思考:synchronized能够实现线程交替执行么?
  2. 要想实现线程交替执行,就必须有方法能够在合适的时间主动阻塞和放行线程
  3. synchronized+Object.wait(阻塞)+Object.notify(放行)可以实现;
  4. 执行代码如下:
public class AlternatePrintOdevity4
{  
	static Object obj = new Object();
	static int step = 0;
	public static void task() {
		while(step<10) {
			synchronized(obj) {
				System.out.println(Thread.currentThread().getName() + step++);
				obj.notify();
				try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}
			}
		}
	}
	public static void main(String[] args) {  
		new Thread(()->{task();},"#T1#").start();
		new Thread(()->{task();},"#T2#").start();
    }
}

五、ReentrantLock+Condition.await+Condition.signal

  1. 基本同synchronized+Object.wait(阻塞)+Object.notify(放行)
  2. 要想实现线程交替执行,就必须有方法能够在合适的时间主动阻塞和放行线程
  3. ReentrantLock+Condition.await(阻塞)+Condition.signal(放行)可以实现;
  4. 执行代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
//报错:IllegalMonitorStateException  if the current thread is not the owner of this object's monitor.
//原因:Condition唤醒和阻塞使用的是signal、signalAll和await,注意要和Object的notify、nofityAll和wait区分开
public class AlternatePrintOdevity5
{  
	static ReentrantLock reentrantLock = new ReentrantLock();
	static Condition condition = reentrantLock.newCondition();
	static int step = 0;
	public static void task() {
		while(step<10) {
			reentrantLock.lock();
			System.out.println(Thread.currentThread().getName() + step++);
			condition.signal();
			if(step<=10-1)
				try {condition.await();} catch (InterruptedException e) {e.printStackTrace();}	
			reentrantLock.unlock();
		}
	}
	public static void main(String[] args) {  
		new Thread(()->{task();},"#T1#").start();
		new Thread(()->{task();},"#T2#").start();
    }
}

六、CountDownLatch倒计时计数器门闩(不推荐)

  1. CountDownLatch本身并不适用于实现线程交替执行这一场景,因为其设计初衷是为了实现:【在执行某个任务之前,必须先执行指定数量的子任务【子任务之间没有顺序,子任务之间不需要相互等待,子任务之间属于并发执行,每个子任务执行完即可自行结束】,待所有子任务都执行完毕,才能进行该任务的执行】【比如:火箭发射任务,在发射火箭之前,需要执行很多子任务:装燃料、检查、点火等】,而非交替执行。
  2. 硬要实现,也有方法:递减取余判断,对CountDownLatch的计数【即总执行次数】做递减;
  3. 其逻辑本质上跟【一、递加取余判断】一样
  4. 思考:取余判断后面为啥还要跟 countDownLatch.getCount()>0这个判断?因为:有两个线程在同时执行while循环这段代码,假设T2已将计数减为0之前,T1已经执行过【1】,而恰好在T2将计数减为0这个时刻,T1执行到了【2】,此时t1就会再获得一次执行机会了。为了避免这多出来的一次执行,所以才加的这个判断。
  5. 执行代码如下:
import java.util.concurrent.CountDownLatch;
public class AlternatePrintOdevity6
{  
	static CountDownLatch countDownLatch = new CountDownLatch(10);
	public static void task(int remainder) {
		while(countDownLatch.getCount()>0) {//【1】
			if(countDownLatch.getCount()%2==remainder && countDownLatch.getCount()>0) {//【2】
				System.out.println(Thread.currentThread().getName() + (10-countDownLatch.getCount()));
				countDownLatch.countDown();
			}
		}
	}
	public static void main(String[] args) {  
		new Thread(()->{task(0);},"#T1#").start();
		new Thread(()->{task(1);},"#T2#").start();
    }
}

七、CyclicBarrier循环屏障(不推荐)

  1. CyclicBarrier本身并不适用于实现线程交替执行这一场景,因为其设计初衷是为了实现:【为多个并发执行的线程设置一个屏障点,每个线程到达屏障点即被阻塞,直到所有线程都到达这个屏障点,各个线程才能继续往下执行】【比如:跑步比赛开始前,需要所有选手都到达代表比赛就绪的起点【屏障点】,比赛才能继续】,而非交替执行。
  2. 硬要实现,也有方法:递加取余判断;
  3. 其逻辑本质上跟【一、递加取余判断】一样,只是加了多余的 CyclicBarrier的相关实现,其实完全可以不加CyclicBarrier,可以说算是只是为了用而用,俗了,完全不讲道理
  4. 执行代码如下:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class AlternatePrintOdevity7
{  
	static CyclicBarrier cyclicBarrier = new CyclicBarrier(1);
	static int step = 0;
	public static void task(int remainder) {
		while(step < 10) {
			if(step%2 == remainder) {
				try {cyclicBarrier.await();} catch (InterruptedException | BrokenBarrierException e) {e.printStackTrace();}		
				System.out.println(Thread.currentThread().getName() + step++ );
				cyclicBarrier.reset();
			}			
		}
	}
	public static void main(String[] args) {  
		new Thread(()->{task(0);},"#T1#").start();
		new Thread(()->{task(1);},"#T2#").start();
    }
}

八、Semaphore信号量(不推荐)

  1. Semaphore本身并不适用于实现线程交替执行这一场景,因为其设计初衷是为了协调多个线程并行执行(只要还有许可可用,线程就能不被阻塞在acquire),而非交替执行。
  2. 硬要实现,也有方法:递加取余判断;
  3. 其逻辑本质上跟【一、递加取余判断】一样,只是加了多余的 Semaphore的相关实现,其实完全可以不加Semaphore,可以说算是只是为了用而用,俗了,完全不讲道理
  4. 执行代码如下:
import java.util.concurrent.Semaphore;
public class AlternatePrintOdevity8
{  
	static Semaphore semaphore = new Semaphore(1);
	static int step = 0;
	public static void task(int remainder) {
		while(step < 10) {
			if(step%2 == remainder) {
				try {semaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}		
				System.out.println(Thread.currentThread().getName() + step++ );
				semaphore.release();
			}			
		}
	}
	public static void main(String[] args) {  
		new Thread(()->{task(0);},"#T1#").start();
		new Thread(()->{task(1);},"#T2#").start();
    }
}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylanioucn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值