本篇是系列第二篇,主要介紹下java中線程操作常用的函數和隔離區數據保護方法,join()、wait()、notify()、synchronized、volatile。
1.join()
當使用join的時候,表示該線程插入,當前線程等待該線程執行完畢
當線程執行完畢后,被等待的線程會在退出前調用notifyAll通知所有等待線程繼續執行。下面我們做個實驗來看下join的具體效果,這樣比較好理解。
public class JoinThreadMain {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new TestThread());
thread1.start();
System.out.println("count = " + count);
}
private static int count = 0;
public static class TestThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}
從代碼看定義了一個static的count變量用于計數,在main函數主線程中調用線程start方法后,接著輸出count值,因為start后線程,主線程會直接輸出當前的count值,當前count值可能還未執行完線程的循環,所以輸出的值一般都是小于10000的,例如下面之行后輸出的是63。
count = 63
如果我們要實現子線程完成后再其他線程再輸出,就可以使用join方法將該線程臨時插入當前線程去執行,直到執行完畢再繼續執行當前線程,修改后的代碼如下:
public class JoinThreadMain {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new TestThread());
thread1.start();
thread1.join();
System.out.println("count = " + count);
}
private static int count = 0;
public static class TestThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}
現在的輸出結果就是我們期望的10000了。
count = 10000
2.wait()、notify()
jdk中兩個非常重要的接口線程方法,wait和notity,這兩個方法不在Thread類而是在Object類。當一個對象實例調用wait方法后,當前線程就會在這個對象上等待,當thread1調用了obj.wait(),那么thread1就會進入等待隊列,當obj.notify被調用,系統會從隊列中隨機選擇一個線程繼續執行。obj.wait方法不可以隨便調用必須包含在對應的synchronzied語句中,無論是wait還是notify都需要先獲得目標對象的監視器。
public class WaitNotifyMain {
private final static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Thread1());
Thread thread2 = new Thread(new Thread2());
thread1.start();
thread2.start();
}
public static class Thread1 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread1 start !");
try {
System.out.println(System.currentTimeMillis() + ":thread1 wait for object !");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread1 end!");
}
}
}
public static class Thread2 extends Thread {
public void run() {
synchronized (object) {
System.out.println(System.currentTimeMillis() + ":thread2 start ! notify one thread");
object.notify();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis() + ":thread2 end!");
}
}
}
}
3.synchronized
synchronized是最常用的隔離區保護的關鍵字,線程中方法如果使用synchronized則表示該方法同時只能有一個線程訪問,這就能夠保證隔離區數據的線程安全,這也是保證隔離區數據安全最常用的方法,下面定義的add方法不管并發多大計算的結果都不會改變。
public class SyncThread implements Runnable {
private synchronized void add(){
SynchronizedMain.count2++;
}
@Override
public void run() {
for(int i=0; i<100000; i++){
add();
}
}
}
4.Volatile
當用volatile去申明變量時,就等于告訴虛擬機,這個變量極有可能會被某些程序或者線程修改,當值修改,所有線程都能看到該值變化。但是執行會發現count還是不等于100000,因為當有線程并發操作count的時候,雖然count修改后通知到各個線程,但是并發讀取到值的線程還是會存在修改后覆蓋的情況,所以肯定是小于100000,所以volatile不適合用在大并發的場景。
public class VolatileMain {
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for(int i=0; i<10; i++){
threads[i] = new Thread(new MyVolatileThread());
threads[i].start();
}
for(int i=0; i<10; i++){
threads[i].join();
}
System.out.println(count);
}
public static class MyVolatileThread implements Runnable{
@Override
public void run() {
for(int i=0; i<10000; i++){
count++;
}
}
}
}