前言
多线程并发编程时,难免会遇到线程间的通信问题。线程通信方式的思想大体上来说可以分为两种:共享和传递。
线程通信
共享的实现方式可以是共享变量、共享文件、数据库、网络等。传递的实现方式可以是消息队列、生产者-消费者模型等,并且这两种通信方式也不是绝对独立的,比如生产者-消费者模型也可以看成是共享内存区域。
对于共享方式,最简单的方式莫过于共享变量。有这样一道题:
使用两个线程,交替打印奇数和偶数?
这道题的主要考点就是:线程间如何通信?一个线程打印一次后,如何通知另外一个线程打印,以此循环,最终效果就是交替打印。
共享变量
利用共享变量,可以这样来实现
public class ThreadDemo {
// 共享变量,线程运行标识
private static volatile Boolean odd = true;
private static int number = 1;
public static void main(String[] args) throws Exception {
// 奇数
Thread threadOdd = new Thread(() -> {
while (number <= 10) {
while (odd) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
odd = false;
}
}
});
// 偶数
Thread threadEven = new Thread(() -> {
while (number <= 10) {
while (!odd) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
odd = true;
}
}
});
threadOdd.start();
threadEven.start();
}
}
输出结果
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
Thread-0:9
Thread-1:10
wait/notify
如果使用wait/notify
机制实现,代码如下:
public class ThreadDemo {
private static int number = 1;
private static final Object object = new Object();
public static void main(String[] args) throws Exception {
// 奇数
Thread threadOdd = new Thread(() -> {
synchronized (object) {
while (number <= 10) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
object.notify();
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 终止程序
object.notify();
}
});
// 偶数
Thread threadEven = new Thread(() -> {
synchronized (object) {
while (number <= 10) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
object.notify();
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 终止程序
object.notify();
}
});
threadOdd.start();
threadEven.start();
}
}
运行结果如下:
Thread-0:1
Thread-1:2
Thread-0:3
Thread-1:4
Thread-0:5
Thread-1:6
Thread-0:7
Thread-1:8
Thread-0:9
Thread-1:10
wait/notify改进
前面两种方法虽然运行结果正确,但是不能保证奇数线程一定先运行。
要想保证奇数线程先运行,可以对上述代码稍微改进一下,以wait/notify
为例:
/**
* @author sicimike
*/
public class ThreadDemo {
private static int number = 1;
private static final Object object = new Object();
final static CountDownLatch latch = new CountDownLatch(1);
public static void main(String[] args) {
// 奇数线程
Thread threadOdd = new Thread(() -> {
synchronized (object) {
latch.countDown();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " : " + number++);
try {
object.notify();
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
object.notify();
}
});
// 偶数线程
Thread threadEven = new Thread(() -> {
try {
latch.await();
synchronized (object) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " : " + number++);
object.notify();
object.wait();
}
object.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadOdd.start();
threadEven.start();
}
}
借助CountDownLatch
类可以轻易实现奇数线程先运行。
LockSupport工具
LockSupport
工具类在 suspend/resume、wait/notify、park/unpark 已经详细讲解过,此处不再赘述。
这个例子中,也是可以保证奇数线程先执行的
/**
* @author sicimike
*/
public class ThreadDemo {
private static int number = 1;
private static Thread threadOdd = null;
private static Thread threadEven = null;
public static void main(String[] args) {
// 奇数线程
threadOdd = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " : " + number++);
// 唤醒threadEven线程
LockSupport.unpark(threadEven);
// 阻塞当前线程
LockSupport.park();
}
});
// 偶数线程
threadEven = new Thread(() -> {
for (int i = 0; i < 5; i++) {
// 阻塞threadEven,使threadOdd先运行
LockSupport.park();
System.out.println(Thread.currentThread().getName() + " : " + number++);
// 唤醒threadOdd线程
LockSupport.unpark(threadOdd);
}
});
threadOdd.start();
threadEven.start();
}
}
执行结果
Thread-0 : 1
Thread-1 : 2
Thread-0 : 3
Thread-1 : 4
Thread-0 : 5
Thread-1 : 6
Thread-0 : 7
Thread-1 : 8
Thread-0 : 9
Thread-1 : 10
结语
有时候程序结果是正确的,不代表程序就是完全正确的,特别是涉及到多线程的代码。如上述代码有错误之处,还请不吝赐教。