本節內容:html
若是對什麼是線程、什麼是進程仍存有疑惑,請先Google之,由於這兩個概念不在本文的範圍以內。一篇淺顯易懂的介紹進程和線程的文章:http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.htmljava
1. 線程是什麼程序員
線程是程序執行的一條路徑,一個進程中能夠包含多條線程,那麼這個進程就是多線程的。面試
多線程:指的是這個程序(一個進程)運行時產生了不止一個線程。windows
多線程併發執行能夠提升程序的效率,能夠同時完成多項工做。設計模式
好比我打開360,點擊「查殺修復」、「電腦清理」和「優化加速」,以下圖。安全
這3個都在運行,這就是多線程。若是是單線程,在運行「查殺修復」時,「電腦清理」和「優化加速」都得等着。等「查殺修復」運行完了,才能運行下一個。服務器
多線程的效率爲何會高?見:https://www.cnblogs.com/shann/p/6851889.html多線程
2. 多線程的應用場景併發
Java程序運行原理:
Java命令會啓動java虛擬機,啓動JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個「主線程」,而後主線程去調用某個類的 main 方法。
JVM的啓動是多線程的
JVM啓動至少啓動了垃圾回收線程和主線程,因此JVM是多線程的。
Demo1_Thread.java
package com.wisedu.thread; /** * Created by jkzhao on 1/22/18. */ public class Demo1_Thread { /** * @param args * 證實jvm是多線程的 */ public static void main(String[] args) { for(int i = 0; i < 1000000; i++) { new Demo(); //創造些垃圾出來,匿名對象就是垃圾 } for(int i = 0; i < 100000; i++) { //看看主線程和垃圾回收線程是否會在cpu上切換 System.out.println("我是主線程的執行代碼"); } } } class Demo { @Override public void finalize() { //Object類中的方法 System.out.println("垃圾被清掃了"); } }
「我是主線程的執行代碼」和「垃圾被清掃了」這兩句話會交替打印,說明是間隔執行的,是多線程的。若是不是多線程的,必定是所有打印完了「垃圾被清掃了」,纔會去打印「我是主線程的執行代碼」。
1. 繼承Thread
【步驟】:
【示例】:Demo2_Thread.java
package com.wisedu.thread; /** * Created by jkzhao on 1/22/18. */ public class Demo2_Thread { /** * @param args */ public static void main(String[] args) { //執行main方法,發現有b有a間隔輸出了 MyThread mt = new MyThread(); //4,建立Thread類的子類對象 mt.start(); //5,開啓線程 注意這裏不是調用run方法 for(int i = 0; i < 1000; i++) { //爲了看出是多線程執行,在主方法裏也寫點內容 System.out.println("bb"); } } } class MyThread extends Thread { //1,繼承Thread public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaa"); } } }
2. 實現Runnable
【步驟】:
【示例】:Demo3_Thread.java
package com.wisedu.thread; /** * Created by jkzhao on 1/22/18. */ public class Demo3_Thread { /** * @param args */ public static void main(String[] args) { MyRunnable mr = new MyRunnable(); //4,建立Runnable的子類對象 //Runnable target = mr; 假如mr = 0x0011 //父類引用指向子類對象,編譯看的是父類,運行看的是子類 Thread t = new Thread(mr); //5,將其看成參數傳遞給Thread的構造函數 t.start(); //6,開啓線程 Thread類中才有start方法 for(int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyRunnable implements Runnable { //1,定義一個類實現Runnable @Override public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaa"); } } }
3. 實現Runnable的原理
查看源碼,能夠發現:
4. 兩種實現多線程方式的區別
查看源碼的區別:
繼承Thread:
實現Runnable:
5. 匿名內部類實現線程的兩種方式
好處就是不用找一個類去繼承Thread類或者實現Runnable接口了。
【示例】:Demo4_Thread.java
package com.wisedu.thread; /** * Created by jkzhao on 1/22/18. */ public class Demo4_Thread { /** * @param args */ public static void main(String[] args) { new Thread() { //1,繼承Thread類 public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("aaaaaaaaaaaaaa"); } } }.start(); //4,開啓線程 new Thread(new Runnable() { //1,將Runnable的子類對象傳遞給Thread的構造方法 public void run() { //2,重寫run方法 for(int i = 0; i < 1000; i++) { //3,將要執行的代碼寫在run方法中 System.out.println("bb"); } } }).start(); //4,開啓線程 } }
1. 獲取名字和設置名字
(1)獲取名字
經過getName()方法獲取線程對象的名字
(2)設置名字
經過構造函數能夠傳入String類型的名字
經過setName(String )方法能夠設置線程對象的名字
【示例】:Demo1_Name.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo1_Name { /** * @param args */ public static void main(String[] args) { //demo1(); Thread t1 = new Thread() { public void run() { //this.setName("張三"); System.out.println(this.getName() + "....aaaaaaaaaaaaa"); //this就至關於這個匿名內部類對象 } }; Thread t2 = new Thread() { public void run() { //this.setName("李四"); System.out.println(this.getName() + "....bb"); } }; t1.setName("張三"); t2.setName("李四"); t1.start(); t2.start(); } public static void demo1() { new Thread("芙蓉姐姐") { //經過構造方法給name賦值 public void run() { System.out.println(this.getName() + "....aaaaaaaaa"); } }.start(); new Thread("鳳姐") { public void run() { System.out.println(this.getName() + "....bb"); } }.start(); } }
2. 獲取當前線程的對象
Thread.currentThread(),主線程也能夠獲取。
【示例】:Demo2_CurrentThread.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo2_CurrentThread { /** * @param args */ public static void main(String[] args) { new Thread() { public void run() { System.out.println(getName() + "....aaaaaa"); } }.start(); new Thread(new Runnable() { public void run() { //Thread.currentThread()獲取當前正在執行的線程 System.out.println(Thread.currentThread().getName() + "...bb"); } }).start(); Thread.currentThread().setName("我是主線程"); System.out.println(Thread.currentThread().getName()); } }
3. 休眠線程
Thread.sleep(毫秒,納秒),控制當前線程休眠若干毫秒,windows不太支持納秒值。 1秒 = 1000毫秒,1秒 = 1000 * 1000 * 1000m納秒。
【示例】:Demo3_Sleep.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo3_Sleep { /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { //demo1(); new Thread() { public void run() { for(int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...aaaaaaaaaa"); } } }.start(); new Thread() { public void run() { for(int i = 0; i < 10; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...bb"); } } }.start(); } public static void demo1() throws InterruptedException { for(int i = 20; i >= 0; i--) { Thread.sleep(1000); //毫秒 System.out.println("倒計時第" +i + "秒"); } } }
4. 守護線程
setDaemon(),設置一個線程爲守護線程,該線程不會單獨執行,當其餘非守護線程都執行結束後,自動退出。
就像下象棋同樣,非守護線程至關於帥,守護線程至關於車馬相士。帥死掉了,其餘的車馬相士也隨之中止,車馬相士自殺有個緩衝時間。
再好比,用QQ向別人傳文件的時候,qq的主界面用的是非守護線程作的,而傳輸的窗口用的守護線程,主界面一關掉,傳輸並非立馬就中止的,它還會再發幾個數據包,由於它要等待接收退出的命令。
【示例】:Demo4_Daemon.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo4_Daemon { /** * @param args * 守護線程 */ public static void main(String[] args) { Thread t1 = new Thread() { //帥 public void run() { for(int i = 0; i < 2; i++) { System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaa"); } } }; Thread t2 = new Thread() { //車馬相士 public void run() { for(int i = 0; i < 50; i++) { System.out.println(getName() + "...bb"); } } }; t2.setDaemon(true); //當傳入true,意味着設置爲守護線程 t1.start(); t2.start(); } }
5. 加入線程
【示例】:Demo5_Join.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo5_Join { /** * @param args * join(), 當前線程暫停, 等待指定的線程執行結束後, 當前線程再繼續 */ public static void main(String[] args) { final Thread t1 = new Thread() { public void run() { for(int i = 0; i < 10; i++) { System.out.println(getName() + "...aaaaaaaaaaaaa"); } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 10; i++) { if(i == 2) { try { //t1.join(); //插隊線程執行完後,當前線程才能執行。這裏匿名內部類在使用它所在方法中的局部變量的時候,該變量必須用final修飾 t1.join(1); //插隊指定的時間,過了指定時間後,兩條線程交替執行 } catch (InterruptedException e) { //插隊過來,當前線程出現中斷異常 e.printStackTrace(); } } System.out.println(getName() + "...bb"); } } }; t1.start(); t2.start(); } }
6. 禮讓線程
yield讓出CPU,可是這個yield實現的效果很差,會達不到這個效果。
【示例】:Demo6_Yield.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo6_Yield { /** * yield讓出cpu,禮讓線程 */ public static void main(String[] args) { new MyThread().start(); new MyThread().start(); } } class MyThread extends Thread { public void run() { for(int i = 1; i <= 1000; i++) { System.out.println(getName() + "..." + i); if(i % 10 == 0) { //10的倍數 Thread.yield(); //讓出CPU } } } }
7. 設置線程優先級
setPriority() 設置線程優先級
【示例】:Demo7_Priority.java
package com.wisedu.threadmethod; /** * Created by jkzhao on 1/22/18. */ public class Demo7_Priority { /** * @param args */ public static void main(String[] args) { Thread t1 = new Thread(){ public void run() { for(int i = 0; i < 100; i++) { System.out.println(getName() + "...aaaaaaaaa" ); } } }; Thread t2 = new Thread(){ public void run() { for(int i = 0; i < 100; i++) { System.out.println(getName() + "...bb" ); } } }; //t1.setPriority(10); //t2.setPriority(1); //直接給值也能夠,可是不能超過下面兩個常量 t1.setPriority(Thread.MIN_PRIORITY); //設置最小的線程優先級 t2.setPriority(Thread.MAX_PRIORITY); //設置最大的線程優先級 t1.start(); t2.start(); } }
Thread.interrupted()
待補充。。。
1. 什麼狀況下須要同步
當多線程併發,有多段代碼同時執行時,咱們但願某一段代碼執行的過程當中CPU不要切換到其餘線程工做,等當前這段代碼執行完再切換到其它線程,這時就須要同步。
若是兩段代碼是同步的,那麼同一個時間只能執行一段,在一段代碼沒執行結束以前,不會執行另一段代碼。
舉個例子:賺了3000塊錢存銀行,而後你有一存款折和一張銀行卡,都是對應於同一個帳戶,你拿這個摺子去櫃檯取錢去,櫃檯服務人員問你取多少錢,你說2000。這個時候服務人員得把2000輸入電腦,電腦會去檢查你如今有沒有2000塊錢。一查發現你有多餘2000塊的存款,這時候就把錢吐給你。而後把你帳戶上的錢減掉2000。咱們假設檢查完你帳戶發現錢夠,正要把錢出給你的時候,你老婆拿着你的銀行卡在取款機上取錢,取2000,取款機一檢查發現你的帳戶裏有超過2000的存款(此時你的帳戶上錢還沒減),而後取款機就把這2000吐給你老婆了,而後取款機把你的帳戶的餘額更新爲1000。而後櫃檯那邊繼續執行,又給了你2000,把你的帳戶餘額更新爲1000。
這顯然對銀行不公平,你和你老婆比如兩個線程,這兩個線程在執行一個對帳戶取款的方法的過程之中,可是大家兩個線程同時訪問同一個帳戶(同一個資源),這種狀況下線程順序協調很差的話,很容易出現先後數據不一致的狀況。咱們對多個線程訪問同一個資源的時候,咱們對這多個線程進行協調的這個東西叫作線程同步。
怎麼解決這個問題呢?
當某個線程在調用取款方法的時候,這個帳戶歸這個線程獨佔,其餘線程不能訪問。
代碼模擬:
public class TestSync implements Runnable { Timer timer = new Timer(); public static void main(String[] args) { TestSync test = new TestSync(); Thread t1 = new Thread(test); Thread t2 = new Thread(test); t1.setName("t1"); t2.setName("t2"); t1.start(); t2.start(); } public void run(){ timer.add(Thread.currentThread().getName()); } } class Timer{ private int num = 0; public void add(String name){ //用來作計數用的 num ++; try { Thread.sleep(1); } catch (InterruptedException e) {} System.out.println(name+", 你是第"+num+"個使用timer的線程"); } }
運行結果:
t1, 你是第2個使用timer的線程 t2, 你是第2個使用timer的線程 Process finished with exit code 0
問題出在執行 num ++ 和 打印語句 之間被打斷了(由於第一個線程睡眠了),第二個線程調add方法去修改同一個對象的成員變量num的值,接着第二個線程睡眠,第一個線程醒了過來,打印num的值爲2。而後第2個線程醒過來打印num的值爲2。
num ++ 和 打印語句 應做爲原子操做,不可再分。
怎麼解決呢?鎖住當前對象。
class Timer{ private int num = 0; public void add(String name){ //用來作計數用的 synchronized (this) { //鎖定當前對象,對於下面的語句,一個線程在執行的時候不會被另外的線程打斷。這個對象裏面的成員變量num也被鎖定了。這叫互斥鎖 num++; try { Thread.sleep(1); } catch (InterruptedException e) { } System.out.println(name + ", 你是第" + num + "個使用timer的線程"); } } }
還有一種更爲簡潔的寫法:
class Timer{ private int num = 0; public synchronized void add(String name){ //在執行該方法時鎖定當前對象,也就是timer對象 //synchronized (this) { //鎖定當前對象,對於下面的語句,一個線程在執行的時候不會被另外的線程打斷。這個對象裏面的成員變量num也被鎖定了。這叫互斥鎖 num++; try { Thread.sleep(1); } catch (InterruptedException e) { } System.out.println(name + ", 你是第" + num + "個使用timer的線程"); //} } }
2. 同步的機制:每一個對象都有的方法
synchronized, wait, notify 是任何對象都具備的同步工具。他們是應用於同步問題的人工線程調度工具。講其本質,首先就要明確monitor的概念,Java中的每一個對象都有一個監視器,來監測併發代碼的重入。在非多線程編碼時該監視器不發揮做用,反之若是在synchronized 範圍內,監視器發揮做用。
wait/notify必須存在於synchronized塊中。而且,這三個關鍵字針對的是同一個監視器(某對象的監視器)。這意味着wait以後,其餘線程能夠進入同步塊執行。
當某代碼並不持有監視器的使用權時(如圖中5的狀態,即脫離同步塊)去wait或notify,會拋出java.lang.IllegalMonitorStateException。也包括在synchronized塊中去調用另外一個對象的wait/notify,由於不一樣對象的監視器不一樣,一樣會拋出此異常。
用法以下:
public class Thread1 implements Runnable { Object lock; public void run() { synchronized(lock){ ..do something } } }
public class Thread1 implements Runnable { public synchronized void run() { ..do something } }
/** * 生產者生產出來的產品交給店員 */ public synchronized void produce() { if(this.product >= MAX_PRODUCT) { try { wait(); System.out.println("產品已滿,請稍候再生產"); } catch(InterruptedException e) { e.printStackTrace(); } return; } this.product++; System.out.println("生產者生產第" + this.product + "個產品."); notifyAll(); //通知等待區的消費者能夠取出產品了 } /** * 消費者從店員取產品 */ public synchronized void consume() { if(this.product <= MIN_PRODUCT) { try { wait(); System.out.println("缺貨,稍候再取"); } catch (InterruptedException e) { e.printStackTrace(); } return; } System.out.println("消費者取走了第" + this.product + "個產品."); this.product--; notifyAll(); //通知等待去的生產者能夠生產產品了 }
下面將進行更詳細的講解。
3. 同步代碼塊
使用synchronized關鍵字加上一個鎖對象來定義一段代碼,這就叫同步代碼塊。
多個同步代碼塊若是使用相同的鎖對象,那麼它們就是同步的。
【示例】:Demo1_Synchronized.java
package com.wisedu.sync; /** * Created by jkzhao on 1/22/18. */ public class Demo1_Synchronized { /** * @param args * 同步代碼塊 */ public static void main(String[] args) { final Printer p = new Printer(); new Thread() { public void run() { while(true) { p.print1(); } } }.start(); new Thread() { public void run() { while(true) { p.print2(); } } }.start(); } } class Printer { Demo d = new Demo(); public void print1() { //synchronized(new Demo()) { //同步代碼塊,鎖機制,鎖是對象來作的,鎖對象能夠是任意的,直接建立個Object對象也能夠 synchronized(d) { System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } } public void print2() { //synchronized(new Demo()) { //鎖對象不能用匿名對象,由於匿名對象不是同一個對象 synchronized(d) { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } } class Demo{}
4. 多線程同步方法
使用synchronized關鍵字修飾一個方法,該方法中全部的代碼都是同步的。
【示例】:Demo2_Synchronized.java
package com.wisedu.sync; /** * Created by jkzhao on 1/22/18. */ public class Demo2_Synchronized { /** * @param args * 同步代碼塊 */ public static void main(String[] args) { final Printer2 p = new Printer2(); new Thread() { public void run() { while(true) { p.print1(); //p.print3(); } } }.start(); new Thread() { public void run() { while(true) { p.print2(); //p.print4(); } } }.start(); } } class Printer2 { //非靜態的同步方法的鎖對象是神馬? //答:非靜態的同步方法的鎖對象是this public synchronized void print1() { //同步方法只須要在方法上加synchronized關鍵字便可 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } public void print2() { synchronized(this) { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } //靜態的同步方法的鎖對象是什麼? //答:是該類的字節碼對象 public static synchronized void print3() { //同步方法只須要在方法上加synchronized關鍵字便可 System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); } public static void print4() { synchronized(Printer2.class) { System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); } } }
多線程併發操做同一數據時,就有可能出現線程安全問題。
使用同步技術能夠解決這種問題,把操做數據的代碼進行同步,不要多個線程一塊兒操做。
【示例】:鐵路售票,一共100張,經過四個窗口賣完(四個窗口也就是四個線程)。
Demo3_Ticket.java --繼承Thread類
package com.wisedu.sync; /** * Created by jkzhao on 1/22/18. */ public class Demo3_Ticket { /** * 需求:鐵路售票,一共100張,經過四個窗口賣完. */ public static void main(String[] args) { new Ticket().start(); new Ticket().start(); new Ticket().start(); new Ticket().start(); } } class Ticket extends Thread { private static int ticket = 100; //全部對象共享這100張票 //private static Object obj = new Object(); //若是用 引用數據類型的成員變量 看成鎖對象,必須是靜態的 public void run() { while(true) { synchronized(Ticket.class) { //obj if(ticket == 0) { break; } try { //模擬這邊可能有n多行代碼執行 Thread.sleep(10); //10毫秒 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "...這是第" + ticket-- + "號票"); } } } }
Demo4_Ticket.java --實現Runnable接口
package com.wisedu.sync; /** * Created by jkzhao on 1/22/18. */ public class Demo4_Ticket { /** * @param args * 火車站賣票的例子用實現Runnable接口 */ public static void main(String[] args) { MyTicket mt = new MyTicket(); new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); new Thread(mt).start(); /*Thread t1 = new Thread(mt); //屢次啓動一個線程是非法的 t1.start(); t1.start(); t1.start(); t1.start();*/ } } class MyTicket implements Runnable { private int tickets = 1000; //由於咱們不須要建立4個對象,因此不須要定義成靜態的 @Override public void run() { while(true) { synchronized(Ticket.class) { //能夠用this,由於只建立了一個對象 if(tickets == 0) { break; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...這是第" + tickets-- + "號票"); } } } }
多線程同步的時候,若是代碼嵌套,使用相同鎖,就有可能出現死鎖。因此儘可能不要嵌套使用。
好比,桌子上擺了一桌滿漢全席,桌子周圍坐着一羣哲學家,而後給哲學家發筷子,一人一根,而後哲學家就用本身的三寸不爛之舌說服別人把筷子給本身。互相去說服,最後可能致使誰都沒有說服誰,致使成了死鎖,所有活活餓死在滿漢全席的邊上。
【示例】:Demo5_DeadLock.java
package com.wisedu.sync; /** * Created by jkzhao on 1/22/18. */ public class Demo5_DeadLock { /** * @param args */ private static String s1 = "筷子左"; //定義兩個字符串當作鎖 private static String s2 = "筷子右"; public static void main(String[] args) { new Thread() { public void run() { while(true) { synchronized(s1) { System.out.println(getName() + "...獲取" + s1 + "等待" + s2); synchronized(s2) { System.out.println(getName() + "...拿到" + s2 + "開吃"); } } } } }.start(); new Thread() { public void run() { while(true) { synchronized(s2) { System.out.println(getName() + "...獲取" + s2 + "等待" + s1); synchronized(s1) { System.out.println(getName() + "...拿到" + s1 + "開吃"); } } } } }.start(); } }
執行結果:
Thread-0...獲取筷子左等待筷子右 Thread-0...拿到筷子右開吃 Thread-0...獲取筷子左等待筷子右 Thread-0...拿到筷子右開吃 Thread-0...獲取筷子左等待筷子右 Thread-0...拿到筷子右開吃 Thread-0...獲取筷子左等待筷子右 Thread-1...獲取筷子右等待筷子左
此時已經產生死鎖了,程序並無中止。。。
Vertor、StringBuffer、Hashtable、Collections.synchronized(xxx)
1. Vector是線程安全的
查看源碼java.util.Vector.java,找到add方法
public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; }
2. ArrayList是線程不安全的
查看源碼,java.util.ArrayList.java,找到add方法
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
3. StringBuffer是線程安全的,StringBuilder是線程不安全的
查看源碼,java.lang.StringBuffer,java.lang.StringBuilder,找到append方法
public synchronized StringBuffer append(Object obj) { super.append(String.valueOf(obj)); return this; } public synchronized StringBuffer append(String str) { super.append(str); return this; }
...
public StringBuilder append(Object obj) { return append(String.valueOf(obj)); } public StringBuilder append(String str) { super.append(str); return this; } ...
4. Hashtable是線程安全的,HashMap是線程不安全的
查看源碼,java.lang.Hashtable,java.lang.HashMap,找到append方法
java.lang.Hashtable
public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } ...
java.lang.HashMap
public V put(K key, V value) { if (table == EMPTY_TABLE) { inflateTable(threshold); } ...
5. Collections.synchronized(xxx)
能夠將線程不安全的線程變成線程安全的。能夠到API文檔裏搜索下Collections這個類,裏面有方法synchronized(xxx)
調用上面的這些方法就能夠將這幾種集合變成同步的。
單例設計模式:保證類在內存中只有一個對象
如何保證類在內存中只有一個對象?
1. 單例模式兩種寫法
(1)餓漢式,開發使用這種方式
(2)懶漢式,面試寫這種方式。多線程問題?
Demo1_Singleton.java
package com.wisedu.thread2; /** * Created by jkzhao on 1/23/18. */ public class Demo1_Singleton { /** * @param args * * 單例設計模式:保證類在內存中只有一個對象。 */ public static void main(String[] args) { Singleton s1 = Singleton.s; //成員變量被私有,不能經過類名.調用 //Singleton.s = null; //修改爲員變量s Singleton s2 = Singleton.s; System.out.println(s1 == s2); /* Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2);*/ } } /* * 餓漢式 * 上來就是new對象,因此稱爲餓漢式 private static Singleton s = new Singleton(); * / /*class Singleton { //1,私有構造方法,其餘類不能訪問該構造方法了 private Singleton(){} //2,建立本類對象 private static Singleton s = new Singleton(); //私有化是爲了防止被修改 //3,對外提供公共的訪問方法 public static Singleton getInstance() { //因爲上面把成員變量s私有化,這裏提供get方法獲取實例 return s; } }*/ /* * 懶漢式,單例的延遲加載模式 */ /*class Singleton { //1,私有構造方法,其餘類不能訪問該構造方法了 private Singleton(){} //2,聲明一個引用 private static Singleton s ; //3,對外提供公共的訪問方法 public static Singleton getInstance() { //獲取實例 if(s == null) { //假設線程1剛進來,結果CPU執行權被別的線程搶走了,那麼線程1等待,線程2剛進來,CPU正好也切換去執行其餘線程了,線程2等待。 //而後線程1搶回了CPU,因而執行下面語句建立了一個對象;線程2也搶回了CPU,因而執行下面語句又建立了一個對象。這就建立了兩個對象 //因此開發的時候不用懶漢式,面試的時候用這個懶漢式,由於面試的時候會讓寫一個單例的延遲加載模式 s = new Singleton(); } return s; } }*/ /* * 餓漢式和懶漢式的區別 * 1,餓漢式是空間換時間,懶漢式是時間換空間 * 2,在多線程訪問時,餓漢式不會建立多個對象,而懶漢式有可能會建立多個對象 */ /** * 第三種,沒有名字 */ class Singleton { //1,私有構造方法,其餘類不能訪問該構造方法了 private Singleton(){} //2,聲明一個引用 public static final Singleton s = new Singleton(); }
2. 單例設計模式應用場景之Runtime類
餓漢式
Demo2_Runtime.java
package com.wisedu.thread2; /** * Created by jkzhao on 1/23/18. */ import java.io.IOException; public class Demo2_Runtime { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { Runtime r = Runtime.getRuntime(); //獲取運行時對象 //r.exec("shutdown -s -t 300"); //在單獨的進程中執行指定的字符串命令 r.exec("shutdown -a"); //爲何使用單例設計模式? // 第一條代碼設置了5min後關機,第二條代碼取消關機,修改的是第一條語句修改後的結果 } }
3. 單例設計模式應用場景之Timer類
Timer類,計時器。一種工具,線程用其安排之後在後臺線程中執行的任務,能夠安排任務執行一次,或者按期重複執行。
與每一個Timer對象相對應的是單個後臺線程,用於順序地執行全部計時器任務。
Demo3_Timer.java
package com.wisedu.thread2; /** * Created by jkzhao on 1/23/18. */ import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class Demo3_Timer { /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Timer t = new Timer(); //在指定時間安排指定任務 //第一個參數,是安排的任務,第二個參數是執行的時間,第三個參數是過多長時間再重複執行 t.schedule(new MyTimerTask(), new Date(118, 0, 22, 16, 39, 51),3000); //year是要-1900 最後的3000是毫秒值 while(true) { Thread.sleep(1000); System.out.println(new Date()); //隔1s打一次時間,看看到指定時間是否執行任務 } } } class MyTimerTask extends TimerTask { @Override public void run() { System.out.println("起牀背英語單詞"); } }
1. 何時須要通訊
多個線程併發執行時,在默認狀況下CPU是隨機切換線程的
若是咱們但願它們有規律的執行,就可使用通訊,例如每一個線程執行一次打印
2. 兩個線程間通訊
Demo1_Notify.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ public class Demo1_Notify { /** * @param args * 等待喚醒機制 */ public static void main(String[] args) { final Printer p = new Printer(); new Thread() { public void run() { while(true) { try { p.print1(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { while(true) { try { p.print2(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } //等待喚醒機制 class Printer { private int flag = 1; public void print1() throws InterruptedException { synchronized(this) { if(flag != 1) { this.wait(); //當前線程等待,沒有人喚醒它的話,就一直在等待 } System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); flag = 2; this.notify(); //隨機喚醒單個等待的線程(假如此時沒有等待的線程,隨機喚醒下也是能夠的) } } public void print2() throws InterruptedException { synchronized(this) { if(flag != 2) { this.wait(); } System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); flag = 1; this.notify(); } } }
3. 三個或三個以上間的線程通訊
多個線程通訊的問題
Demo2_NotifyAll.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ public class Demo2_NotifyAll { /** * 循環打印:黑馬程序員、傳智播客、itheima * @param args */ public static void main(String[] args) { final Printer2 p = new Printer2(); new Thread() { public void run() { while(true) { try { p.print1(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { while(true) { try { p.print2(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { while(true) { try { p.print3(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } /* 1,在同步代碼塊中,用哪一個對象鎖,就用哪一個對象去調用wait方法,好比下面用的是this * 2,爲何wait方法和notify方法定義在Object這類中? * 由於鎖對象能夠是任意對象,Object是全部類的基類,因此wait方法和notify方法須要定義在Object這個類中 * 3,sleep方法和wait方法的區別? * a,sleep方法必須傳入參數,參數就是時間,時間到了自動醒來 * wait方法能夠傳入參數也能夠不傳入參數,傳入參數就是在參數的時間結束後等待,不傳入參數就是直接等待 * b,sleep方法在同步函數或同步代碼塊中,不釋放鎖,睡着了也抱着鎖睡(就是在sleep的時間內,也佔着CPU) * wait方法在同步函數或者同步代碼塊中,釋放鎖(就是該線程調用wait方法等待了,把鎖釋放了,這樣CPU才能去執行其餘線程) */ class Printer2 { private int flag = 1; public void print1() throws InterruptedException { synchronized(this) { while(flag != 1) { this.wait(); //當前線程等待 } System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); flag = 2; //this.notify(); //不能用這個 this.notifyAll(); } } public void print2() throws InterruptedException { synchronized(this) { while(flag != 2) { this.wait(); //線程2在此等待 } System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); flag = 3; //this.notify(); //不能用這個 this.notifyAll(); } } public void print3() throws InterruptedException { synchronized(this) { while(flag != 3) { this.wait(); //線程3在此等待,if語句是在哪裏等待,就在哪裏起來 //while循環是循環判斷,每次搶到執行權都會去判斷標記flag } System.out.print("i"); System.out.print("t"); System.out.print("h"); System.out.print("e"); System.out.print("i"); System.out.print("m"); System.out.print("a"); System.out.print("\r\n"); flag = 1; //this.notify(); //不能用這個 this.notifyAll(); } } }
分析下執行過程:
可是這不合理,也就是說無論符合不符合規則,到沒到時間,就把其它線程都叫起來。好比說園區有3個保安,一個值早上8點到下午4點的班,一個是下午4點到晚上12點,一個是晚上12點到次日早上8點的班。第一個值班後並不知道誰盯下一個班,因而就把另外兩個保安叫起來了。兩我的起來後看誰知足條件就去值班,另外一個回去睡覺。若是總這麼叫起人的話,人家就可能怒了。
這是JDK 1.5以前的解決方案,在JDK 1.5有個更好的解決方案叫互斥鎖。
4. JDK 1.5的新特性之互斥鎖
Condition 將Object監視器方法(wait、notify和notifyAll)分解成大相徑庭的對象,以便經過將這些對象與任意Lock實現組合使用,爲每一個對象提供多個等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和語句的使用,Condition替代了Object監視器方法的使用。Condition實例實質上被綁定到一個鎖上,要爲特定 Lock 實例得到 Condition 實例,請用其 newCondition() 方法。
Demo3_ReetrantLock.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Demo3_ReentrantLock { /** * @param args */ public static void main(String[] args) { final Printer3 p = new Printer3(); new Thread() { public void run() { while(true) { try { p.print1(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { while(true) { try { p.print2(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); new Thread() { public void run() { while(true) { try { p.print3(); } catch (InterruptedException e) { e.printStackTrace(); } } } }.start(); } } class Printer3 { private ReentrantLock r = new ReentrantLock(); private Condition c1 = r.newCondition(); //建立3個監視器,一個線程上放一個監視器 private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private int flag = 1; public void print1() throws InterruptedException { r.lock(); //獲取鎖 if(flag != 1) { c1.await(); //等待 } System.out.print("黑"); System.out.print("馬"); System.out.print("程"); System.out.print("序"); System.out.print("員"); System.out.print("\r\n"); flag = 2; //this.notify(); c2.signal(); //喚醒 r.unlock(); //釋放鎖 } public void print2() throws InterruptedException { r.lock(); if(flag != 2) { c2.await(); } System.out.print("傳"); System.out.print("智"); System.out.print("播"); System.out.print("客"); System.out.print("\r\n"); flag = 3; //this.notify(); c3.signal(); r.unlock(); } public void print3() throws InterruptedException { r.lock(); if(flag != 3) { c3.await(); } System.out.print("i"); System.out.print("t"); System.out.print("h"); System.out.print("e"); System.out.print("i"); System.out.print("m"); System.out.print("a"); System.out.print("\r\n"); flag = 1; c1.signal(); r.unlock(); } }
1. 線程組的概述
Java中使用ThreadGroup來表示線程組,它能夠對一批線程進行分類管理,Java容許程序直接對線程組進行控制。
默認狀況下,全部的線程都屬於主線程組。
咱們也能夠給線程設置分組
2. 線程組的使用
【示例】:線程組的使用,默認是主線程組。
Demo4_ThreadGroup.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ public class Demo4_ThreadGroup { /** * @param args * ThreadGroup */ public static void main(String[] args) { //demo1(); ThreadGroup tg = new ThreadGroup("我是一個新的線程組"); //建立新的線程組 MyRunnable mr = new MyRunnable(); //建立Runnable的子類對象 Thread t1 = new Thread(tg, mr, "張三"); //將線程t1放在組中 Thread t2 = new Thread(tg, mr, "李四"); //將線程t2放在組中 System.out.println(t1.getThreadGroup().getName()); //獲取線程組的名字 System.out.println(t2.getThreadGroup().getName()); tg.setDaemon(true); //整個組內的線程都變成守護線程了 } public static void demo1() { MyRunnable mr = new MyRunnable(); Thread t1 = new Thread(mr, "張三"); Thread t2 = new Thread(mr, "李四"); ThreadGroup tg1 = t1.getThreadGroup(); ThreadGroup tg2 = t2.getThreadGroup(); System.out.println(tg1.getName()); //打印出來是main,線程默認的是在主線程組 System.out.println(tg2.getName()); } } class MyRunnable implements Runnable { @Override public void run() { for(int i = 0; i < 1000; i++) { System.out.println(Thread.currentThread().getName() + "...." + i); } } }
其中,stop()方法太暴力,已過期了。在之前經過thread.stop()能夠中止一個線程,注意stop()方法是能夠由一個線程去中止另一個線程,這種方法太過暴力並且是不安全的,怎麼說呢,線程A調用線程B的stop方法去中止線程B,調用這個方法的時候線程A其實並不知道線程B執行的具體狀況,這種忽然間地中止會致使線程B的一些清理工做沒法完成,還有一個狀況是執行stop方法後線程B會立刻釋放鎖,這有可能會引起數據不一樣步問題。基於以上這些問題,stop()方法被拋棄了。
再來兩張圖:
線程狀態
線程狀態轉換
各類狀態一目瞭然,值得一提的是"blocked"這個狀態:
線程在Running的過程當中可能會遇到阻塞(Blocked)狀況
此外,在runnable狀態的線程是處於被調度的線程,此時的調度順序是不必定的。Thread類中的yield方法可讓一個running狀態的線程轉入runnable。
1. 線程池概述
程序啓動一個新線程成本是比較高的,由於它涉及到要與操做系統進行交互。而使用線程池能夠很好的提升性能,尤爲是當程序中要建立大量生存期很短的線程時,更應該考慮使用線程池。線程池裏的每個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。在JDK 5以前,咱們必須手動實現本身的線程池,從JDK 5開始,Java內置了線程池。
2. 內置線程池的使用概述
JDK 5新增了一個Executors工廠類來產生線程池,有以下幾個方法:
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
Future<?> submit(Runnable task) //提交一個返回值的任務用於執行,返回一個表示任務的未決結果的Future
<T> Future<T> submit(Callable<T> task) //提交一個Runnable任務用於執行,並返回一個表示該任務的Future
使用步驟:
【示例】:提交的是Runnable
Demo5_Executors.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo5_Executors { /** * public static ExecutorService newFixedThreadPool(int nThreads) * public static ExecutorService newSingleThreadExecutor() */ public static void main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池 pool.submit(new MyRunnable()); //將線程放進池子裏並執行 pool.submit(new MyRunnable()); pool.shutdown(); //關閉線程池,啓動一次順序關閉,執行之前提交的任務,但不接受新任務。 // 若是不關閉池子,線程會不停的執行它裏面的代碼 } }
3. 多線程程序的實現方式三
Callable
future模式:併發模式的一種,能夠有兩種形式,即無阻塞和阻塞,分別是isDone和get。其中Future對象用來存放該線程的返回值以及狀態
ExecutorService e = Executors.newFixedThreadPool(3); //submit方法有多重參數版本,及支持callable也可以支持runnable接口類型. Future future = e.submit(new myCallable()); future.isDone() //return true,false 無阻塞。若是任務已完成,返回true future.get() // return 返回值,阻塞直到該線程運行結束
Demo6_Callable.java
package com.wisedu.thread_communication; /** * Created by jkzhao on 1/24/18. */ import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Demo6_Callable { /** * @param args * @throws ExecutionException * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService pool = Executors.newFixedThreadPool(2);//建立線程池 Future<Integer> f1 = pool.submit(new MyCallable(100)); //將線程放進池子裏並執行 Future<Integer> f2 = pool.submit(new MyCallable(50)); System.out.println(f1.get()); System.out.println(f2.get()); pool.shutdown(); //關閉線程池 } } class MyCallable implements Callable<Integer> { private int num; public MyCallable(int num) { this.num = num; } @Override public Integer call() throws Exception { //call()計算結果,若是沒法計算結果,則拋出一個異常。原來的run()是不能夠拋異常的 int sum = 0; for(int i = 1; i <= num; i++) { sum += i; } return sum; } }
1. 概述
簡單工廠模式又叫靜態工廠方法模式,它定義了一個具體的工廠類負責建立一些類的實例
2. 案例
public abstract Animal { public abstract void eat(); }
public class Dog extends Animal {}
public class Cat extends Animal {}
開始,在測試類中每一個具體的內容本身建立對象,可是,建立對象的工做若是比較麻煩,就須要有人專門作這個事情,因此就製造了一個專門的類來建立對象。
先來3個類:
Animal.java
package com.wisedu.staticFactory; /** * Created by jkzhao on 1/25/18. */ public abstract class Animal { public abstract void eat(); }
Dog.java
package com.wisedu.staticFactory; /** * Created by jkzhao on 1/25/18. */ public class Dog extends Animal { @Override public void eat() { System.out.println("狗吃肉"); } }
Cat.java
package com.wisedu.staticFactory; /** * Created by jkzhao on 1/25/18. */ public class Cat extends Animal { @Override public void eat() { System.out.println("貓吃魚"); } }
接下來得作一個動物工廠來生產動物,若是沒有這個工廠,用到對象就得本身建立。和上面的線程池工廠一個道理。
Animal.java
package com.wisedu.staticFactory; /** * Created by jkzhao on 1/25/18. */ public class AnimalFactory { public static Dog createDog() { return new Dog(); } public static Cat createCat() { return new Cat(); } }
接下來寫個測試類
test.java
package com.wisedu.staticFactory; /** * Created by jkzhao on 1/25/18. */ public class Test { /** * @param args */ public static void main(String[] args) { Dog d = AnimalFactory.createDog(); //不用本身建立了,用工廠來建立 d.eat(); } }
可是咱們發現這種方式不太好,工廠類裏面對於建立Dog和Cat各寫了一個方法,若是動物不少的話,得定義不少方法,複用性太差。能夠改進下:
public class AnimalFactory { /*public static Dog createDog() { return new Dog(); } public static Cat createCat() { return new Cat(); }*/ //發現方法會定義不少,複用性太差 //改進 public static Animal createAnimal(String name) { if("dog".equals(name)) { return new Dog(); }else if("cat".equals(name)) { return new Cat(); }else { return null; } } }
修改測試類的main方法,以下:
public class Test { /** * @param args */ /*public static void main(String[] args) { Dog d = AnimalFactory.createDog(); //不用本身建立了,用工廠來建立 d.eat(); }*/ public static void main(String[] args) { //Dog d = AnimalFactory.createDog(); Dog d = (Dog) AnimalFactory.createAnimal("dog"); d.eat(); Cat c = (Cat) AnimalFactory.createAnimal("cat"); c.eat(); } }
1. 概述
工廠方法模式中的抽象工廠類負責定義建立對象的接口,具體對象的建立工做由繼承抽象工廠的具體類實現。
2. 案例
先來3個類,Animal.java Dog.java Cat.java,這幾個和上面的案例中的代碼同樣。
接着定義一個工廠接口,Factory.java
package com.wisedu.FactoryMethod; /** * Created by jkzhao on 1/25/18. */ public interface Factory { public Animal createAnimal(); //多態的思想 }
接着編寫貓工廠和狗工廠
CatFactory.java
package com.wisedu.FactoryMethod; /** * Created by jkzhao on 1/25/18. */ public class CatFactory implements Factory { @Override public Animal createAnimal() { return new Cat(); } }
DogFactory.java
package com.wisedu.FactoryMethod; /** * Created by jkzhao on 1/25/18. */ public class DogFactory implements Factory { @Override public Animal createAnimal() { return new Dog(); } }
再來編寫測試類
package com.wisedu.FactoryMethod; /** * Created by jkzhao on 1/25/18. */ public class Test { /** * @param args */ public static void main(String[] args) { DogFactory df = new DogFactory(); //先建立狗工廠 Dog d = (Dog) df.createAnimal(); d.eat(); } }
你會發現也很麻煩,都有優缺點。