前言
线程通讯的本质,其实就是通知和控制,而在一个线程中,通过十八般武艺去控制其他线程的方法,就是线程通讯实现方式。
其目的是为了线程之间更好的协作,从而完成一些复杂的工作。
线程通讯的几种方式
想要实现线程之间的通讯,方式方法非常的多,下面我们举一个很简单的线程题目,通过几种比较常见的方法去完成这道题目,从而理解线程之间通讯的过程和方法。
演示案例
1 2
| 假设有2个线程,一个线程仅打印数字,一个线程仅打印字母。而需求是要实现数字和字母交替打印的效果, 并且第一个打印的必须是数字,如:1A2B3C.. 应该如何去实现呢?
|
notify + wait方式
使用notify和wait的时候呢,我们必须先使用关键字synchronized加锁对象,否则是无法使用对象的这两个方法的。
所以准确的说,应该是synchronized + notify/wait的实现方式。
主要方法:
1 2 3 4 5
| notify(): 随机唤醒一个等待的线程
notifyAll(): 唤醒所有等待的线程
wait(): 使当前线程进入等待状态
|
演示案例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class NotifyWaitTest {
private static final Object obj = new Object();
private static final String[] LETTER = new String[]{"A","B","C","D"}; private static final Integer[] NUMBER = new Integer[]{1,2,3,4};
public static void main(String[] args) { new Thread(() -> { synchronized (obj){ try { obj.wait(); for (String str : LETTER){ System.out.print(str); obj.notify(); obj.wait(); } } catch (InterruptedException e) { e.printStackTrace(); } obj.notify(); } },"LETTER").start();
new Thread(() -> { synchronized (obj){ for (Integer num : NUMBER){ System.out.print(num); obj.notify(); try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } obj.notify(); } },"NUMBER").start(); } }
|
LockSupport方式
LockSupport是一个工具类,内部所有的方法都是静态的,而其功能,主要就是对线程进行阻塞和唤醒。
同样的,我们通过LockSupport控制线程的阻塞和唤醒,也是可以轻易完成以上案例要求的。
主要方法:
1 2 3 4 5 6 7
| void part(): 阻塞当前线程
void parkUntil(long deadline): 阻塞当前线程,并指定截止时间(单位:13位的时间戳)
void parkNanos(long nanos): 阻塞当前线程,并设置超时时间(单位:纳秒,1秒=1000000000L纳秒)
unpark(Thread thread): 唤醒指定线程
|
演示案例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public class LockSupportTest {
private static Thread thread4letter = null, thread4number = null;
private static final String[] LETTER = new String[]{"A","B","C","D"}; private static final Integer[] NUMBER = new Integer[]{1,2,3,4};
public static void main(String[] args) { thread4letter = new Thread(() -> { LockSupport.park(); for (String str : LETTER){ System.out.print(str); LockSupport.unpark(thread4number); LockSupport.park(); } LockSupport.unpark(thread4number); },"LETTER");
thread4number = new Thread(() -> { for (Integer num : NUMBER){ System.out.print(num); LockSupport.unpark(thread4letter); LockSupport.park(); } LockSupport.unpark(thread4letter); },"NUMBER");
thread4letter.start(); thread4number.start(); } }
|
Lock + Condition方式
Lock + Condition来实现这个案例,相对来说是比较优雅的,因为它可以给锁指定多个条件,我们通过操控条件,就可以轻易的在不同场景下完成对锁的控制,从而完成对线程的控制。
演示案例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class LockConditionTest {
private static final Lock lock = new ReentrantLock();
private static final Condition letterCondition = lock.newCondition();
private static final Condition numberCondition = lock.newCondition();
private static final String[] LETTER = new String[]{"A","B","C","D"}; private static final Integer[] NUMBER = new Integer[]{1,2,3,4};
public static void main(String[] args) { new Thread(() -> { lock.lock(); try { letterCondition.await(); for (String str : LETTER){ System.out.print(str); numberCondition.signal(); letterCondition.await(); } numberCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"LETTER").start();
new Thread(() -> { lock.lock(); try { for (Integer num : NUMBER){ System.out.print(num); letterCondition.signal(); numberCondition.await(); } letterCondition.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } },"NUMBER").start(); } }
|
volatile方式
volatile关键字保证了不同线程,对变量进行操作的可见性,以及读和写的原子性,而且它禁止指令重排,所以它还具备有序性。
因此,我们可以通过这个关键字特性,能够轻易的完成以上案例的要求。
演示案例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class VolatileTest { private static volatile int flag = 1;
private static final String[] LETTER = new String[]{"A","B","C","D"}; private static final Integer[] NUMBER = new Integer[]{1,2,3,4};
public static void main(String[] args) { new Thread(() -> { for (String str : LETTER){ while (flag != 2){} System.out.print(str); flag = 1; } },"LETTER").start();
new Thread(() -> { for (Integer num : NUMBER){ while (flag != 1){} System.out.print(num); flag = 2; } },"NUMBER").start(); } }
|
AtomicInteger方式
我们都知道java并发机制中主要有三个特性需要我们去考虑:原子性、可见性和有序性。
synchronized关键字可以保证可见性和有序性却无法保证原子性,而AtomicInteger的作用就是为了保证原子性。
通过它的原子性,我们可以像volatile一样,轻易的完成案例想要的效果,
演示案例实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public class AtomicIntegerTest {
private static AtomicInteger flag = new AtomicInteger(1);
private static final String[] LETTER = new String[]{"A","B","C","D"}; private static final Integer[] NUMBER = new Integer[]{1,2,3,4};
public static void main(String[] args) { new Thread(() -> { for (String str : LETTER){ while (flag.get() != 2){} System.out.print(str); flag.set(1); } },"LETTER").start();
new Thread(() -> { for (Integer num : NUMBER){ while (flag.get() != 1){} System.out.print(num); flag.set(2); } },"NUMBER").start(); } }
|