新建一個線程有兩種方法:繼承Thread類,而後重寫run方法;實現Runnable接口,而後實現run方法。實際上Thread類也是實現的Runnable接口,再加上類只能單繼承,因此推薦使用Runnable接口。示例以下:html
class Demo1 implements Runnable{ @Override public void run() { //新建線程須要執行的邏輯 } }
class Demo2 extends Thread{ @Override public void run() { //新建線程須要執行的邏輯 } }
對於Thread類,固然可使用匿名內部類來簡化寫法:數據庫
Thread thread=new Thread(){ public void run(){ //新建線程須要執行的邏輯 } }; //Lambda表達式簡化後 Thread thread=new Thread(()->{ //須要執行的邏輯 });
新建完一個線程後,就能夠用對象實例來啓動線程,啓動後就會執行咱們重寫後的run方法:編程
thread.start();
此外,Thread類有個很是重要的構造方法:安全
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
可見,傳入的是一個Runnable類型的參數。爲何須要這個構造函數?由於Runnable接口只有一個run方法,若是咱們直接實例化實現了這個接口的類,而後調用run方法,其實就和普通的類沒有區別,並無另一個線程去執行run方法。說白了,Runnable並非新建了一個線程,而只是線程裏面執行任務的一種類型。在Java併發編程裏,咱們老是說的任務,不少時候就是Runnable類型的。因此咱們仍是須要把實現了Runnable接口的類的實例傳入Thread的構造函數,而後經過start方法去調用Runnable的run方法。多線程
//新建一個任務(Demo1實現了Runnable接口) Demo1 task=new Demo1; //新建一個線程並傳入須要執行的任務 Thread thread=new Thread(task); //啓動線程執行任務 thread.start();
熟悉了線程的建立,再簡單瞭解一下操做線程的其餘方法。併發
stop方法:做用是終止線程,但不推薦使用,由於它是強制結束線程,無論線程執行到了哪一步,很容易形成錯誤數據,引發數據不一致的問題。ide
interrupt方法:做用和stop相似,可是並不會那麼粗魯的終止線程,若是隻調用這一個方法並不會中斷線程,它還須要配合一個方法使用:函數
class Demo implements Runnable { @Override public void run() { //經過isInterrupted方法判斷當前線程是否須要中止,不須要中止就執行邏輯代碼 while (!Thread.currentThread().isInterrupted()){ //邏輯 } } } public class Use { public static void main(String[] args) throws InterruptedException { Demo task = new Demo (); Thread thread=new Thread(task); thread.start(); //通知thread能夠終止了 thread.interrupt(); } }
wait方法和notify方法:這兩個方法放在一塊兒說,是由於它們須要配合使用。簡單提一下synchronized ,這個會在在鎖裏面講。synchronized大概的做用就是:代碼塊裏的代碼,同時只能由一個線程去執行,如何確保只有一個線程去執行?誰擁有鎖誰就有資格執行。任何對象均可以調用wait方法,如obj.wait,它的意思就是讓當前線程在obj上等待並釋放當前線程佔用的鎖。obj.notify就是喚醒在obj上等待的線程並從新嘗試獲取鎖。下面演示一下簡單的使用:工具
public class Use { //必定要確保等待和喚醒是同一個對象,用類鎖也能夠,至於什麼是類鎖能夠看後面synchronized部分 static Object object=new Object(); static int i = 0; static class Demo1 implements Runnable { @Override public void run() { synchronized (object){ for(int j=0;j<10000;j++){ i++; if(i==5000){ //1.由於t1先啓動並進入同步代碼塊,因此首先輸出5000 System.out.println(); try { //釋放鎖並等待 object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //3.被喚醒後接着執行完剩餘的代碼,輸出20000 System.out.println(i); } } } static class Demo2 implements Runnable{ @Override public void run() { synchronized (object){ for(int j=0;j<10000;j++){ i++; } //2.獲取到t1釋放的鎖,執行完代碼後輸出15000並喚醒object上等待的線程 System.out.println(i); object.notify(); } } } public static void main(String[] args) throws InterruptedException { Demo1 task1 = new Demo1(); Demo2 task2 = new Demo2(); Thread thread1=new Thread(task1); Thread thread2=new Thread(task2); thread1.start(); thread2.start(); } }
須要注意的是,若是有多個線程在obj等待,只執行一次obj.notify的話,它是隨機從obj等待列表中選擇一個線程喚醒的,若是要喚醒全部等待線程,可使用obj.notifyAll。無論wait、notify仍是notifyAll只能在synchronized代碼塊中使用,不然會報IllegalMonitorStateException異常。Java之因此這麼規定,是確保不會發生Lost Wake Up問題,也就是喚醒丟失。上面那個例子中使用了同步代碼塊,因此不會發生這種問題。試想一種狀況,若是沒有synchronized確保線程是有秩序執行的,當t2線程先喚醒了object上的對象,t1線程後暫停的,那麼t1是否是就永遠會暫停下去,t2的notify至關於丟失了,這就是Lost wake up。性能
join方法:做用是讓指定線程加入當前線程。爲了節約篇幅仍是以interrupt方法的代碼爲例,若是在main方法裏調用thread.join(),那麼主線程就會等待thread線程執行完才接着執行。其實這就和單線程的效果差很少了。若是有時候thread線程執行時間太長,爲了避免影響其餘線程,咱們能夠在join方法裏傳入一個時間,單位是毫秒,當過了這個時間無論thread線程有沒有執行完,主線程都會接着執行。join方法實際上是經過wait方法實現的,注意這個wait是被加入線程等待,而不是加入的線程等待。貼一下源碼,邏輯很簡單就不復述了,若是join不傳入參數,millis默認就是0:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
yield方法:這個方法會讓出當前線程的CPU,讓出後還會接着去爭奪,可是還能不能爭奪到就不必定了。通常優先級比較低的線程,爲了節約資源能夠適當調用這個方法。
如何保證線程安全?無非就是鎖的爭奪。誰擁有了鎖,誰纔有資格執行。爲何只讓一個線程執行代碼?這就與Java的內存模型有關了。不瞭解的能夠看其餘資料,也能夠看看個人另一篇博客:http://www.javashuo.com/article/p-pfjxfzla-bq.html,最後一節就是講Java內存模型的。簡單的說:線程共享的資源是放在一個共享內存區域的。當線程A去操做一個共享變量時,它會先把這個變量拷貝到本身私有的內存空間,而後進行操做,最後把操做後的值賦值到共享內存中的變量。若是在賦值以前另一個線程B剛剛更新了這個值,那麼線程A的操做就把線程B的操做給覆蓋了,而線程B渾然不知,接着執行它的邏輯,這就形成了數據不一致的狀況。因此咱們必須加上一把鎖,確保同一時間只能由一個線程來修改這個變量。
關鍵字synchronized的做用前面已經提到過了,下面給個簡單的示例:
class Demo implements Runnable { static int i = 0; @Override public void run() { //小括號中的Demo.class就是鎖,大括號內的代碼同時只能由一個線程執行 synchronized (Demo.class){ for(int j=0;j<10000;j++) { i++; } } } } public class Use { public static void main(String[] args) throws InterruptedException { Demo task = new Demo(); Thread thread1=new Thread(task); Thread thread2=new Thread(task); thread1.start(); thread2.start(); //讓兩個線程加入主線程,這樣就能夠輸出執行後的i了 thread1.join(); thread2.join(); System.out.println(Demo.i);//輸出20000,若是去掉同步代碼塊,i絕對小於20000 } }
其實這個關鍵字的做用很好理解,關鍵在於,小括號裏面有什麼實際意義,它與Lock有什麼區別?
首先synchronized和Lock都是Java裏面的鎖機制,前者用起來更加方便,後者功能更多。方便在哪?進入代碼塊前自動獲取鎖,若是鎖已經被佔,則會等待。執行完同步代碼塊中的內容,自動釋放鎖。而Lock須要手動加鎖解鎖,接下來會講。
接着說說synchronized具體用法,小括號裏就是鎖對象。有一點須要注意,synchronized鎖的是對象,而不是裏面的代碼,誰擁有指定的鎖誰就能執行裏面的代碼。明白這一點有助於理解下面的內容。
synchronized的鎖分爲類鎖和對象鎖。它們的區別就是做用域的不一樣。
首先說說對象鎖怎麼用以及它的特色:
//對象鎖: synchronized(this){...} synchronized(類的實例){...} //修飾在void前也是對象鎖 public synchronized void run(){...}
若是synchronized裏指定的是對象鎖,那麼在建立task時,不一樣的實例對象就是不一樣的鎖。你們能夠在上面示例代碼的基礎上,再用Demo類實例化一個task2,而後用thread去執行它,接着把synchronized小括號裏的鎖換成this,也就是對象鎖,會發現輸出的i小於20000。由於task和task2徹底就是不一樣的鎖,兩個線程並不衝突,這就是爲何上面強調,鎖的是對象,而不是裏面的代碼。
再說說類鎖的用法和特色:
//類鎖 synchronized(類名.class){...} //修飾在靜態方法前也是類鎖,run方法裏直接調用handler就行 private synchronized static void handler(){...}
上面的示例代碼就是一個類鎖,即便實例化兩個不一樣的對象,提交給兩個線程執行後,輸出結果確定是20000,也就是說它們是同步的。
最後說一點,同一個類中,類鎖和對象鎖依舊是不一樣的鎖,它們之間互不干擾,不是同步的。舉個例子:
class Demo implements Runnable { static int i = 0; @Override public void run() { run2(); run3(); } //類鎖 private synchronized static void run2(){ for(int j=0;j<10000;j++) { i++; } } //對象鎖 private synchronized void run3(){ for(int j=0;j<10000;j++) { i++; } } }
main方法就不貼了,記得實例化一個task2給thread2執行。最後的輸出結果確定小於40000,若是把run3改爲靜態方法,也就是變成類鎖,輸出結果就是40000了。
Lock接口下提供了一套功能更完整的鎖機制。若是項目中線程的競爭並不激烈,使用synchronized徹底足夠,若是競爭很激烈,還須要其餘一些功能,這時候就能夠嘗試一下Lock提供的鎖了。
簡單的示例以下,說明也在註釋當中:
class ReenterLock implements Runnable { //可重入鎖,意思是:在同一個線程中,能夠對lock屢次加鎖,固然也必須解鎖對應次數 //那麼Lock下的鎖是類鎖仍是對象鎖,取決於鎖對象是類變量仍是普通的全局變量,加上static就是類鎖,反之就是對象鎖 static ReentrantLock lock = new ReentrantLock(); static int i = 0; @Override public void run() { lock.lock(); for (int j=0;j<10000;j++){ i++; } lock.unlock(); } } public class 可重入鎖 { public static void main(String[] args) throws InterruptedException { ReenterLock task = new ReenterLock(); Thread thread1=new Thread(task); Thread thread2=new Thread(task); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(ReenterLock.i); } }
可重入鎖除了以上加鎖、解鎖的基本功能外,還有其餘一些功能:
lockInterruptibly方法和interrupt方法:後者在線程中已經出現過一次了,雖然名字同樣,功能也差很少,可是做用對象不同。若是咱們的線程在加鎖也就是獲取鎖時,用的是lockInterruptibly方法,若是在等待一段時間後,還沒獲取到鎖,那麼就能夠經過interrupt方法通知這個線程不用等了。這兩個方法配合使用,在設置合理的等待時間後,能夠避免死鎖的發生。但須要注意,被通知放棄獲取鎖的線程會釋放本身的資源,結束執行任務。
tryLock方法:除了上面那種外部通知放棄獲取鎖的方法外,還有一種限時等待的方法,tryLock有兩個參數,第一個是時間,第二個是時間類型。若是不傳入任何參數,獲取到鎖直接返回true,沒獲取到直接返回false。對的,tryLock和普通的lock方法不一樣,它返回的是Boolean類型,因此通常須要配合if判斷使用:
@Override public void run() { try { if (lock.tryLock(10, TimeUnit.SECONDS)) { //邏輯代碼 } } catch (InterruptedException e) { e.printStackTrace(); } }
公平鎖和非公平鎖:公平鎖的分配是公平的,先到先得。非公平鎖則是隨機分配鎖的,你先等待的不必定能先獲取到鎖。具體的是在ReenterLock構造函數中進行設置:
//構造函數傳入true就是公平鎖,默認狀況下是非公平鎖 static ReentrantLock lock = new ReentrantLock(true);
默認狀況下采用非公平鎖,是由於公平鎖須要維護一個有序隊列,性能相較於非公平鎖是很是低的。
在synchronized代碼塊中,可使用wait方法讓當前線程釋放鎖並等待,而後經過notify方法喚醒線程並嘗試從新獲取鎖。可是這兩個方法是做用在synchronized中的,前面也說過了。在可重入鎖也有相似的功能,下面舉個簡單的例子,會發現和synchronized中的wait和notify差不都:
public class Use { static ReentrantLock lock = new ReentrantLock(); //建立的lock的condition對象 static Condition condition = lock.newCondition(); static int i = 0; static class Demo1 implements Runnable { @Override public void run() { //t1先進來加鎖(遇到一次特殊狀況,t2後啓動的反而先獲取到鎖了) lock.lock(); for (int j = 0; j < 10000; j++) { i++; if (i == 5000) { //1.輸出5000 System.out.println(i); try { //釋放鎖並等待 condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } } //被喚醒後接着執行完剩下的代碼,並輸出20000 System.out.println(i); lock.unlock(); } } static class Demo2 implements Runnable { @Override public void run() { //獲取鎖 lock.lock(); for (int j = 0; j < 10000; j++) { i++; } System.out.println(i); //2.執行完後輸出15000,並喚醒等待的線程 condition.signal(); lock.unlock(); } } public static void main(String[] args) throws InterruptedException { Demo1 task1 = new Demo1(); Demo2 task2 = new Demo2(); Thread thread1 = new Thread(task1); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); } }
await方法會使當前線程等待,同時釋放當前鎖,當其餘線程中使用signal()方法或者signalAll()方法時,線程會從新得到鎖並繼續執行。或者當線程被中斷時,也能跳出等待。這和Object.wait()方法類似。
awaitUninterruptibly方法與await方法基本相同,可是它並不會在等待過程當中響應中斷。
singal方法用於喚醒一個在等待中的線程,singalAll方法會喚醒全部在等待中的線程。這和Obejct.notify()方法很相似。
前面提到的可重入鎖和同步代碼塊一次只能讓一個線程進入,而Semaphore能夠指定多個線程,同時訪問一個資源。
public class Use { static int i = 0; //一次容許兩個線程進入 static Semaphore sema = new Semaphore(2); static class Demo2 implements Runnable { @Override public void run() { try { //若是有多餘的名額就容許一個線程進入 sema.acquire(); for(int j=0;j<10000;j++){ i++; } } catch (InterruptedException e) { e.printStackTrace(); }finally { //當前線程執行完代碼並釋放一個名額 sema.release(); } } } public static void main(String[] args) throws InterruptedException { //Demo1 task1 = new Demo1(); Demo2 task2 = new Demo2(); Thread thread1 = new Thread(task2); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println(i);//輸出小於20000,說明同時有兩個線程進去了,互相干擾了。 } }
不少時候線程只是執行讀操做,並不會互相干擾,其實這個時候並不須要線程之間相互排斥。在數據庫裏面讀寫鎖是比較常見的,在Java中,它們的邏輯實際上是同樣的。只有讀和讀不會阻塞,有寫操做必然阻塞。
代碼篇幅太多了,就再也不演示邏輯代碼了,下面是讀寫鎖的建立代碼:
static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock(); //讀鎖 static Lock readLock=readWriteLock.readLock(); //寫鎖 static Lock writLock=readWriteLock.writeLock();
一個實用的多線程工具類,說倒計數器可能有點不明白,其實就是等來指定數量的線程執行完後才執行接下來的代碼,看示例更清楚:
public class Use { //須要兩個線程執行完任務 static CountDownLatch count = new CountDownLatch(2); static int i=0; static class Demo2 implements Runnable { @Override public void run() { synchronized (Use.class) { for (int j = 0; j < 10000; j++) { i++; } //當前線程執行完任務,計數器+1 count.countDown(); } } } public static void main(String[] args) throws InterruptedException { //Demo1 task1 = new Demo1(); Demo2 task2 = new Demo2(); Thread thread1 = new Thread(task2); Thread thread2 = new Thread(task2); thread1.start(); thread2.start(); //等待指定數量的線程都執行完任務後才接着執行,至關於阻塞了當前的主線程,從而實現了join的功能 count.await(); System.out.println(i);//輸出20000 } }