最近發現,編程這東西,一段時間不用,就差不多忘了,感覺腦子永遠不夠用,這下利用點時間整理下思路,記錄下來,已被不時之需。
線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之為多線程程序。簡而言之:一個程序運行后至少有一個進程,一個進程中可以包含多個線程。什么是多線程呢?即就是一個程序中有多個線程在同時執行。
單線程程序:即,若有多個任務只能依次執行。當上一個任務執行結束后,下一個任務開始執行。如,去網吧上網,網吧只能讓一個人上網,當這個人下機后,下一個人才能上網。
多線程程序:即,若有多個任務可以同時執行。如,去網吧上網,網吧能夠讓多個人同時上網。
程序運行原理
分時調度
所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間。
搶占式調度
優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個(線程隨機性),Java使用的為搶占式調度。
大部分操作系統都支持多進程並發運行,現在的操作系統幾乎都支持同時運行多個程序。比如:現在我們上課一邊使用編輯器,一邊使用錄屏軟件,同時還開着畫圖板,dos窗口等軟件。此時,這些程序是在同時運行,”感覺這些軟件好像在同一時刻運行着“。
實際上,CPU(中央處理器)使用搶占式調度模式在多個線程間進行着高速的切換。對於CPU的一個核而言,某個時刻,只能執行一個線程,而 CPU的在多個線程間切換速度相對我們的感覺要快,看上去就是在同一時刻運行。
其實,多線程程序並不能提高程序的運行速度,但能夠提高程序運行效率,讓CPU的使用率更高。
線程安全
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
/** * * @author LYJ * 實現Runnable的代碼 * */ public class Ticket implements Runnable { //設置總票數為100,這里的ticket是成員變量, //由於在測試類中new了一次,所以值存在一個,被三個售票窗口共享 int ticket=100; public void run() { //模擬售票 while(true) { //如果票數大於0,繼續售票 if(ticket>0) { //為了讓線程安全問題效果明顯些,加入線程定時休眠Thread.sleep() try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Thread.currentThread()是線程獲取當前線程對象的方法 getName()獲取調用者的線程名 System.out.println(Thread.currentThread().getName()+"正在售票:"+ticket--); } } } /** * * 開啟多線程的代碼 * */ public class ThreadDemo01 { public static void main(String[] args) { //創建Ticket的Runnable對象 Ticket ticket = new Ticket(); //創建線程3個對象模擬三個售票窗口,並把Runnable對象加入Thread和給Thread命名 new Thread(ticket,"窗口1").start();; new Thread(ticket,"窗口2").start();; new Thread(ticket,"窗口3").start();; }
*****************************************************************
輸出結果:
窗口3正在售票:3
窗口2正在售票:2
窗口1正在售票:1
窗口3正在售票:0
窗口2正在售票:-1
結果中出現了負數和0,這就是線程安全問題,要怎么解決呢?
加同步鎖 synchronized(Object o){....} o可以是任意對象
******************************************************************************
加入同步鎖后的代碼
public class Ticket implements Runnable {
//設置總票數為100,這里的ticket是成員變量,
//由於在測試類中new了一次,所以值存在一個,被三個售票窗口共享
int ticket=100;
public void run() {
//模擬售票
while(true) {
//如果票數大於0,繼續售票
//加入同步鎖
synchronized(this) {
if(ticket>0) {
//為了讓線程安全問題效果明顯些,加入線程定時休眠Thread.sleep()
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Thread.currentThread()是線程獲取當前線程對象的方法 getName()獲取調用者的線程名
System.out.println(Thread.currentThread().getName()+"正在售票:"+ticket--);
}
}
}
}
**********************************************
運行幾次,發現運行結果中沒有出現負數和0
同步方法:在方法聲明上加上synchronized
public synchronized void method(){
可能會產生線程安全問題的代碼
}
同步方法中的鎖對象是 this(即調用者對象)
靜態同步方法: 在方法聲明上加上static synchronized
public static synchronized void method(){
可能會產生線程安全問題的代碼
}
靜態同步方法中的鎖對象是 類名.class(因為在加載類文件的時候,靜態同步方法由於是靜態的也被加載進內存了,類名.class的加載優先級高於靜態方法)
同步代碼塊:在需要同步的代碼外面包上一個synchronized
(Object o){
可能會產生線程安全問題的代碼
}
同步代碼塊中的所對象可以是任意對象
死鎖
同步鎖使用的弊端:當線程任務中出現了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發一種現象:程序出現無限等待,這種現象我們稱為死鎖。這種情況能避免就避免掉。
synchronzied(A鎖){
synchronized(B鎖){
}
}
/** * *創建鎖對象 * */ public class Lock { //這里用private封裝,為了不讓外面隨便造鎖,限制只能有A,B鎖個一把,這樣容易出現死鎖 //即A同學和B同學想相互串門,可是沒人只有一把自己房間的鑰匙,而且各自都不願意先給,於是死鎖 private Lock() {}; public static final Object lockA =new Object(); public static final Object lockB = new Object(); //這里使用static 為了讓外界可以通過類名調用成員變量lockA和lockB //因為外面無法創建Lock對象,為了讓外面在不創對象的情況下調用,加了static,通過類名加變量名訪問 } /** * 線程任務類 * */ import java.util.Random; public class ThreadTask implements Runnable { int x = new Random().nextInt(1);//用隨機數隨機獲取0、1,來模擬CPU隨機分配執行權的行為 @Override public void run() { while(true) { if(x%2==0) { //情況一 // 先執行A再執行B:即A同學先拿了A門的鑰匙去開A門,然后打算開B門 synchronized(Lock.lockA) { System.out.println("A同學...開A門"); synchronized(Lock.lockB) { System.out.println("A同學...開B門"); } } }else { //情況二 // 先執行B執行A:B同學先拿了B門的鑰匙,去開B門,然后打算開A門 synchronized(Lock.lockB) { System.out.println("B同學...開B門"); synchronized(Lock.lockA) { System.out.println("B同學...開A門"); } } } x++; } } /** * * 線程測試類 * */ public class ThreadDemo { public static void main(String[] args) { //創建Runnable的實現類對象 ThreadTask tt = new ThreadTask(); //把Runnable實現類對象加入線程中,創建2個線程 Thread t1 = new Thread(tt); Thread t2 = new Thread(tt); t1.start(); t2.start(); }
*********************************************************
輸出結果:A同學...開A門
A同學...開B門
B同學...開B門
B同學...開A門
A同學...開A門
B同學...開B門
結論:A同學或者B同學,一個人先后拿走兩把鑰匙時,線程是正常運行的,一旦A拿了A鎖進去A門的時候,CPU突然讓B開始執行,讓B拿了B鎖進入B門,結果A需要B鎖,B也需要A鎖,兩者又不能后退
於是死鎖現象發生了。
等待喚醒機制
線程之間的通信:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。通過一定的手段使各個線程能有效的利用資源。而這種手段即等待喚醒機制
