题目:两个线程,第一个线程是从1到26,第二个线程是从A到一直到Z,然后要让这两个线程做到同时运行,交替输出,顺序打印A1B2C3…Z26
方法一:LockSupport结合park()/unpark()
package com.my.controller.question;
import java.util.concurrent.locks.LockSupport;
/**
* 用LockSupport顺序打印A1B2C3....Z26
*/
public class TestLockSupport {
static Thread t1 = null, t2 = null;
public static void main(String[] args) throws Exception {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
t1 = new Thread(() -> {
for(char c : aC) {
System.out.print(c);
LockSupport.unpark(t2); //叫醒T2
LockSupport.park(); //T1阻塞
}
}, "t1");
t2 = new Thread(() -> {
for(char c : aI) {
LockSupport.park(); //t2阻塞
System.out.print(c);
LockSupport.unpark(t1); //叫醒t1
}
}, "t2");
t1.start();
t2.start();
}
}
方法二:synchronized结合wait()/notify()
package com.my.controller.question;
/**
* 使用synchronized配合wait()/notify()顺序打印A1B2C3....Z26
*/
public class Test_sync_wait_notify {
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(()->{
synchronized (o) {
for(char c : aC) {
System.out.print(c);
try {
o.notify();
o.wait(); //让出锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify(); //必须,否则无法停止程序
}
}, "t1").start();
new Thread(()->{
synchronized (o) {
for(char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
如果需要保证某个线程先运行,可以使用volatile修饰的布尔变量,如下所示,保证t2先运行,因为要先打印A
package com.my.controller.question;
/**
* 使用synchronized_wait()_notify()配合布尔变量(volatile+布尔变量是为了保证某个线程先运行)
*/
public class Test_sync_wait_notify2 {
private static volatile boolean t2Started = false;
//private static CountDownLatch latch = new C(1);
public static void main(String[] args) {
final Object o = new Object();
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(()->{
//latch.await();
synchronized (o) {
while(!t2Started) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(char c : aI) {
System.out.print(c);
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t1").start();
new Thread(()->{
synchronized (o) {
for(char c : aC) {
System.out.print(c);
//latch.countDown()
t2Started = true;
try {
o.notify();
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
o.notify();
}
}, "t2").start();
}
}
方法三:使用ReentrantLock,定义等待队列Condition,结合lock()/signal(),一个condition就相当于一个等待队列,定义几个condition就是几个等待队列,此案例有一个condition,所以是t1和t2公用的等待队列
package com.my.controller.question;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock,定义一个等待队列Condition,结合lock()/signal()
*/
public class Test_lock_condition {
public static void main(String[] args) {
char[] charsI = "1234567".toCharArray();
char[] charsC = "ABCDEFG".toCharArray();
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(()->{
try {
lock.lock();
for(char c : charsC) {
System.out.print(c);
condition.signal();
condition.await();
}
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(()->{
try {
lock.lock();
for(char c : charsI) {
System.out.print(c);
condition.signal();
condition.await();
}
condition.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
还可以定义两个condition等待队列,即t1和t2分别有自己的等待队列,避免了只有一个等待队列导致的不必要的通知
package com.my.controller.question;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用ReentrantLock,定义两个等待队列Condition,结合lock()/signal()
*/
public class Test_lock_condition {
public static void main(String[] args) {
char[] charsI = "1234567".toCharArray();
char[] charsC = "ABCDEFG".toCharArray();
ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
new Thread(()->{
try {
lock.lock();
for(char c : charsC) {
System.out.print(c);
// 唤醒condition2等待队列中的线程
condition2.signal();
// 在t1线程中调用condition1.await(),表示线程1到condition1中去等待
condition1.await();
}
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
new Thread(()->{
try {
lock.lock();
for(char c : charsI) {
System.out.print(c);
condition1.signal();
condition2.await();
}
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t2").start();
}
}
方法四:自写一个cas自旋的方式
package com.my.controller.question;
public class Test_cas {
enum ReadyToRun {T1, T2}
//思考为什么必须volatile(保证可见性)
static volatile ReadyToRun r = ReadyToRun.T1;
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(() -> {
for (char c : aC) {
// 如果r = ReadyToRun.T1,while判断条件不成立,继续向下执行
// 如果r = ReadyToRun.T2,while判断条件成立,会一直循环下去,相当于自旋
while (r != ReadyToRun.T1) {}
System.out.print(c);
r = ReadyToRun.T2;
}
}, "t1").start();
new Thread(() -> {
for (char c : aI) {
while (r != ReadyToRun.T2) {}
System.out.print(c);
r = ReadyToRun.T1;
}
}, "t2").start();
}
}
方法五:利用BlockingQueue的阻塞方法,put()/take(),定义2个容量为1的ArrayBlockingQueue或者LinkedBlockingQueue
package com.my.controller.question;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
/**
* 利用BlockingQueue的阻塞方法,put()/take(),定义2个容量为1的ArrayBlockingQueue/LinkedBlockingQueue
*/
public class Test_BlockingQueue {
static BlockingQueue q1 = new ArrayBlockingQueue(1);
static BlockingQueue q2 = new ArrayBlockingQueue(1);
public static void main(String[] args) throws Exception {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
new Thread(() -> {
for(char c : aC) {
System.out.print(c);
try {
q1.put("ok");
q2.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
new Thread(() -> {
for(char c : aI) {
try {
q1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(c);
try {
q2.put("ok");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2").start();
}
}
方法六:利用TransferQueue的特性,结合transfer()/take()
package com.my.controller.question;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;
/**
* 利用TransferQueue的特性,结合transfer()/take()
*/
public class Test_TransferQueue {
public static void main(String[] args) {
char[] aI = "1234567".toCharArray();
char[] aC = "ABCDEFG".toCharArray();
TransferQueue queue = new LinkedTransferQueue();
new Thread(()->{
try {
for (char c : aI) {
//take()不到,所以先阻塞,相当于等着取
System.out.print("#"+queue.take());
queue.transfer(c);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
new Thread(()->{
try {
for (char c : aC) {
//t2中调用transfer,这个c只能t1取,同样t1调用了transfer,只能t2取
//如果t1未提前take,t2中调用的transfer(c)将阻塞,直到t1取走
queue.transfer(c);
System.out.print(queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2").start();
}
}
以上提供的解题方法不唯一,有更好的方法欢迎评论区分享共勉。