Java基礎-知識點總結-Java多線程

 

Java多線程

進程:

 

       是一個正在運行的程序,比如正在運行的迅雷,QQ等。

 

   線程:

 

每個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫做一個控制單元,線程就是進程中的一個獨立的控制單元;線程控制着進程的運行;一個進程中至少有一個線程。Java虛擬機允許應用程序併發地運行多個執行線程。

 

比如:JVM啓動時會運行一個進程java.exe該進程中至少有一個線程負責java程序的執行,並且這個線程運行的代碼存在於main方法中,該線程成爲主線程。除了主線程外,還有負責垃圾回收機制的進程。

 

 

class Test {
	public static void main(String[] args) // 主線程
	{
		for (int i = 0; i < 4000; i++) {
			System.out.println("Hello World!");
		}
	}
}

 

 

 

 

創建線程

 

       創建新執行線程有兩種方法:

 

       方法一:將類聲明爲Thread的子類。該子類應重寫Thread類的run方法。接下來可以分配並啓動該子類的實例。

 

class Demo extends Thread // 定義子類繼承Thread
{
	public void run() // 複寫run()方法,存儲自定義代碼
	{
		for (int i = 0; i < 30; i++) {
			System.out.println("Demo run" + i);
		}
	}
}

class ThreadDemo {
	public static void main(String[] arge) {
		Demo d = new Demo(); // 創建子類對象,創建一個線程
		d.start(); // 調用start()方法:啓動線程;調用run()方法
		d.run(); // 僅僅是對象調用方法,沒有開啓新的線程
		for (int i = 0; i < 30; i++) {
			System.out.println("Hello World" + i);
		}
	}
}

 

 

 

 

       方法二:聲明實現Runnable接口的類。該類然後實現run方法。然後可以分配該類的實例,在創建Thread時作爲一個參數來傳遞並啓動。

 

  1. 定義類實現Runnable接口

  2. 覆蓋Runnable接口中run()方法,將線程要運行的代碼存放在該run方法中

  3. 通過Thread類建立線程對象

  4. 將Runnable接口的子類對象作爲實際參數傳遞給Thread類的構造函數;因爲自定義的run()方法所屬對象是Runnable接口的子類對象,而又要線程去執行指定對象的run()方法,所以要明確該run()方法所屬的對象

  5. 調用Thread類的start()方法開啓線程並調用Runnable接口子類的run()方法

 

//定義類實現Runnable接口
class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆蓋Runnable接口中的run()方法
		while (true) {
			if (ticket > 0) {
				System.out.println(Thread.currentThread().getName()
						+ " sale..." + ticket--);
			}
		}
	}
}
public class Demo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket t = new Ticket();
		// 創建線程
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		// 開啓線程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

 

 

 

 

         兩種創建線程的方式的區別:

 

                     第一種方式:直接繼承Thread類創建對象,線程代碼存放在Thread子類的

 

                     run()方法中。Thread子類無法再從其它類繼承(java語言單繼承);編寫簡

 

                    單,run()方法的當前對象就是線程對象,可直接操作。

 

                     第二種方式:使用Runnable接口創建線程,線程代碼存放在Runnable接口

 

實現的子類的run()方法中。可以將CPU,代碼和數據分開,形成清晰的模

 

;線程體run()方法所在的類可以從其它類中繼承一些有用的屬性和方法;

 

有利於保持程序的設計風格一致

 

           在實際應用中,幾乎都採用第二種方式,第二種能避免單繼承的侷限性

 

        *從運行的結果中可以發現:

 

             發現運行結果每次都不一樣,因爲多個線程都在獲取cpu的執行使用權,cpu

 

             執行到誰,誰就運行。在某一時刻,cpu(多核除外)只能有一個程序在運

 

             行,由於cpu在做着快速的切換,一大到看上去是在同時運行的效果。可以

 

             得到多線程的一個特性:隨機性。

 

       爲什麼要覆蓋run()方法呢?

 

               Run()方法:用於存儲要運行的代碼,

 

               Start()方法:用於開啓線程

 

       線程的四種狀態

 

     獲取線程對象及名稱

 

                    線程都有自己默認的名稱名稱的格式:Thread-編號編號從0開始

 

                    1、currentThread():靜態方法,用於獲取當前正在執行的線程對象的引用

 

                    2、GetName():用戶與獲取當前線程對象的名稱

 

3、currentThread().getName()和this.getName()作用相同,都用於獲取當前線程對象的名稱

 

4、setName():用於給當前線程對象設置名稱或者調用Thread類的構造方法設置當前線程對象的名稱

 

class Demo extends Thread {
	public Demo(String name) {
		super(name);
	}

	public void run() {
		for (int i = 0; i < 10; i++) {
			// 通過調用this.getName()方法獲取該線程對象的名稱
			System.out.println(this.getName() + "  run…" + i);
			// 通過靜態方法currentThread()方法獲取當前線程對象
			// 通過currentThread.getName()方法獲取當前線程對象名稱
			System.out.println(Thread.currentThread().getName() + "  run…" + i);
		}
	}
}

