1 多線程
1.1 進程:
進程就是一個正在執行中的程序。
每一個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。
1.2 線程:
線程就是進程中的一個獨立的控制單元。
線程在控制着進程的執行,一個進程中至少有一個線程。
1.3 主線程:
Java虛擬機(JVM)啓動的時候會有一個進程java.exe,該進程中至少有一個線程負責java程序的執行,而且這個線程運行的代碼存在於main方法中,該線程稱之爲主線程。
其實JVM啓動不止一個線程,還有負責垃圾回收機制的線程。
1.4 創建線程:
通過對API的查找,Java已經提供了對線程這類事物的描述,就是Thread類。
創建線程的第一種方式:繼承Thread類,覆run()方法。
步驟:1,自定義類,並繼承Thread類。
2、重寫Thread類中的run()方法。
目的:將定義的代碼存儲在run()方法中,讓線程運行。
3、調用線程的start()方法。
start()方法有兩個作用:啓動線程,調用run()方法。
代碼示例如下:
class Demo extends Thread { //要創建線程必須繼承Thread類
public void run(){
for(int x=0;x<120;x++)
System.out.println("Demo run--"+x);
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo(); //創建好一個線程
d.start(); //啓動線程,並調用run()方法。
//d.run(); //這樣,僅僅是對象調用方法,而線程創建了,並沒有運行。
for(int x=0;x<120;x++)
System.out.println("Hello World--"+x);
}
}
發現運行結果每一次都不同。
因爲多個線程都獲取CPU執行權,CPU執行到誰,誰就運行。Demo線程和主線程爭奪CPU。
明確一點,在某一個時刻,只能有一個程序在運行(多核除外)。
CPU在做着快速切換,以達到看上去是同時運行的效果。
我們可以形象的把多線程的運行形容爲在互相搶奪CPU的執行權(CPU資源);
這就是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長時間,CPU說的算。
爲什麼要覆蓋run()方法呢?
Thread類用於描述線程;該類就定義了一個功能,用於存儲線程要運行的代碼,該存儲功能就是run()方法。也就是說Thread類中的run()方法,用於存儲線程要執行的代碼。
線程都有自己默認的名稱:
Thread-編號,該編號從0開始。
currentThread(); 該方法獲取當前線程對象;
getName(); 該方法獲取線程名稱
設置線程名稱:serName()或者構造函數。
1.5 簡單的賣票程序:
需求:簡單的賣票程序,多個窗口同時賣票。
創建線程的第二種方式:實現Runnable接口。
步驟:1、定義類實現Runnable接口。
2、覆蓋Runnable接口中的run()方法。
將線程要運行的代碼存放在該run()方法中。
3、通過Thread類建立線程對象。
4、將Runnable接口的子類對象作爲實際參數,傳遞給Thread類的構造函數。
爲什麼要將Runnable接口的子類對象傳遞給Thread的構造函數:
因爲,自定義的run()方法所屬的對象是Runnable接口的子類對象。
所以要讓線程去執行指定對象的run()方法,就必須明確該run()方法所屬的對象。
5、調用Thread類的start()方法,啓動線程,並調用Runnable接口子類的run方法。
第一種和第二種,即實現Runnable接口方式和繼承Thread類方式有什麼區別?
實現方式的好處:避免了單繼承的侷限性。
在定義線程時,建議使用實現方式。
兩種方式的區別:
繼承Thread類:線程代碼存放在Thread子類的run()方法中。
實現Runnable接口:線程代碼存放在接口的子類的run()方法中。
多線程的安全問題:
問題的原因:當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,這時另一個線程參與進來執行,導致了共享數據的錯誤。
解決辦法:對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
Java對於多線程的安全問題提供了專業的解決方式:同步代碼塊和同步函數。
同步代碼塊:
synchronized(對象)
{
需要被同步的代碼; //即操作共享數據的代碼,是需要被同步的代碼
}
對象如同鎖,持有鎖的線程可以在同步中執行。
沒有持有鎖的線程即使獲取CPU的執行權,也進不去,因爲沒有獲取鎖。
同步的前提:
1、必須要有兩個或兩個以上的線程。鎖住操作共享數據的代碼。
2、必須是多個線程使用同一個鎖(即同一個對象),必須保證同步中只能有一個線程在運行。
好處:解決了多線程的安全問題
弊端:多個線程都需要判斷鎖,較爲消耗資源。
賣票程序代碼:
class Ticket implements Runnable {//extends Thread {
private int tick = 100;
Object obj = new Object(); //解決安全問題
public void run() {
while(true) {
synchronized(obj){ //同步代碼塊,解決安全問題
if(tick>0) {
try{Thread.sleep(10);}catch(Exception e){} //sleep()拋出一個異常。出現-1、-2,出現安全問題
System.out.println(Thread.currentThread().getName()+"sale: "+tick--);
}
}
}
}
}
class ThreadDemo2 {
public static void main(String[] args){
Ticket t = new Ticket(); //t不是線程,因爲與Thread類無關
Thread t1 = new Thread(t); //創建一個線程,並把Runnable子類對象傳遞給Thread構造函數
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println("---main---");
}
}
1.6 同步函數:
把synchronized作爲修飾符放在函數聲明中,此函數就具有同步功能(相當於同步代碼塊)。
這個被synchronized修飾的函數就是同步函數。
同步函數用的哪一個鎖呢?
函數需要被對象調用,那麼函數都有一個所屬對象的引用,就是this。
所以同步函數使用的同步鎖是this。
牢記同步的兩個前提,如果加同步後還有問題,就查看是否滿足同步到前提。
靜態同步函數:
如果同步函數被靜態修飾後,使用的鎖是什麼呢?
通過驗證,發現不再是this,因爲靜態方法中也不可以定義this。
靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。getClass()方法
類名.class 該對象的類型是class
靜態的同步方法,使用的同步鎖是該方法所在類的字節碼文件對象:類名.class
需求:銀行有一個金庫,有兩個儲戶分別存300元,每次存100,存三次。
目的:該程序是否有安全問題,如果有,如何解決?
如何找問題(需要同步的代碼怎麼找):
1、明確哪些代碼是多線程運行代碼。
2、明確共享數據。
3、明確多線程運行代碼中哪些語句是操作共享數據的。
代碼:
class Bank {
private int sum;
//Object obj = new Object();
public synchronized void add(int n){ //用synchronized修飾,add爲同步函數
//synchronized(obj){
sum = sum + n;
try{Thread.sleep(10);} catch(Exception e){}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable {
private Bank b = new Bank();
public void run() {
for(int x=0; x<3; x++) {
b.add(100);
}
}
}
class ThreadDemo3 {
public static void main(String[] args) {
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
2 單例設計模式
設計模式:解決某一類問題最行之有效的方法。
單例設計模式:一個類在內存只存在一個對象,想要保證對象唯一。
1、爲了避免其他程序過多建立該類對象,先禁止其他程序建立該類對象
2、還爲了讓其他程序可以訪問到該類對象,只好在本類中,自定義一個對象。
3、爲了方便其他程序對自定義對象的訪問,可以對外提供一些訪問方式。
這三部分怎麼用代碼體現呢?
1、將構造函數私有化
2、在類中創建一個本類對象
3、提供一個方法可以獲取到該對象。
對於事物該怎麼描述,還怎麼描述。
當需要將該事物的對象保證在內存中唯一時,就將以上的三步加上即可。
2.1 餓漢式 :
餓漢式:先初始化對象。
Single類一進內存,就已經創建好了對象。
class Single {
private Single(){}
private static final Single s = new Single();
public static Single getInstance() {
return s;
}
}
2.2 懶漢式
懶漢式:對象是方法被調用時,才初始化,也叫做對象的延時加載。
Single類進內存,對象還沒有存在,只有調用了getInstance()方法時,才建立對象。
class Single {
private Single(){}
private static Single s = null;
public static Single getInstance() {
if(s==null)
s = new Single();
return s;
}
}
代碼示例:
class Single { //懶漢式延遲加載,會出現多線程安全問題,用同步鎖解決
private Single(){}
private static Single s = null;
public static Single getInstance() { //靜態函數中,同步鎖使用本類字節碼
if(s==null) {
synchronized(Single.class) { //同步鎖是該類所屬的字節碼文件對象
if(s==null) //同步會降低執行效率
s = new Single();
}
}
return s;
}
}
class SingleDemo {
public static void main(String[] args){
System.out.println(Single.getInstance().getClass());
}
}
3 死鎖
死鎖:同步中嵌套同步,會發生死鎖。
比如,A鎖的同步代碼中需要B鎖,而B鎖的同步代碼中需要A鎖,
就會產生衝突,發生死鎖。
代碼示例:
class Ticket implements Runnable {
private int tick = 1000;
Object obj = new Object();
boolean flag = true;
public void run() {
if(flag) {
while(true) {
synchronized(obj) { //obj鎖
show(); //需要this鎖,而此時this鎖被使用
} //同步中嵌套同步,出現死鎖
}
}
else
while(true)
show();
}
public synchronized void show() { //this鎖
synchronized(obj) { //需要obj鎖,而此時obj鎖被使用
if(tick > 0) {
try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
}
}
}
}
class DeadLockDemo {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{Thread.sleep(10);}catch(Exception e){}
t.flag = false;
t2.start();
}
}
小練習:寫一個死鎖程序。
提示:多線程,同步鎖,死鎖。
class Test implements Runnable {
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run() {
if(flag){
synchronized(MyLock.locka){ //使用a鎖
System.out.println("if locka");
synchronized(MyLock.lockb){ //需要b鎖,而此時b鎖在被使用
System.out.println("if lockb");
}
}
}
else{
synchronized(MyLock.lockb){ //使用b鎖
System.out.println("else lockb");
synchronized(MyLock.locka){ //需要a鎖,而此時a鎖在被使用
System.out.println("else locka");
}
}
}
}
}
class MyLock {
static Object locka = new Object();
static Object lockb = new Object();
}
class DeadLockTest{
public static void main(String[] args){
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}