【JavaSE筆記】多線程(一)進程&線程&Thread&同步代碼塊

本期知識點:
進程
線程
Thread
同步代碼塊


1.進程:

a.線程依賴於進程而存在

b.進程:

就是正在運行的程序
進程是系統進行資源分配和調用的獨立單位,每一個進程都有自己的內存空間和系統資源

c.多進程的意義:

在一段時間內執行多個任務,並且提高CPU的使用率
例如:我們一邊玩遊戲一邊聽音樂是同時進行的嗎?
不是,單核CPU在某一個時間上只能做一件事情,而在玩遊戲或者聽歌的時候,CPU在做程序間高效的切換讓人覺得是同時進行。

2.線程:

a.概述:

在同一個進程內又可以執行多個任務,而每一個任務,可以i看作是一個線程


線程:是程序的執行單元,執行路徑。是程序使用CPU的最基本單位
單線程:程序只有一條執行路徑
多線程:程序有多條執行路徑


b.多線程的意義:

多線程的存在,不是爲了提高程序的執行速度,其實是爲了提高應用程序的使用率。
程序的執行其實是在搶CPU的資源、CPU的執行權。 多個程序是在搶資源,如果一個進程的執行路徑比較多,就有更高的機率搶到CPU的執行權。
線程執行具有隨機性。


c.並行和併發的區別:

並行:邏輯上同時發生,指在某一個時間同時運行多個程序。
併發:物理上同時發生,指在某一個時間同時運行多個程序。


d.JVM程序的運行原理:

由java命令啓動JVM,JVM啓動就相當於啓動了一個進程,接着由該進程創建了一個主線程去調用main方法。

(問題)JVM虛擬機的啓動是多線程還是單線程?

多線程。
因爲,垃圾回收線程也要先啓動,否則很容易出現內存溢出。垃圾回收線程+主線程,至少啓動了兩個線程,所以是多線程。

3.Thread

a.如何實現多線程程序?

java語言是不能直接調用系統功能(用系統功能創建對象)
java語言可以通過C/C++底層已經調用了系統功能
java提供了一個類:Thread 是程序中的執行線程。Java虛擬機允許應用程序併發地運行多個執行線程。

b.多線程程序實現方法一

繼承Thread類
i.步驟:
1)自定義類MyThread繼承Thread類
2)MyThread類裏重寫run()
3)創建對象
4)啓動線程
(問題)爲什麼要重寫run()?
因爲不是類中所有代碼都要被線程執行。爲了區分哪些代碼能夠被線程執行,java提供了Thread類中的run()方法用來包含那些要被線程執行的代碼
(問題)run()和start()的區別?
run():僅僅是封裝被線程執行的代碼,直接調用是普通方法。
start():首先啓動了線程,然後再由JVM去調用該線程的run()方法。
public class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100 ; i++) {
			System.out.println(i);
		}
	}
}
public class MyThreadDemo {
	public static void main(String[] args) {
		//創建線程對象
		MyThread my = new MyThread();
		//啓動線程
		my.start();
//		my.start();//IllegalThreadStateException
//		指示線程沒有處於請求操作所要求的適當狀態時拋出的異常。
//		相當於my線程被調用了兩次,而不是兩個線程啓動
		MyThread my2 = new MyThread();
		my2.start();
		
	}
}


c.常用方法:

如何獲取線程的名稱?
public final String getName()返回該線程的名稱。
如何設置線程的名稱?
public final void setName(String name)改變線程名稱,使之與參數 name 相同

public static Thread currentThread()返回對當前正在執行的線程對象的引用。

public class MyThread extends Thread {
	public MyThread() {
		// TODO Auto-generated constructor stub
	}
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		for (int i = 0; i < 100 ; i++) {
			System.out.println(getName()+":"+i);
		}
	}
}
/*
設置名字的源碼:
	public final synchronized void setName(String name) {
        checkAccess();
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
        if (threadStatus != 0) {
            setNativeName(name);
        }
    }
 */
public class MyThreadDemo {
	public static void main(String[] args) {
//		無參構造+setXxx();
//		//創建線程對象
//		MyThread my1 = new MyThread();
//		MyThread my2 = new MyThread();
//		//設置名稱
//		my1.setName("Yang");
//		my2.setName("Fan");
//		有參構造
		MyThread my1 = new MyThread("Ash");
		MyThread my2 = new MyThread("Glaz");
//		啓動線程
		my1.start();
		my2.start();
		
//		獲取main方法所在線程對象的名稱?
//		public static Thread currentThread()返回對當前正在執行的線程對象的引用。 
		System.out.println(Thread.currentThread().getName());
	}
}
/*爲什麼名稱是:Thread-1:60 帶有編號?
Thread的無參構造 源碼: 
	private volatile String name;
	
	public Thread() {
		init(null, null, "Thread-" + nextThreadNum(), 0);
	}
	

	private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
	   //省略代碼
	        this.name = name;
	   //省略代碼
	}
	
 	private static int threadInitNumber;//0,1
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;//return 0,1
    }
    
    public final String getName() {
        return name;
    }
    
 */


