对于一些需要多线程保持顺序的场景,需要我们实现线程间的交互。
常见多线程通信方式:
- Synchronized + wait() + notify()
- Lock + await() + signal()
- semaphore
- 基于semaphore的变体
结合经典例题进行分析:
多线程打印10遍ABC,形如ABCABCABCABC
-
Synchronized + wait() + notify()
wait()与notify()作为Object类中的方法,作用如下wait() 持有锁的线程,释放锁,一直阻塞,直到有别的线程调用notify()将其唤醒
notify() 通知一个等待线程,唤醒任意一个wait线程
private static int count = 30;
static class ThreadA extends Thread {
Thread c;
public void setC(Thread c) {
this.c = c;
}
@Override
public void run() {
while (count-- > 0) {
synchronized (c) {
synchronized (this) {
System.out.print("A");
this.notify();
}
try {
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class ThreadB extends Thread {
Thread a;
public void setA(Thread a) {
this.a = a;
}
@Override
public void run() {
while (count-- > 0) {
synchronized (a) {
synchronized (this) {
System.out.print("B");
this.notify();
}
try {
a.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class ThreadC extends Thread {
Thread b;
public void setB(Thread b) {
this.b = b;
}
@Override
public void run() {
while (count-- > 0) {
synchronized (b) {
synchronized (this) {
System.out.print("C");
this.notify();
}
try {
b.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
ThreadC c = new ThreadC();
a.setC(c);
b.setA(a);
c.setB(b);
a.start();
Thread.sleep(100);
b.start();
Thread.sleep(100);
c.start();
}
-
Lock + await() + signal()
await()与signal()方法有点类似wait(),notify()。不过await()与signal()是Condition工具类下的方法
Condition在内部维护了一个队列,简言之,
await() 将线程包装成节点并放入队列中,阻塞当前线程
signal() 从同步队列中重新获取线程信息。
static ReentrantLock lock = new ReentrantLock();
static Condition conditionA = lock.newCondition();
static Condition conditionB = lock.newCondition();
static Condition conditionC = lock.newCondition();
static String state = "A";
static void nextState() {
switch (state) {
case "A":
state = "B";
break;
case "B":
state = "C";
break;
default:
state = "A";
}
}
static class PrintState extends Thread {
String name;
public PrintState(String name) {
this.name = name;
}
@Override
public void run() {
printState(getCurrentCondition(), getNextCondition());
}
void printState(Condition currentCondition, Condition nextCondition) {
for (int i = 0; i < 10; i++) {
lock.lock();
if (!name.equals(state)) {
try {
currentCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.print(state);
nextState();
nextCondition.signal();
lock.unlock();
}
}
Condition getCurrentCondition() {
switch (name) {
case "A":
return conditionA;
case "B":
return conditionB;
default:
return conditionC;
}
}
Condition getNextCondition() {
switch (name) {
case "A":
return conditionB;
case "B":
return conditionC;
default:
return conditionA;
}
}
}
public static void main(String[] args) {
PrintState printStateA = new PrintState("A");
PrintState printStateB = new PrintState("B");
PrintState printStateC = new PrintState("C");
printStateA.start();
printStateC.start();
printStateB.start();
}
- semaphore
信号量提供操作计数的方式来同步控制线程
acquire() 信号量减1,若信号量为0,则阻塞
release() 信号量加1
static Semaphore a = new Semaphore(1);
static Semaphore b = new Semaphore(0);
static Semaphore c = new Semaphore(0);
private static class PrintABC extends Thread {
String state;
Semaphore currentSemaphore;
Semaphore nextSemaphore;
public PrintABC(String state) {
this.state = state;
switch (state) {
case "A":
currentSemaphore = a;
nextSemaphore = b;
break;
case "B":
currentSemaphore = b;
nextSemaphore = c;
break;
default:
currentSemaphore = c;
nextSemaphore = a;
break;
}
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
currentSemaphore.acquire();
System.out.print(state);
} catch (InterruptedException e) {
e.printStackTrace();
}
nextSemaphore.release();
}
}
}
public static void main(String[] args) {
PrintABC printA = new PrintABC("A");
PrintABC printB = new PrintABC("B");
PrintABC printC = new PrintABC("C");
printA.start();
printB.start();
printC.start();
}
4.基于semaphore的变体
通过上述实现方法,我们可以得知,多线程间通信的思路是由符合条件的线程获取CPU资源并执行,若不符合条件的线程获取到的CPU信息,则进行阻塞。
对于该例题,我们可以通过变量映射线程执行顺序。从而控制线程流转.
private static AtomicInteger autoInt = new AtomicInteger();
private static class PrintNum extends Thread {
private int num;
public PrintNum(int num) {
this.num = num;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (autoInt.intValue() % 3 == num) {
printStr();
autoInt.intValue();
}
Thread.yield();
}
}
private void printStr() {
switch (num) {
case 0:
System.out.print("A");
case 1:
System.out.print("B");
default:
System.out.print("C");
}
}
}
public static void main(String[] args) {
PrintNum printA = new PrintNum(0);
PrintNum printB = new PrintNum(1);
PrintNum printC = new PrintNum(2);
printA.start();
printB.start();
printC.start();
}