本文给出类LockSupport,自旋锁,wait/notify,ReentrantLock实现该功能的方式。还有一些方法比如Semaphore(使用信号量实现限流,通过acquire和release方式每次只允许一个线程执行)、BlockingQueue(需要用两个BlockingQueue,通过两个BlockingQueue之间的信息交互和put、take方法达成功能)、管道流(PipedStream),这些方法也可以完成功能,请读者自行考虑实现。
最简单的的一种方式,使用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的方式完成线程通信,注意这里的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();
}
这种方式和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();
}
使用两个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();
}
可以采用上文中最简单的LockSupport的方式,将线程逐一唤醒执行
//思路:由于t2和t3一开始是park(阻塞)的,所以一定会先输出1,然后逐一唤醒对应线程
static Thread 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();
}