10,7學習記錄(多執行緒)
-
建立多執行緒的方式一:繼承Thread類
-
自定義執行緒類繼承Thread類
-
重寫run()方法,編寫執行緒執行體
-
-
-
建立多執行緒的方式二:實現Runable介面
-
建立一個實現了Runnable介面的類
-
實現類去實現Runnable中的抽象方法:run()
-
建立實現類的物件
-
將此物件作為引數傳遞到Thread類的構造器中,建立Thread類的物件
-
通過Thread類的物件呼叫start()
-
-
建立多執行緒的方式三:實現Callable介面
-
建立一個實現Callable的實現類
-
實現call方法,將此執行緒需要執行的操作宣告在call()方法中
-
建立Callable介面實現類的物件
-
將此Callable介面實現類的物件作為傳遞到FutureTask構造器中,建立FutureTask的物件
-
將FutureTask的物件作為引數傳遞到Thread類的構造器中,建立Thread物件,並呼叫start()
-
獲取Callable中call方s法的返回值
-
-
建立多執行緒的方式四:實現Callable介面
-
提供指定執行緒數量的執行緒池
-
執行指定的執行緒的操作。需要提供實現Runnable介面或Callable介面實現類的物件
-
關閉連線池
-
-
注意:執行緒開啟不一定立即執行,由CPU排程執行
-
注意:若要再啟動一個執行緒,不可以還讓已經start()的執行緒去執行。(會報ILLeaglThreadStateException異常) 我們需要重新建立一個執行緒的物件呼叫start()
-
建立多執行緒的方式一:繼承Thread類
public class ThreadTeat extends Thread{
-
建立多執行緒的方式二:實現Runable介面
public class ThreadTest {
public static void main(String[] args) {
//建立實現類的物件
MyThread myThread = new MyThread();
//將此物件作為引數傳遞到Thread類的構造器中,建立Thread類的物件
Thread t1 = new Thread(myThread);
//通過Thread類的物件呼叫start() 1、啟動執行緒 2、呼叫當前執行緒的run()--> 呼叫了Runnable型別的target的run()
t1.start();
//再啟動一個執行緒,遍歷100以內的偶數
Thread t2=new Thread(myThread);
t2.start();
}
}
//建立一個實現了Runnable介面的類
class MyThread implements Runnable{
//實現類去實現Runnable中的抽象方法:run()
-
例項1:建立三個視窗賣票,總票數為100張,使用繼承類方法實現
//建立三個視窗賣票,總票數為100張,使用繼承類方法實現
//目前存線上程的安全問題 (待解決)
public class WindowsTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Ticket ticket1 = new Ticket();
Ticket ticket2 = new Ticket();
ticket.setName("視窗1");
ticket1.setName("視窗2");
ticket2.setName("視窗3");
ticket.start();
ticket1.start();
ticket2.start();
}
}
class Ticket extends Thread{
private static int ticket=100;
-
例項2:建立三個視窗賣票,總票數為100張,使用實現Runnable介面的方式
//建立三個視窗賣票,總票數為100張,使用實現Runnable介面的方式
//目前存線上程的安全問題 (待解決)
public class WindowsTestRunnable {
public static void main(String[] args) {
windows w1 = new windows();
Thread t1 = new Thread(w1);
Thread t2=new Thread(w1);
Thread t3=new Thread(w1);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
class windows implements Runnable{
private int ticket=100;
-
練習一:建立兩個分執行緒,其中一個執行緒遍歷100以內的偶數,另外一個遍歷100以內的奇數
public class ThreadDemo1 {
public static void main(String[] args) {
Mythread1 mythread1 = new Mythread1();
Mythread2 mythread2 = new Mythread2();
mythread1.start();
mythread2.start();
}
}
class Mythread1 extends Thread{
-
練習二:建立兩個分執行緒,其中一個執行緒遍歷100以內的偶數,另外一個遍歷100以內的奇數(利用Thread類的匿名子類的方式)
public class ThreadDemo2 {
public static void main(String[] args) {
new Thread(){
比較建立執行緒的兩種方式
開發中:優先選擇:實現Runnable介面的方式
原因:1.實現的方式沒有類的單繼承性的侷限性
2.實現的方式更適合來處理多個執行緒有共享資料的情況
聯絡:public class Thread implements Runnable
相同點:兩種方式都需要重寫run(),將執行緒要執行的邏輯宣告再run()中。
目前兩種方式,要想啟動執行緒,都是呼叫的Thread類中的start()。
測試Thread中的常用方法:
-
start():啟動當前執行緒;呼叫當前執行緒的run();
-
run():通常需要重寫Thread類中的此方法,將建立的執行緒要執行的操作宣告在此方法中
-
currentThread():靜態方法,返回執行當前程式碼的執行緒
-
getName():獲取當前執行緒的名字
-
setName():設定當前執行緒的名字
-
yield():釋放當前cpu的執行權
-
join():線上程a中呼叫執行緒b的join(),此時執行緒a就進入阻塞狀態,直到執行緒完全執行完以後,執行緒a才結束阻塞狀態。
-
stop():已過時。當執行此方法時,強制結束當前執行緒。
-
sleep(long millitime):讓當前執行緒“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前執行緒是阻塞狀態。
-
isAlive():判斷當前執行緒是否存活
執行緒的優先順序
-
MAX_PRIORITY:10
-
MIN_PRIORITY:1
-
NORM_PRIORITY:5
如何獲取和設定當前執行緒的優先順序
-
getPriority():獲取執行緒的優先順序
-
setPriority(int p):設定執行緒的優先順序
執行緒的生命週期
處理實現Runnable的執行緒安全問題(以賣車票的例子舉例)
-
問題:賣票的過程中,出現了重票、錯票 -->出現了執行緒的安全問題
-
問題出現的原因:當某個執行緒操作車票的過程中,尚未完成操作時,其他執行緒參與進來,也操作車票
-
如何解決:當一個執行緒a在操作ticket的時候,其他執行緒不能參與進來。直到執行緒a操作完ticket時,執行緒才可以開始操作ticket。這種情況即使a出現了阻塞,也不能被改變。
-
在Java中,我們通過同步機制,來解決執行緒的安全問題。
-
同步的方式,解決了執行緒的安全問題。------優點
操作同步程式碼時,只能有一個執行緒參與,其他執行緒等待。相當於時一個單執行緒的過程,效率低。-------侷限性
方法一:同步程式碼塊
synchronized(同步監視器){
//需要被同步的程式碼
}
public class WindowsTestRunnable {
public static void main(String[] args) {
windows w1 = new windows();
Thread t1 = new Thread(w1);
Thread t2=new Thread(w1);
Thread t3=new Thread(w1);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
class windows implements Runnable{
private int ticket=100;
Object oj=new Object();
說明:
-
操作共享資料的程式碼,即為需要被同步的程式碼 ----->不能包多也不能包少
-
共享資料:多個執行緒共同操作的變數。比如ticket就是共享資料。
-
同步監視器,俗稱:鎖。任何一個類的物件,都可以充當鎖。
要求:多個執行緒必須要共用一把鎖
補充:在實現Runnable介面建立多執行緒的方式中,我們可以考慮使用this充當同步監視器
方法二:同步方法
public class WindowsTestRunnablemethod { public static void main(String[] args) { Windows2 w2 = new Windows2(); Thread t1 = new Thread(w2); Thread t2 = new Thread(w2); Thread t3 = new Thread(w2); t1.setName("視窗1"); t2.setName("視窗2"); t3.setName("視窗3"); t1.start(); t2.start(); t3.start(); } } class Windows2 implements Runnable{ private int ticket=100; @Override public void run() { while(true){ show(); if (ticket==0) break; } } private synchronized void show(){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 賣票,票號為:"+ticket); ticket--; } } }
關於同步方法的總結:
-
同步方法仍然涉及到同步監視器,只要是不需要我們顯示的宣告。
-
非靜態的同步方法,同步監視器是:this
靜態的同步方法,同步監視器是:當前類本身
解決繼承Thread類的方式的執行緒安全問題(以賣車票的例子舉例)
例子:建立三個視窗賣票,總票數為100張,使用繼承Thread類的方式
方法一:同步程式碼塊
public class WindowsTestExtends { public static void main(String[] args) { Ticket ticket = new Ticket(); Ticket ticket1 = new Ticket(); Ticket ticket2 = new Ticket(); ticket.setName("視窗1"); ticket1.setName("視窗2"); ticket2.setName("視窗3"); ticket.start(); ticket1.start(); ticket2.start(); } } class Ticket extends Thread{ private static int ticket=100; private static Object obj=new Object(); @Override public void run() { while (true){ // synchronized(Ticket.class) { synchronized(obj) { if (ticket > 0) { System.out.println(getName() + ": 賣票,票號為:" + ticket); ticket--; } else { break; } } } } }
說明:
-
操作共享資料的程式碼,即為需要被同步的程式碼----->不能包多也不能包少
-
共享資料:多個執行緒共同操作的變數。比如ticket就是共享資料。
-
同步監視器,俗稱:鎖。任何一個類的物件,都可以充當鎖。
要求:多個執行緒必須要共用一把鎖
補充:在繼承Thread類建立多執行緒的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器
方法二:同步方法
public class WindowsTestExtendsmethod { public static void main(String[] args) { Windows1 w1 = new Windows1(); Windows1 w2 = new Windows1(); Windows1 w3 = new Windows1(); w1.setName("視窗1"); w2.setName("視窗2"); w3.setName("視窗3"); w1.start(); w2.start(); w3.start(); } } class Windows1 extends Thread{ private static int ticket=100; @Override public void run() { while(true){ show(); if (ticket==0) break; } } private static synchronized void show(){//同步監視器:Windows1.class if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": 賣票,票號為:"+ticket); ticket--; } } }
解決執行緒安全問題的方式三:Lock鎖 --- JDK5.0新增
步驟:
-
例項化ReentrantLock
-
呼叫鎖定方法lock()
-
呼叫解鎖方法unlock()
public class WindowsTestLockmethod { public static void main(String[] args) { window w1 = new window(); Thread t1 = new Thread(w1); Thread t2 = new Thread(w1); Thread t3 = new Thread(w1); t1.setName("視窗1"); t2.setName("視窗2"); t3.setName("視窗3"); t1.start(); t2.start(); t3.start(); } } class window implements Runnable{ private int ticket=100; //1.例項化ReentrantLock private ReentrantLock lock=new ReentrantLock(true); @Override public void run() { while(true){ try{ //2.呼叫鎖定方法Lock() lock.lock(); if (ticket>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+":賣票! 票號為"+ticket); ticket--; } else { break; } }finally { // 3.呼叫解鎖方法:unlock() lock.unlock(); } } } }
synchronized與lock的異同
相同:二者都可以解決執行緒的安全問題
不同:synchronized機制在執行完相應的同步程式碼以後,自動的釋放同步監視器。
lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
優先使用順序:
lock -->同步程式碼塊(已經進入了方法體,分配了相應資源)-->同步方法(方法體之外)
執行緒通訊的三個方法
-
wait():一旦執行此方法,當前執行緒就進入阻塞狀態,並釋放同步監視器
-
notify():一旦執行此方法,就會喚醒被wait的一個執行緒。如果有多個執行緒被wait,就喚醒優先順序高的那個
-
notifyAll():一旦執行此方法,就會喚醒所有被wait的執行緒
說明:
-
wait(),notify(),notifyAll()三個方法必須使用在同步程式碼塊或同步方法中。
-
wait(),notify(),notifyAll()三個方法的呼叫者必須是同步程式碼塊或同步方法中的同步監視器。 否則會出現IllegalMonitorStateException異常
-
wait(),notify(),notifyAll()三個方法是定義在java.long.Object類中。
***sleep()和wait()的異同
-
相同的:一旦執行方法,都可以使得當前的執行緒進入阻塞狀態。
-
不同點:1).兩個方法宣告的位置不同:Thread類中宣告sleep(),Object類中宣告wait()
2).呼叫的要求不同:sleep()可以在任何需要的場景下呼叫。wait()必須使用在同步程式碼塊或同步方法中
3).關於是否釋放同步監視器:如果兩個方法都使用在同步程式碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
執行緒通訊的應用經典例題:生產者/消費者問題
//執行緒通訊的應用:經典例題:生產者/消費者問題 /** * 生產者(Productor)將產品交給店員(clerk),而消費者(Customer)從店員處取走產品, * 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員 * 會叫生產者停一下,如果店中有空位放產品了再通知生產者繼續生產;如果店中沒有產品了, * 店員會告訴消費者等一下,如果店中有了產品了再通知消費者來取走產品 */ public class ProductTest { public static void main(String[] args) { clerk clerk=new clerk(); Productor p1 = new Productor(clerk); Consumer c1 = new Consumer(clerk); p1.setName("生產者1"); c1.setName("消費者1"); p1.start(); c1.start(); } } class clerk{ private int produceCount=0; public synchronized void productormethod() {//生產產品 if (produceCount<20) { produceCount++; System.out.println(Thread.currentThread().getName()+"開始生產第"+produceCount+"個產品"); notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consumermethod() {//消費產品 if (produceCount>0) { System.out.println(Thread.currentThread().getName()+"開始消費第"+produceCount+"個產品"); produceCount--; notify(); }else{ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Productor extends Thread{//生產者 private clerk cle; public Productor(clerk cle) { this.cle = cle; } @Override public void run() { System.out.println(getName()+":開始生產產品。。。。"); while(true){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } cle.productormethod(); } } } class Consumer extends Thread{//消費者 private clerk cle; public Consumer(clerk cle) { this.cle = cle; } @Override public void run() { System.out.println(getName()+":開始消費產品。。。。"); while(true){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } cle.consumermethod(); } } }
JDK5.0新增執行緒建立方式一:實現Callable介面
-
與使用Runnable相比,Callable功能更加強大些
-
相比run()方法,可以有返回值
-
方法可以丟擲異常
-
支援泛型的返回值
-
需要藉助FutureTask類,比如獲取返回結果
-
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /* 建立執行緒的方式三:實現Callable介面。------JDK 5.0新增 如何理解實現Callable介面的方式建立多執行緒比實現Runnable介面建立多執行緒方式強大? 1、call()方法可以有返回值 2、call()方法可以丟擲異常,被外面的操作捕獲,獲取異常的資訊 3、Callable是支援泛型 */ //1、建立一個實現Callable的實現類 class NumThread implements Callable{ //2、實現call方法,將此執行緒需要執行的操作宣告在call()方法中 @Override public Object call() throws Exception { int sum=0; for (int i = 0; i <=100 ; i+=2) { System.out.println(i); sum+=i; } return sum; } } public class ThreadNew { public static void main(String[] args) { //3、建立Callable介面實現類的物件 NumThread numThread = new NumThread(); //4、將此Callable介面實現類的物件作為傳遞到FutureTask構造器中,建立FutureTask的物件 FutureTask futureTask = new FutureTask(numThread); //5、將FutureTask的物件作為引數傳遞到Thread類的構造器中,建立Thread物件,並呼叫start() new Thread(futureTask).start(); try { //6、獲取Callable中call方法的返回值 //get()返回值即為FutureTask構造器引數Callable實現類重寫的call()的返回值。 Object sum= futureTask.get(); System.out.println("總和為:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
JDK5.0新增執行緒建立方式一:使用執行緒池
-
背景:經常建立和銷燬、使用量特別大的資源,比如併發情況下的執行緒,對效能影響很大。
-
思路:提前建立好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中。可以避免頻繁建立銷燬、實現重複利用。類似生活中的公共交通工具。
-
好處:
-
提高了響應速度(減少了建立新執行緒的實踐)
-
降低資源消耗(重複利用執行緒池執行緒,不需要每次都建立)
-
便於執行緒的管理
-
corePoolSize:核心池的大小
-
maximumPoolSize:最大執行緒數
-
KeepAliveTIme:執行緒沒任務時最多保持多長時間會終止
-
-
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i <=100; i++) { if(i%2==0) System.out.println(Thread.currentThread().getName()+":"+i); } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i <=100; i++) { if(i%2!=0) System.out.println(Thread.currentThread().getName()+":"+i); } } } public class ThreadPool { public static void main(String[] args) { //1.提供指定執行緒數量的執行緒池 ExecutorService service = Executors.newFixedThreadPool(10); //2.執行指定的執行緒的操作。需要提供實現Runnable介面或Callable介面實現類的物件 service.execute(new NumberThread()); //適用於Runnable service.execute(new NumberThread1()); //適用於Runnable // service.submit(); //適用於Callable // 3.關閉連線池 service.shutdown(); } }