d.線程調度的兩種模型

i.分時調度模型:

所有線程輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間片

ii.搶佔式調度模型(Java採用的是該調度方式)

優先讓優先級高的線程使用CPU,如果線程優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的CPU時間片相對多一些。

iii.如何獲取默認優先級?

public final int getPriority()返回線程的優先級。
注意:線程默認優先級是5

iv.如何設置優先級?

public final void setPriority(int newPriority)更改線程的優先級。
注意:
1)會有異常java.lang.IllegalArgumentException
拋出的異常表明向方法傳遞了一個不合法或不正確的參數。
2)java.lang.Thread
public static final int MAX_PRIORITY 線程可以具有的最高優先級。 10
public static final int MIN_PRIORITY 線程可以具有的最低優先級。1
public static final int NORM_PRIORITY 分配給線程的默認優先級。5
優先級範圍:1~10

3)線程優先級僅僅表示線程獲取的CPU時間片的機率。要在多次運行時才能看到效果。

public class ThreadPriority extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 100 ; i++) {
			System.out.println(getName()+":"+i); 
		}
	}
}
public class ThreadPriorityDemo {
	public static void main(String[] args) {
		ThreadPriority tp1 = new ThreadPriority();
		ThreadPriority tp2 = new ThreadPriority();
		ThreadPriority tp3 = new ThreadPriority();
		
		tp1.setName("Ash");
		tp2.setName("Glaz");
		tp3.setName("Ying");
		
		//獲取默認優先級
//		System.out.println(tp1.getPriority());//5
//		System.out.println(tp2.getPriority());//5
//		System.out.println(tp3.getPriority());//5
		//設置優先級
//		tp1.setPriority(1000);
//		IllegalArgumentException
//		拋出的異常表明向方法傳遞了一個不合法或不正確的參數。 
		//設置正確的優先級
		tp1.setPriority(1);
		tp2.setPriority(10);
		
		tp1.start();
		tp2.start();
		tp3.start();
	}
}	

e.線程的控制

i.休眠線程

public static void sleep(long millis) throws InterruptedException在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
注意:父的方法沒有異常拋出,子的重寫方法不能有異常拋出只能 try...catch(這種情況只能子類中進行捕獲異常);

ii.加入線程

public final void join() throws InterruptedException 等待該線程終止。

iii.禮讓線程

public static void yield() 暫停當前正在執行的線程對象,並執行其他線程。
讓多個線程的執行更和諧但是不能保證一個一次

iv.後臺線程

public final void setDaemon(boolean on)將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。

v.中斷線程

public final void stop()
public void interrupt() 中斷線程。把線程的狀態終止,並拋出SecurityException

f.線程的生命週期

i.新建:創建線程對象
ii.就緒:有執行資格,沒有執行權
iii.運行:有執行資格,有執行權
iv.阻塞:由於一些操作讓線程處於該狀態。沒有執行資格,沒有執行權。而另一些操作可以激活它,讓他處於就緒狀態。

v.死亡:線程對象變成垃圾,等待回收


g.多線程程序實現方法二

實現 Runnable接口
i.步驟:
1)自定義一個類MyRunnable(同一個資源)實現Runnable接口
2)然後實現Rannable裏面的run方法:耗時的操作
3)在主線程中創建MyRuhnable類的對象
4)創建Thread類對象,將第三步的對象作爲參數進行傳遞來啓動線程
ii.有了方式一爲什麼要出現方式二?(實現接口方式的好處)
例如:有個類已經繼承了一個父類,而且父類不想繼承Thread類
解決了單繼承的侷限性。
適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼,數據有效分離,較好的體現了面向對象的設計思想。
public class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 100 ; i++) {
			//由於實現接口的方式不能直接使用Thread類的方法,但是可以間接使用
			System.out.println(Thread.currentThread().getName()+":"+i);
		}
	}
}
public class MyRunnableDemo {
	public static void main(String[] args) {
		//創建MyRunnable類對象
		MyRunnable mr = new MyRunnable();
		//創建Thread類對象,把MyRunnable類對象作爲構造參數傳遞
//		public Thread(Runnable target)
//		Thread t1 = new Thread(mr);
//		Thread t2 = new Thread(mr);
//		
//		t1.setName("Ash");
//		t2.setName("Ying");
//		public Thread(Runnable target,String name)
		
		Thread t1 = new Thread(mr, "Ash");
		Thread t2 = new Thread(mr, "Ying");
		
		t1.start();
		t2.start();
		
	}
}


4.解決線程安全問題的基本思想

a.賣票問題的問題

i.相同的票出現多次

CPU的一次操作必須是原子性的

ii.出現了負數的票

隨機性和延遲導致的

