Java并发——两个线程交替打印两个数组中的元素|Java并发——两个线程交替打印两个数组中的元素 | 多个线程按顺序输出数字

有两个字符数组

  • number = {‘1’,‘2’,‘3’,‘4’,‘5’,‘6’,‘7’,‘8’,‘9’}
  • letter = {‘A’,‘B’,‘C’,‘D’,‘E’,‘F’,‘G’,‘H’,‘I’}
要求启动2个线程,交替打印其中元素,输出结果为
  • 1 A 2 B 3 C 4 D 5 E 6 F 7 G 8 H 9 I
本文给出类LockSupport,自旋锁,wait/notify,ReentrantLock实现该功能的方式。还有一些方法比如Semaphore(使用信号量实现限流,通过acquire和release方式每次只允许一个线程执行)、BlockingQueue(需要用两个BlockingQueue,通过两个BlockingQueue之间的信息交互和put、take方法达成功能)、管道流(PipedStream),这些方法也可以完成功能,请读者自行考虑实现。
LockSupport的方法 最简单的的一种方式,使用park和unpark的方式完成两个线程之间的通信
public static void main(String[] args) { char[] number = {'1','2','3','4','5','6','7','8','9'}; char[] letter = {'A','B','C','D','E','F','G','H','I'}; t1 = new Thread(() ->{ for(char num : number){ System.out.print(num + " "); LockSupport.unpark(t2); //唤醒t2线程 LockSupport.park(); //将本线程阻塞 } }); t2 = new Thread(() ->{ for(char let : letter){ LockSupport.park(); //防止t2线程先执行从而先输出A System.out.print(let + " "); LockSupport.unpark(t1); //唤醒t1线程 } }); t1.start(); t2.start(); }

模拟自旋锁的方式 使用一个原子类型的值当作自旋的标示,如果线程要执行时发现标示不对,则一直while自旋等待
static final AtomicInteger threadNo = new AtomicInteger(1); public static void main(String[] args) { char[] number = {'1','2','3','4','5','6','7','8','9'}; char[] letter = {'A','B','C','D','E','F','G','H','I'}; new Thread(() ->{ for(char num : number){ while(threadNo.get() != 1){} System.out.print(num + " "); threadNo.set(2); } }).start(); new Thread(() -> { for(char let : letter){ while(threadNo.get() != 2){} System.out.print(let + " "); threadNo.set(1); } }).start(); }

wait/notify的方式 最经典的方式,使用wait/notify的方式完成线程通信,注意这里的synchronized不能使用this,因为这是在静态方法中,这里除了可以使用自定义的监视器,还可以使用类的Class对象。
下列写法无法正确保证是先输出1还是A,读者可自行添加CountDownLatch完成按指定顺序输出的功能。
public static void main(String[] args) { final Object obj = new Object(); char[] number = {'1','2','3','4','5','6','7','8','9'}; char[] letter = {'A','B','C','D','E','F','G','H','I'}; new Thread(() -> { synchronized (obj){ for(char num : number){ System.out.print(num + " "); try { obj.notify(); //叫醒其他线程,这里就是t2 obj.wait(); //让自己阻塞,让出锁 } catch (InterruptedException e) { e.printStackTrace(); } } obj.notify(); //必须要有,因为两个线程的try里面的最后一步是阻塞,如果线程执行完了还在阻塞肯定不对,必须要唤醒,才能正确结束程序 } }).start(); new Thread(() -> { synchronized (obj){ for(char let : letter){ System.out.print(let + " "); try { obj.notify(); //叫醒其他线程,这里是t1 obj.wait(); //让自己阻塞,让出锁 } catch (InterruptedException e) { e.printStackTrace(); } } obj.notify(); //同上 } }).start(); }

ReentrantLock——单condition方式 这种方式和wait/notify方式一样,并不能体现出使用ReentrantLock可针对不同线程定制condition的优势
public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); char[] number = {'1','2','3','4','5','6','7','8','9'}; char[] letter = {'A','B','C','D','E','F','G','H','I'}; new Thread(() -> { lock.lock(); try{ for(char num : number){ System.out.print(num + " "); condition.signal(); condition.await(); } condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); } }).start(); new Thread(() -> { lock.lock(); try{ for(char let : letter){ System.out.print(let + " "); condition.signal(); condition.await(); } condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); } }).start(); }

ReentrantLock——多condition方式 使用两个condition,从而实现对不同线程的监视,体现出ReentrantLock对比wait/notify的优势特性
public static void main(String[] args) { Lock lock = new ReentrantLock(); Condition conditionNum = lock.newCondition(); Condition conditionLet = lock.newCondition(); char[] number = {'1','2','3','4','5','6','7','8','9'}; char[] letter = {'A','B','C','D','E','F','G','H','I'}; new Thread(() -> { lock.lock(); try{ for(char num : number){ System.out.print(num + " "); conditionLet.signal(); conditionNum.await(); } conditionLet.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); } }).start(); new Thread(() -> { lock.lock(); try{ for(char let : letter){ System.out.print(let + " "); conditionNum.signal(); conditionLet.await(); } conditionNum.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); } }).start(); }

启动三个线程,每个线程输出一个数字,要求按顺序输出1,2,3
【Java并发——两个线程交替打印两个数组中的元素|Java并发——两个线程交替打印两个数组中的元素 | 多个线程按顺序输出数字】可以采用上文中最简单的LockSupport的方式,将线程逐一唤醒执行
//思路:由于t2和t3一开始是park(阻塞)的,所以一定会先输出1,然后逐一唤醒对应线程 staticThread t11 = null, t22 = null, t33 = null; public static void main(String[] args) { t11 = new Thread(() -> { System.out.println(1); LockSupport.unpark(t22); }); t22 = new Thread(() -> { LockSupport.park(); System.out.println(2); LockSupport.unpark(t33); }); t33 = new Thread(() -> { LockSupport.park(); System.out.println(3); }); t11.start(); t22.start(); t33.start(); }

    推荐阅读