class ThreadTest {
	public static void main(String[] args) {
		Demo t1 = new Demo("線程1");
		Demo t2 = new Demo("線程2");
		t1.start();
		t2.start();
	}
}

 

 

 

 

 

 

     多線程的安全問題

 

class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆蓋Runnable接口中的run()方法
		while (true) {
			if (ticket > 0) {
				try {
					Thread.sleep(10);
				} catch (Exception e) {
				}
				System.out.println(Thread.currentThread().getName()
						+ " sale..." + ticket--);
			}
		}
	}
}

public class Demo {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Ticket t = new Ticket();
		// 創建線程
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		// 開啓線程
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}//運行結果出現了0,-1,-2等的錯票,多線程出現了安全問題

 

 

 

 

問題原因

 

當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行,導致共享數據錯誤。

 

解決辦法:

 

               對多條操作共享數據的語句,只能讓一個線程執行完,在執行過程中,其

 

              他線程不可以參與執行。

 

                           Java對於多線程的安全問題提供了專業的解決方案:同步代碼快

 

                           Synchronized(對象){

 

                                  需要同步的代碼

 

                           }

 

           代碼應修改爲:

 

class Ticket implements Runnable {
	private int ticket = 100;

	public void run() { // 覆蓋Runnable接口中的run()方法
		Object obj = new Object();
		while (true) {
			synchronized (obj) {
				if (ticket > 0) {
					try {
						Thread.sleep(10);
					} catch (Exception e) {
					}
					System.out.println(Thread.currentThread().getName()
							+ " sale..." + ticket--);
				}
			}
		}
	}
}

 

 

 

 

 

     多線程同步代碼塊

          Synchronized(對象){

                      需要同步的代碼

               }

               對象如同鎖,持有鎖的線程可以在同步中執行,沒有持有鎖的線程即使擁有cpu

               的執行權,也進不去,因爲沒有持有鎖。

               同步的前提:

  1. 必須有兩個或有兩個以上的線程

  2. 必須是多個線程使用同一個鎖

                    好處:解決了多線程的安全問題

                    弊端:多個線程需要判斷鎖,較爲消耗資源

           多線程同步函數

               Public synchronized 返回值類型方法阿明(){方法體}

class Bank {
	private int sum;

	public synchronized void add(int n) {
		sum = sum + n;
		System.out.println("sum=" + sum);
	}
}

class Cus implements Runnable {
	private Bank b = new Bank();

	public void run() {
		for (int i = 0; i < 3; i++) {
			b.add(100);
		}
	}
}

class BankDemo {
	public static void main(String[] args) {
		Cus c = new Cus();
		Thread t1 = new Thread(c);
		Thread t2 = new Thread(c);
		t1.start();
		t2.start();
	}
}

 

同步函數用的鎖是this

 

因爲函數需要被對象調用,那麼函數都有一個所屬的對象引用,就是this

驗證同步函數用的鎖是this:

思路:使用兩個線程來買票,一個線程在執行同步代碼快中所用的鎖是obj,一個線程在執行同步函數中,兩個線程都在執行買票動作。

class Ticket implements Runnable {
	private int tick = 100;
	Object obj = new Object();
	public Boolean f = true; // 用於區分第一個線程執行同步代碼塊,第二

	// 個線程在執行同步函數
	public void run() {
		if (f) {
			while (true) {
				synchronized (obj) {
					if (tick > 0) {
						try {
							Thread.sleep(10);
						} catch (Exception e) {
						}
						System.out.println(Thread.currentThread().getName()
								+ "…code " + tick--);
					}
				}
			}
		} else {
			while (true)
				show();
		}
	}

	public synchronized void show() {
		if (tick > 0) {
			try {
				Thread.sleep(10);
			} catch (Exception e) {
			}
			System.out.println(Thread.currentThread().getName() + "…show…"
					+ tick--);
		}
	}
}

class ThisLockDemo {
	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.f = false;
		t2.start();
	}
}

運行結果出現了0號票,說明程序不安全,兩個線程所用的鎖不是同一個鎖

而把同步代碼塊中對象參數改爲’this’,則運行結果沒有出現不符合的票,說明

兩個線程用的是同一個鎖,所以同步函數所用的鎖是this

靜態同步函數所用的鎖是該方法所在類字節碼文件對象,即類名.class

如果把上面代碼中的同步函數用static修飾,則運行結果又會出現0號票,說

明靜態同步函數所用的鎖不是this,而把同步代碼塊中的對象參數改

爲’Ticket.class’後,運行結果沒有出現不合法的票,所以說明兩個線程用的鎖是

同一個鎖,所以靜態同步函數所用的鎖是本類類名.class

 



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