//是否是多線程環境 	是
//是否有共享數據		是
//是否有多條語句操作共享數據	是
//滿足出問題的條件
public class SellTicket implements Runnable {
	//定義100張票
	private int ticket = 100;
	@Override
	public void run() {
		while(true){
			if(ticket>0){
				try {
					Thread.sleep(100);//t1進來不休息,t2進來休息,t3進來休息
				} catch (InterruptedException e) {
					e.printStackTrace();
				}	
				System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
			}
		}
	}
}
//實現Runnable接口的方法
public class SellTicketDemo {
	public static void main(String[] args) {
		//創建資源對象
		SellTicket st = new SellTicket();
		//創建三個線程對象
		Thread t1 = new Thread(st, "窗口1");
		Thread t2 = new Thread(st, "窗口2");
		Thread t3 = new Thread(st, "窗口3");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

b.首先想爲什麼出現問題?(也是我們判斷是否有問題的標準)

i.是否是多線程環境
ii.是否有共享數據
iii.是否有多條語句操作共享數據

c.如何解決?

i和ii的問題改變不了,只能想辦法把C改變。
思想:把多條語句操作共享數據的代碼給包成一個整體,讓某個線程在執行的時候,別人不能來執行。

5.同步代碼塊:

a.格式:

synchronized (對象){

需要被同步的代碼;

}

注意:同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。

public class SellTicket implements Runnable {
	//定義100張票
	private int ticket = 100;
	//創建鎖對象
	private  Object obj = new Object();
	
	@Override
	public void run() {
		while(true){
			//t1,t2,t3都可以走到這裏
			//t1搶到執行權t1進
			//門(開/關)
			synchronized (obj) {//t1進來後門關
				if(ticket>0){
					try {
						Thread.sleep(100);//t1睡眠
					} catch (InterruptedException e) {
						e.printStackTrace();
					}	
					System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
				}
			}
		}
	}
}

b.同步的前提

多個線程
多個線程使用的是同一個鎖對象

c.同步的優點和缺點:

優點:同步的出現解決了多線程的安全問題。
缺點:當線程相當多時,因爲每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。

d.同步解決線程安全問題

i:同步代碼塊

這裏的鎖對象可以是任意對象

ii:同步方法

把同步加在方法上。
這裏的鎖對象是this

iii:靜態同步方法

把同步加在方法上。
這裏的鎖對象是當前類的字節碼文件對象(反射再講字節碼文件對象)

i.
class Demo{
	
}
public class SellTicket implements Runnable {
	//定義100張票
	private int ticket = 100;
	//創建鎖對象
	private Object obj = new Object();
	private Demo d = new Demo(); 
	private int x = 0;
//  同步代碼塊用Obj做鎖
//	@Override
//	public void run() {
//		while(true){
//			synchronized (obj) {
//				if(ticket>0){
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}	
//					System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
//				}
//			}
//		}
//	}
	
//	同步代碼塊
//	這裏的鎖對象可以是任意對象。
//	@Override
//	public void run() {
//		while(true){
//			synchronized (d) {
//				if(ticket>0){
//					try {
//						Thread.sleep(100);
//					} catch (InterruptedException e) {
//						e.printStackTrace();
//					}	
//					System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
//				}
//			}
//		}
//	}
//}
	@Override
	public void run() {
		while(true){
			if(x%2==0){
				synchronized (d) {
					if(ticket>0){
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}	
						System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
					}
				}
			}else{
//				synchronized (d) {
//					if(ticket>0){
//						try {
//							Thread.sleep(100);
//						} catch (InterruptedException e) {
//							e.printStackTrace();
//						}	
//						System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
//					}
//				}
			sellTicket();
			}
		}	
	}
	//如果一個代碼一進去就同步 把同步加在方法上
	private void sellTicket() {
//		synchronized (d) {
//			if(ticket>0){
//				try {
//					Thread.sleep(100);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}	
//				System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
//			}
//		}
	}
}

ii.
class Demo{
	
}
public class SellTicket implements Runnable {
	//定義100張票
	private int ticket = 100;
	//創建鎖對象
	private Object obj = new Object();
	private Demo d = new Demo(); 
	private int x = 0;

	@Override
	public void run() {
		while(true){
			if(x%2==0){
				synchronized (this) {
					if(ticket>0){
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}	
						System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
					}
				}
			}else{
				sellTicket();
			}
		}	
	}
	//如果一個代碼一進去就同步 把同步加在方法上
	private  synchronized void sellTicket() {
			if(ticket>0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}	
				System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
			}
	}
}

iii.
class Demo{
	
}
public class SellTicket implements Runnable {
	//定義100張票
	private static int ticket = 100;
	//創建鎖對象
	private Object obj = new Object();
	private Demo d = new Demo(); 
	private int x = 0;

	@Override
	public void run() {
		while(true){
			if(x%2==0){
				synchronized (SellTicket.class) {
					if(ticket>0){
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}	
						System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
					}
				}
			}else{
				sellTicket();
			}
		}	
	}
	private  static synchronized void sellTicket() {
			if(ticket>0){
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}	
				System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"張票");
			}
	}
}



發佈了51 篇原創文章 · 獲贊 15 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章