两个线程交替运行
有个需求,打印1-100;线程A打印奇数,线程B打印偶数,效果如下:
线程A:==>1
线程B:==>2
线程A:==>3
线程B:==>4 .................................
线程A:==>97
线程B:==>98
线程A:==>99
线程B:==>100
1,使用CyclicBarrier
此方案需要两个线程执行次数相等;此方法的灵感来源于cyclicBarrier的Horse赛跑,详见日志
注意:1,此实例中.shutDown();和shutDownNow();都能够正确运行,原因是
1.1,此实例中是CyclicBarrier中先执行的函数控制线程的结束;
1.2,此实例中线程结束有具体的条件,并不是 !Thread.interrupted();
2,本例子中将.newFixedThreadPool(1); 替换为 .newCachedThreadPool();,则可以不调用.shutDown();或shutDownNow();
.newCachedThreadPool(); corePoolSize == 0,不存在核心线程数,执行任务完成后大概60s无新任务,线程池就会被关闭。
【不是立刻关闭,需要等大概60s。】
此操作是由于下方下划线思考后尝试。
3,注意此实例中的 B线程 并不是一个循环。
public class ThreadCommunicate_1 { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(1,new B_Th()); ExecutorService service = Executors.newFixedThreadPool(1); service.execute(new A_Th(barrier, service)); } } /** * 打印奇数 */ class A_Th implements Runnable{ private final AtomicInteger num = new AtomicInteger(1); private CyclicBarrier barrier; private ExecutorService service; public A_Th(CyclicBarrier barrier, ExecutorService service) { this.barrier = barrier; this.service = service; } @Override public void run() { // for(int i = 0; i < 100; i++){ // System.out.println(Thread.currentThread().getName()+"==>"+i); // } while (num.intValue() < 100){ System.out.println("线程A:==>" + num.getAndAdd(2)); try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } //service.shutdownNow(); service.shutdown(); } } /** * 打印偶数 */ class B_Th implements Runnable{ private final AtomicInteger num = new AtomicInteger(2); @Override public void run() { //while (num.intValue() < 100){ System.out.println("线程B:==>" + num.getAndAdd(2)); //} } }
2,使用wait();notify();
方式一:
由 生产者-消费者模式 转换而来;
思路:既然要求两个线程交替执行,那么直接将队列长度设为1。
不使用线程池的目的是,整个程序可以在达到对应的条件后自动结束,不需要手动停止。
思考:或许有运行完就自动关闭的线程池?;newCachedThreadPool满足需求。
/** * @program: * @description: * * 1,线程交替打印 一个线程打印偶数,一个线程打印奇数 * * 使用wait()/notify(); * 类似于生产者与消费者,将队列长度设置为1 */ public class ThreadCommunicate_2 { public static void main(String[] args) { Object lock = new Object(); List<String> list = new ArrayList<>(); // Runnable a_R = new A_Th_2(list, lock); // Runnable b_R = new B_Th_2(list, lock); // // ExecutorService service = Executors.newFixedThreadPool(2); // service.execute(b_R); // service.execute(a_R); Thread a_Th = new A_Th_2(list, lock); Thread b_Th = new B_Th_2(list, lock); b_Th.start(); a_Th.start(); } } /** * 打印 奇数线程 */ class A_Th_2 extends Thread { private static final int MAX_SIZE = 1; private List<String> arrayList; private Object lock; private int num = 1; public A_Th_2(List<String> arrayList,Object lock) { this.arrayList = arrayList; this.lock = lock; } @Override public void run() { while (num < 100){ synchronized (lock){ while (arrayList.size() == MAX_SIZE){ try { //System.out.println("线程A:==> wait...前"); lock.wait(); //System.out.println("线程A:==> wait...后"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程A:==>" + num); arrayList.add("xxxx"); num = num + 2; try { //休眠1s TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } lock.notifyAll(); } } } } /** * 打印 偶数线程 */ class B_Th_2 extends Thread { private List<String> arrayList; private Object lock; private int num = 2; public B_Th_2(List<String> arrayList, Object lock) { this.arrayList = arrayList; this.lock = lock; } @Override public void run() { while (num <= 100 ){ synchronized (lock){ while (arrayList.isEmpty()){ try { //System.out.println("线程B:==> wait...前"); lock.wait(); //System.out.println("线程B:==> wait...后"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("线程B:==>" + num); arrayList.remove(0); num = num + 2; try { //休眠1s TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } lock.notifyAll(); } } } }
优化策略:syn可以存放在循环之外,个人觉得此写法比较优秀。
方式二:
思路:简化代码,不使用list参数。
此代码可能先打印A,也可能先打印B,重点关注交替运行。
public class ThreadCommunicate_2 { public static void main(String[] args) { Object lock = new Object(); List<String> list = new ArrayList<>(); Thread a_Th = new A_Th_2_1("A",lock); Thread b_Th = new A_Th_2_1("B",lock); a_Th.start(); b_Th.start(); } } /** * 打印 0-100 */ class A_Th_2_1 extends Thread { private String name; private Object lock; private int num = 1; public A_Th_2_1(String name, Object lock) { this.name = name; this.lock = lock; } @Override public void run() { synchronized (lock){ while (num <= 100 ){ System.out.println("线程"+name+":==>" + num); num = num + 1; try { //休眠任意s TimeUnit.SECONDS.sleep(new Random().nextInt(3)+1); } catch (InterruptedException e) { e.printStackTrace(); } lock.notifyAll(); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //循环结束后使程序结束 lock.notifyAll(); } } }
运行结果:
线程A:==>1
线程B:==>1
线程A:==>2
线程B:==>2
线程A:==>3
线程B:==>3
线程A:==>4
线程B:==>4
线程A:==>5
线程B:==>5... ... ...直到100
3,使用Lock,await();signal();
此处代码垃圾,纯属脱裤子放屁,只是提供一个思路。
用boolean值控制两个线程的交替执行可以实现,但是没必要使用此种方式实现。
1 /** 2 * @program: 3 * @description: 4 * 5 * lock;singal 6 */ 7 public class ThreadCommunicate_3 { 8 9 public static void main(String[] args) { 10 ReentrantLock lock = new ReentrantLock(); 11 Condition condition = lock.newCondition(); 12 //Boolean flag = Boolean.FALSE; 13 A_Th_3 a_Th = new A_Th_3(lock, condition, false); 14 B_Th_3 b_Th = new B_Th_3(lock, condition, false); 15 16 a_Th.setB_th_3(b_Th); 17 b_Th.setA_th_3(a_Th); 18 19 a_Th.start(); 20 b_Th.start(); 21 22 } 23 } 24 25 /** 26 * 打印奇数线程 27 */ 28 class A_Th_3 extends Thread { 29 30 private final ReentrantLock lock; 31 32 private final Condition condition; 33 34 private Boolean flag; 35 36 private B_Th_3 b_th_3; 37 38 public A_Th_3(ReentrantLock lock,Condition condition,Boolean flag) { 39 this.lock = lock; 40 this.condition = condition; 41 this.flag = flag; 42 } 43 44 public Boolean getFlag() { 45 return flag; 46 } 47 48 public void setFlag(Boolean flag) { 49 this.flag = flag; 50 } 51 52 public B_Th_3 getB_th_3() { 53 return b_th_3; 54 } 55 56 public void setB_th_3(B_Th_3 b_th_3) { 57 this.b_th_3 = b_th_3; 58 } 59 60 @Override 61 public void run() { 62 for (int i = 1; i < 100; ){ 63 lock.lock(); 64 //true就等着 65 while (flag){ 66 try { 67 condition.await(); 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 } 72 System.out.println("线程A:==>" + i); 73 i = i + 2; 74 //让本线程下个循环wait() 75 flag = true; 76 //设置B线程 标识 77 b_th_3.setFlag(true); 78 79 try { 80 TimeUnit.SECONDS.sleep(1); 81 } catch (InterruptedException e) { 82 e.printStackTrace(); 83 } 84 85 condition.signalAll(); 86 lock.unlock(); 87 } 88 } 89 } 90 91 /** 92 * 打印偶数线程 93 */ 94 class B_Th_3 extends Thread{ 95 96 private final ReentrantLock lock; 97 98 private final Condition condition; 99 100 private Boolean flag ; 101 102 private A_Th_3 a_th_3; 103 104 public B_Th_3(ReentrantLock lock,Condition condition, Boolean flag) { 105 this.lock = lock; 106 this.condition = condition; 107 this.flag = flag; 108 } 109 110 public Boolean getFlag() { 111 return flag; 112 } 113 114 public void setFlag(Boolean flag) { 115 this.flag = flag; 116 } 117 118 public A_Th_3 getA_th_3() { 119 return a_th_3; 120 } 121 122 public void setA_th_3(A_Th_3 a_th_3) { 123 this.a_th_3 = a_th_3; 124 } 125 126 @Override 127 public void run() { 128 for (int i = 2; i < 101; ){ 129 lock.lock(); 130 //false就等着 131 //System.out.println(flag); 132 while (!flag){ 133 try { 134 condition.await(); 135 } catch (InterruptedException e) { 136 e.printStackTrace(); 137 } 138 } 139 System.out.println("线程B:==>" + i); 140 i = i + 2; 141 //设置本线程下个循环wait 142 flag = false; 143 //设置 A线程标识 144 a_th_3.setFlag(false); 145 146 try { 147 TimeUnit.SECONDS.sleep(1); 148 } catch (InterruptedException e) { 149 e.printStackTrace(); 150 } 151 152 condition.signalAll(); 153 lock.unlock(); 154 } 155 } 156 }
小小思考:
为什么要使用condition.await();?似乎某些情况不使用也没问题。
但是此示例是一定要使用的,否则程序就会出现假死的情形。一般打到1-3就会停止打印,但其实程序一直运行。
其原因是:
在lock,与unlock之间,存在一个while(){} ,
在某些情况下(其实这个情况很容易发生,例如:当A 线程连续两次竞争到锁时),A线程会一直在循环中,
此时需要codition.await();来释放锁 ,让另外的线程竞争锁。
使用锁相关,一般来说,共享变量就不再需要使用volatile修饰。
当然,Lock相较与sync来说,一个明显的优点的就是可以精确唤醒相应线程,所以其实另外某个方法更为贴切。
public class ThreadCommunicate_3 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); CountDownLatch latch = new CountDownLatch(1); new A_Th_3_1(lock, condition1, condition2, latch).start(); new B_Th_3_1(lock, condition1, condition2, latch).start(); } } /** * 打印奇数线程 */ class A_Th_3_1 extends Thread { private ReentrantLock lock; private Condition condition1; private Condition condition2; private CountDownLatch latch; public A_Th_3_1(ReentrantLock lock, Condition condition1, Condition condition2, CountDownLatch latch) { this.lock = lock; this.condition1 = condition1; this.condition2 = condition2; this.latch = latch; } @Override public void run() { System.out.println("---------------A_out"); for (int i = 1; i < 100; ){ //System.out.println("---------------A_for"); try { lock.lock(); System.out.println("线程A:==>" + i); if ( i == 1){ //放开栅栏 latch.countDown(); } i = i + 2; //先唤醒,再等待 condition2.signalAll(); condition1.await(); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } //此处是为了解决当循环结束时,程序不结束的问题。 lock.lock(); condition2.signalAll(); lock.unlock(); } } /** * 打印偶数线程 */ class B_Th_3_1 extends Thread{ private final ReentrantLock lock; private final Condition condition1; private final Condition condition2; private CountDownLatch latch; public B_Th_3_1(ReentrantLock lock, Condition condition1, Condition condition2, CountDownLatch latch) { this.lock = lock; this.condition1 = condition1; this.condition2 = condition2; this.latch = latch; } @Override public void run() { try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("---------------B_out"); for (int i = 2; i < 101; ){ //System.out.println("---------------B_for"); try { lock.lock(); System.out.println("线程B:==>" + i); i = i + 2; TimeUnit.SECONDS.sleep(1); condition1.signalAll(); condition2.await(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } lock.lock(); condition1.signalAll(); lock.unlock(); } }
该代码 在循环结束之后还需唤起另外的线程,否则程序会一直运行不结束。
使用了CountDownLatch来控制A B线程运行的顺序,否则A B线程虽然是交替运行,但是顺序随机。