此題考查的是線程間的通信方式。
- 可以利用park/unpark實現
- 可以利用volatile關鍵字實現
- 可以利用synchronized結合wait notify實現
- 可以利用JUC中的CountDownLatch實現
- 可以利用Condition中的await signal 實現
代碼示例
利用Park/Unpak實現線程通信
private void notifyThreadWithParkUnpark(){
Thread thb = new Thread("線程B"){
@Override
public void run() {
LockSupport.park();
System.out.println(Thread.currentThread().getName()+"啟動了");
}
};
Thread tha =new Thread("線程A"){
@Override
public void run() {
for(int i=1;i<11;i++){
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
LockSupport.unpark(thb);
}
}
}
};
thb.start();
tha.start();
}
park與unpark可以看做一個令牌,park就是等待令牌,unpark就是頒發一個令牌,另外需要注意的是park與unpark的調用次數不用一一對應,而且假如在同步代碼塊中調用park方法,線程會進入阻塞狀態,但是不會釋放已經占用的鎖。
本例使用park使線程B進入阻塞等待狀態,在線程A調用unpark並傳入線程B的名稱使線程B可以繼續運行。
使用Volatile關鍵字實現線程通信
private static volatile boolean flag = false;
private void notifyThreadWithVolatile(){
Thread thc= new Thread("線程C"){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i==5){
flag=true;
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+i);
}
}
};
Thread thd= new Thread("線程D"){
@Override
public void run() {
while (true){
// 防止偽喚醒 所以使用了while
while(flag){
System.out.println(Thread.currentThread().getName()+"收到通知");
break;
}
}
}
};
thd.start();
try {
Thread.sleep(1000L);
} catch (Exception e) {
e.printStackTrace();
}
thc.start();
}
volatile表示的禁用CPU緩存,用volatile修飾的變量,會強制從主內存中讀取變量的值。java內存模型中關於volatile也是有說明的,volatile只能保證可見性,但不能保證原子性。
本例通過在volatile來修飾一個標志位,線程C修改了該標志位,然后線程D就可以“看到”標志位的修改,從而實現互相通信。
使用Synchronized 集合wait notify實現線程間通信
private static final Object lock = new Object();
private void notifyThreadWithSynchronized(){
Thread the = new Thread("線程E"){
@Override
public void run() {
synchronized (lock){
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
lock.notify();
}
}
}
}
};
Thread thf = new Thread("線程F"){
@Override
public void run() {
while(true){
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"啟動了");
}
}
}
};
thf.start();
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
the.start();
}
synchronized修飾同步代碼塊,而wait notify notify必須是在synchronized修飾代碼塊中使用,否則會拋出監視器異常。
本實例定義一個對象鎖,而線程F首先獲取到互斥鎖,在執行wait()方法時,釋放已經持有的互斥鎖,進入等待隊列。而線程E執行獲取到互斥鎖開始執行,當1==5時,調用notify方法,就會通知lock的等待隊列,然后線程E會繼續執行,由於線程F此時還是獲取不到互斥鎖(因為被線程E占用),所以會在線程E執行完畢后,才能獲取到執行權。
利用CountDonwLatch實現線程間通信
// 倒計時器
private CountDownLatch cdl = new CountDownLatch(1);
private void notifyThreadWithCountDownLatch(){
Thread thg = new Thread("線程G"){
@Override
public void run() {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"啟動了");
}
};
thg.start();
Thread thh = new Thread("線程H"){
@Override
public void run() {
for (int i = 1; i < 11; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
cdl.countDown();
}
}
}
};
thh.start();
}
本示例中使用了CountDownLatch倒計時器,利用了倒計時器的阻塞特性來實現等待。具體就是聲明一個計數器為1的倒計時器,線程G調用await()方法進入等待,直到計數器為0的時候才能夠進入執行,而線程H在i==5會將計數器減一,使其為0,此時線程G就會繼續執行了。
利用Condition中的await和signal來實現
// ReentrantLock+ condition
private Lock rtl=new ReentrantLock();
private Condition condition = rtl.newCondition();
private void notifyThreadWithCondition(){
Thread thi = new Thread("線程I"){
@Override
public void run() {
while (true){
rtl.lock();
try {
condition.await();
System.out.println(Thread.currentThread().getName()+"啟動了");
break;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rtl.unlock();
}
}
}
};
Thread thj = new Thread("線程J"){
@Override
public void run() {
rtl.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);
if(i==5){
condition.signal();
}
}
} finally {
rtl.unlock();
}
}
};
thi.start();
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
thj.start();
}
本示例是結合ReentrantLock和Condition來進行控制線程間的執行順序,Condition的await()和signal(),他們的語義和wait notify是一樣的。區別是在synchronized代碼塊里調用wait notify。通過示例可以看到這中方法實現會不斷的加鎖與解鎖,所以看起來稍微復雜些。
總結
通過以上代碼看到通過volatile的方式是最簡潔方便,用park與unpark方式是比較靈活,不用加鎖或解鎖,剩下的synchronized與Conditon都是用了鎖,而CountDownLatch則是利用了計數器。