1.創建線程的三種方式
使用Thread
package com.wpbxx.test; //1.自定義一個類,繼承java.lang包下的Thread類 class MyThread extends Thread{ //2.重寫run方法 @Override public void run() { //3.將要在線程中執行的代碼編寫在run方法中 for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } } public class helloworld { public static void main(String[] args) { //4.創建上面自定義類的對象 MyThread mt = new MyThread(); //5.調用start方法啟動線程 mt.start(); for(int i = 0; i< 1000; i++) { System.out.println("xx"); } } }
使用Runnable
package com.wpbxx.test; //1.自定義一個類實現java.lang包下的Runnable接口 class MyRunnable implements Runnable{ //2.重寫run方法 @Override public void run() { //3.將要在線程中執行的代碼編寫在run方法中 for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } } public class helloworld { public static void main(String[] args) { //4.創建上面自定義類的對象 MyRunnable mr = new MyRunnable(); //5.創建Thread對象並將上面自定義類的對象作為參數傳遞給Thread的構造方法 Thread t = new Thread(mr); //6.調用start方法啟動線程 t.start(); for(int i = 0; i < 1000; i++) { System.out.println("xx"); } } }
使用Callable接口創建的線程會獲得一個返回值並且可以聲明異常。
優點: 可以獲取返回值 可以拋出異常
線程池
線程池是初始化一個多線程應用程序過程中創建一個線程集合,然后在需要執行新的任務時直接去這個線程集合中獲取,而不是創建一個線程。任務執行結束后,線程回到池子中等待下一次的分配。
線程池的作用
解決創建單個線程耗費時間和資源的問題。
package com.wpbxx.test; 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; //1.自定義一個類實現java.util.concurrent包下的Callable接口 class MyCallable implements Callable<Integer>{ private int count; public MyCallable(int count) { this.count = count; } //2.重寫call方法 @Override public Integer call() throws Exception{ //3.將要在線程中執行的代碼編寫在call方法中 for(int i = 0; i < 100; i++) { count++; } return count; } } public class helloworld { public static void main(String[] args) { //4.創建ExecutorService線程池 里面為線程的數量 ExecutorService es = Executors.newFixedThreadPool(2); ////創建一個線程池,里面的線程會根據任務數量進行添加 //ExecutorService es = Executors.newCachedThreadPool(); //5.將自定義類的對象放入線程池里面 Future<Integer> f1= es.submit(new MyCallable(5)); Future<Integer> f2 = es.submit(new MyCallable(3)); try { //6.獲取線程的返回結果 System.out.println(f1.get()); System.out.println(f2.get()); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } //7.關閉線程池,不再接收新的線程,未執行完的線程不會被關閉 es.shutdown(); } }
繼承Thread
優點:可以直接使用Thread類中的方法,代碼簡單
缺點:繼承Thread類之后就不能繼承其他的類
實現Runnable接口
優點:即時自定義類已經有父類了也不受影響,因為可以實現多個接口
缺點: 在run方法內部需要獲取到當前線程的Thread對象后才能使用Thread中的方法
實現Callable接口
優點:可以獲取返回值,可以拋出異常
缺點:代碼編寫較為復雜
package com.wpbxx.test; 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 helloworld { public static void main(String[] args) throws InterruptedException, ExecutionException { new Thread() { public void run() { for(int i = 0; i < 1000; i++) { System.out.println("wpb"); } } }.start(); new Thread(new Runnable() { public void run() { for(int i = 0; i< 1000; i++) { System.out.println("xx"); } } }).start(); ExecutorService exec = Executors.newCachedThreadPool(); Future<Integer> result = exec.submit(new Callable<Integer>() { @Override public Integer call() throws Exception{ return 1024; } }); System.out.println(result.get()); } }
Thread設置線程的名字
方法一
new Thread("馬化騰") { //通過構造方法給name賦值 public void run() { System.out.println("我是" + this.getName() + ",來騰訊工作吧 "); } }.start();
方法二
new Thread() { public void run() { this.setName("馬化騰"); //調用setName System.out.println("我是" + this.getName() + ",來騰訊啊"); } }.start();
使用Thread.currentThread() 獲得正在運行的線程
可以這樣改變Runnable中線程名字
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { new Thread(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName()); Thread.currentThread().setName("wpb"); System.out.println(Thread.currentThread().getName()); } }).start(); } }
線程睡眠
Thread中的sleep方法可以使當前線程睡眠,線程睡眠后,里面的任務不會執行,待睡眠時間過后會自動蘇醒,從而繼續執行任務。
Thread.sleep(1000); //讓當前線程睡眠1秒
線程的優先級
setPriority()方法接收一個int類型的參數,通過這個參數可以指定線程的優先級,取值范圍是整數1~10,優先級隨着數字的增大而增強。 但並不是一定執行優先級高的執行完之后 才執行別的
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { Thread t1 = new Thread() { public void run() { for(int i = 0; i<100; i++) { System.out.println("wpb"); } } }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 100; i++) { System.out.println("1024"); } } }; t1.setPriority(10); t2.setPriority(0); t1.start(); t2.start(); } }
喚醒睡眠中的線程
t1.interrupt();
用interrupt方法會拋出一個InterruptedException的異常。
同步方法
package com.wpbxx.test; public class helloworld { public static void main(String[] args) { Task tk = new Task(); Thread t1 = new Thread() { public void run() { tk.changeNum(true); } }; Thread t2 = new Thread() { public void run() { tk.changeNum(false); } }; t1.start(); t2.start(); } } class Task{ private int num = 0; public void changeNum(boolean flag) { if(flag) { num = 99; System.out.println(Thread.currentThread().getName() + "-------" + "begin"); System.out.println(Thread.currentThread().getName() + "-------" + num); System.out.println(Thread.currentThread().getName() + "-------" + "end"); }else { num = 22; System.out.println(Thread.currentThread().getName() + "-------" + "begin"); System.out.println(Thread.currentThread().getName() + "-------" + num); System.out.println(Thread.currentThread().getName() + "-------" + "end"); } } }
正常情況下應該打印出一個88一個66,可是上面卻兩個線程打印出的兩個66,這樣就出現了線程安全的問題,出現這個問題的原因是成員變量存儲在堆內存中,兩個線程共享堆內存,即兩個線程可以對同一個num進行修改。
程序執行分析:
cpu執行t1線程,將num修改為88,之后cpu開始執行t2線程,將num修改為66,打印出66,cpu開始執行t1線程,打印num的值,此時num的值是66。
在方法上加入synchronized關鍵字,這樣在執行多個線程時看哪個線程先執行這個方法,假設有t1,t2,t3三個線程中都調用了changeNum方法,t1線程先執行了這個方法,那么t1會先在Task對象上面加鎖,加鎖后,別的線程就無法執行當前Task對象上的changeNum方法,直到t1執行結束changeNum方法之后,t2,t3中的一個線程才可以執行這個方法,這就保證了在某個時間段內只有一個線程執行changeNum方法,解決了線程安全問題。
注意:synchronized鎖住的是當前對象,如果t1線程和t2線程里面是不同的對象,則不需要同步,因為不會發生線程安全問題
public synchronized void changeNum(boolean flag) 加這一句就ok了
也可以對需要互斥訪問的代碼塊加上synchronized
public void changeNum(boolean flag){ try { Thread.sleep(3000); System.out.println("執行一個耗時較長的任務"); } catch (InterruptedException e) { e.printStackTrace(); } //這個方法中,需要同步的代碼塊是這部分,而上面耗時操作的代碼,不涉及到線程安全問題,所以不需要同步 synchronized(obj){ if(flag){ num = 88; System.out.println(Thread.currentThread().getName() + "========== begin"); System.out.println(Thread.currentThread().getName() + "==========" + num); System.out.println(Thread.currentThread().getName() + "========== end"); }else{ num = 66; System.out.println(Thread.currentThread().getName() + "========== begin"); System.out.println(Thread.currentThread().getName() + "==========" + num); System.out.println(Thread.currentThread().getName() + "========== end"); } } }
死鎖
發生死鎖原因就是兩個或多個線程都在等待對方釋放鎖導致,下面通過代碼來演示一下死鎖情況。
package com.wpbxx.test; public class helloworld { private static Object obj1 = new Object(); private static Object obj2 = new Object(); public static void main(String[] args) { new Thread() { public void run() { synchronized(obj1) { System.out.println(this.getName()); synchronized(obj2) { System.out.println(this.getName()); } } } }.start(); new Thread() { public void run() { synchronized(obj2) { System.out.println(this.getName()); synchronized(obj1) { System.out.println(this.getName()); } } } }.start(); } }
volatile關鍵字
package com.wpbxx.test; public class helloworld { public static void main(String[] args) throws InterruptedException { Task task = new Task(); Thread t1 = new Thread(task); t1.start(); Thread.sleep(100); task.setFlag(false); } } class Task implements Runnable{ private boolean flag = true; public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } public void run() { while(flag) { System.out.println("while循環"); } System.out.println("循環結束"); } }
上面程序中在64位的機器上以server模式運行時,有可能會出現死循環的現象。
JVM的運行可以分為下面兩種模式:
- client:啟動快,運行后性能不如server模式,一般運行時默認是client模式
- server:啟動慢,運行后性能比client模式好。
在eclipse中可以通過配置來使用server模式,右鍵—>run as—>run configurations。寫上-server。然后點擊run即可
上面程序出現問題的原因這樣的,雖然在主線程中將flag的設置為false,但是jvm為了提升效率,t1線程一直在私有內存中獲取flag的值,而私有內存中的flag值並沒有被改變,所以導致死循環的發生。
使用volatile修飾flag解決上面問題:
volatile private boolean flag = true;
將flag聲明為volatile后,t1線程會從公共的內存中訪問flag的值,這樣在主線程將flag設置為false后,t1線程中的循環就會結束了。
注意:volatile只能修飾變量,不能修飾方法
原子性和非原子性
原子性:即一個操作或者多個操作 要么全部執行並且執行的過程不會被任何因素打斷,要么就都不執行。
非原子性:不符合原子性的就是非原子性
int x = 1024; //原子性 int y = x; //cpu先去內存中讀取x的值,讀取后在為y進行賦值,在讀取后給y賦值前的這段時間可能會切換到其他線程上面。 x++; //包含了三個操作,先讀取x的值,然后進行加1操作,最后寫入新的值,在這三個操作的間隙可能會切換到其他線程上面。 x = x + 1; //同上
volatile是非原子性的。
synchronized是原子性的。
TimerTask
TimerTask是一個實現了Runnable接口的抽象類,需要編寫一個類繼承TimerTask類,將要在定時任務執行的代碼編寫在run方法中。
要想執行定時任務,需要創建Timer的對象並調用里面的schedule方法,在Timer類中有多個重載的schedule方法,這里咱們使用這個:
schedule(TimerTask task, Date firstTime, long period);
package com.wpbxx.test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; public class helloworld { public static void main(String[] args) throws InterruptedException, ParseException { Timer t = new Timer(); t.schedule(new MyTimerTask(), new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS").parse("2017-07-03 18:09:00 000"), 5000); } } class MyTimerTask extends TimerTask{ @Override public void run() { System.out.println("wpbxx"); } }
線程之間的通信
多線程環境下CPU會隨機的在線程之間進行切換,如果想讓兩個線程有規律的去執行,那就需要兩個線程之間進行通信,在Object類中的兩個方法wait和notify可以實現通信。
wait方法可以使當前線程進入到等待狀態,在沒有被喚醒的情況下,線程會一直保持等待狀態。
notify方法可以隨機喚醒單個在等待狀態下的線程。
利用wait 和notify 進行交替打印
package com.wpbxx.test; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Timer; import java.util.TimerTask; public class helloworld { public static void main(String[] args) throws InterruptedException, ParseException { Print p = new Print(); Thread t1 = new Thread() { public void run() { while(true) { p.print1(); } } }; Thread t2 = new Thread() { public void run() { while(true) { p.print2(); } } }; t1.start(); t2.start(); } } class Print{ private int flag = 1; public void print1() { synchronized(this) { if(flag != 1) { try { this.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("wpb"); flag = 2; this.notify(); } } public void print2() { synchronized(this) { if(flag != 2) { try { this.wait(); }catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("xx"); flag = 1; this.notify(); } } }
但這樣如果是三個線程以上的 就不行, 可能出現死鎖了
因為是隨機喚醒一個等待的線程, 假設一線程 進行玩后 隨即喚醒一個線程,並把flag = 2, 但這時喚醒了線程3 就會一直等待
notifyAll() 為喚醒所有的線程

package com.wpbxx.test; /** * 三個(三個以上)線程之間的通信 * */ public class helloworld { public static void main(String[] args) { Print1 p = new Print1(); Thread t1 = new Thread(){ public void run(){ while(true){ p.print1(); } } }; Thread t2 = new Thread(){ public void run(){ while(true){ p.print2(); } } }; Thread t3 = new Thread(){ public void run(){ while(true){ p.print3(); } } }; t1.start(); t2.start(); t3.start(); } } class Print1{ private int flag = 1; public void print1(){ synchronized(this){ while(flag != 1){ try { //讓當前線程進入等待狀態 this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("monkey"); flag = 2; //喚醒所有等待的線程 this.notifyAll(); } } public void print2(){ synchronized(this){ while(flag != 2){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("1024"); flag = 3; this.notifyAll(); } } public void print3(){ synchronized(this){ while(flag != 3){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("888"); flag = 1; this.notifyAll(); } } }
這樣就可以實現三個線程的交替打印, 但會有問題 就是喚醒所有的線程 開銷太大。
上面notify() 或者 notifyAll() 並不能喚醒指定的線程,所以多出了 互斥鎖
新增了 ReenTrantLock類 和 Condition接口 來替換 synchronized關鍵字 和 wait、notify 方法。
ReenTrantLock類 和Condition接口 都在java.util.concurrent.locks包下。
可以使用 ReentrantLock類中 的 lock方法 和 unlock方法 進行上鎖和解鎖,用來替代synchronized關鍵字。
Condition接口中的await方法和signal方法用來讓線程等待和喚醒指定線程。用來替代wait方法和notify方法。
如 還是循環打印東西
package com.wpbxx.test; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class helloworld { public static void main(String[] args) throws InterruptedException { Print p = new Print(); Thread t1 = new Thread() { public void run() { while(true) { p.print1(); } } }; Thread t2 = new Thread() { public void run() { while(true) { p.print2(); } } }; Thread t3 = new Thread() { public void run() { while(true) { p.print3(); } } }; t1.start(); t2.start(); t3.start(); } } class Print{ private ReentrantLock r = new ReentrantLock(); private Condition c1 = r.newCondition(); private Condition c2 = r.newCondition(); private Condition c3 = r.newCondition(); private int flag = 1; public void print1() { r.lock(); while(flag != 1) { try { c1.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb1"); flag = 2; c2.signal(); r.unlock(); } public void print2() { r.lock(); while(flag != 2) { try { c2.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb2"); flag = 3; c3.signal(); r.unlock(); } public void print3() { r.lock(); while(flag != 3) { try { c3.await(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("wpb3"); flag = 1; c1.signal(); r.unlock(); } }
以后再補充