多線程簡述

進程和線程

進程的介紹
  • 是一個程序的運行狀態和資源佔用(內存,cpu)的描述
  • 進程是程序的一個動態過程,它指的是從代碼加載到執行完畢的一個完成過程
  • 進程的特點:
    • 獨立性:不同的進程之間是獨立的,相互之間資源不共享
    • 動態性:進程在系統中不是靜止不動的,而是在系統中一直活動的
    • 併發性:多個進程可以在單個處理器上同時進行,且互不影響
線程的介紹
  • 是進程的組成部分,一個進程可以有多個線程,每個線程去處理一個特定的子任務
  • 線程的執行時搶佔式的,多個線程在同一個進程中可以併發執行,其實就是cpu快速的在不同的線程之間切換,也就是說,當前運行的線程在任何時候都有可能被掛起,以便另一個線程可以運行
進程和線程的關係以及區別
  • 一個程序運行後至少有一個進程
  • 一個進程可以包含多個線程,但是至少需要有一個線程,否則這個進程是沒有意義的
  • 進程間不能共享資源,但線程之間可以
  • 系統創建進程需要爲該進程重新分配系統資源,而創建線程則容易的多,因此使用線程實現多任務併發比多線程的效率高

線程的實現

繼承Thread類
  • 進程自Thread類,Thread類是所有線程類的父類,實現了對線程的抽取和封裝
  • 繼承Thread類創建並啓動多線程的步驟:
    • 定義一個類,繼承自Thread類,並重寫該類的run方法,該run方法的方法體就代表了線程需要完成的任務,因此,run方法的方法體被稱爲線程執行體
    • 創建Thread子類的對象,即創建了子線程
    • 用線程對象的start方法來啓動該線程
package waking.test.xc;
/**
 * 線程的實現,繼承Thread類
 * @author waking
 *
 */
public class Demo01 extends Thread{
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
	
	public static void main(String[] args) {
		Demo01 d = new Demo01();
		d.setName("a");
		d.start();
		for (int i = 0; i <100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
		
	}
}

  • 多線程買票
package waking.test.xc;
/**
 * 售票線程,Thread
 * @author waking
 *
 */
public class Demo02 {
	public static void main(String[] args) {
		SellTickets s1 = new SellTickets();
		SellTickets s2 = new SellTickets();
		SellTickets s3 = new SellTickets();
		SellTickets s4 = new SellTickets();
		
		//開啓線程,4個窗口買票
		s1.start();
		s2.start();
		s3.start();
		s4.start();
	}
}
/**
 * 售票線程
 * @author waking
 *
 */
class SellTickets extends Thread{
	
	static int num = 100;
	
	@Override
	public void run() {
		while(num>0) {
			System.out.println(Thread.currentThread().getName()+"===>"+num--);
		}
	}
}
實現Runnable接口
  • 實現Runnable接口創建並啓動多線程的步驟:
    • 定義一個Runnable接口的實現類,並重寫該接口中的run方法,該run方法的方法體同樣是該線程的線程執行體
    • 創建Runnable實現類的實例,並以此實例作爲Thread的target來創建Thread對象,該Thread對象纔是真正的線程對象
    • 調用線程對象的start方法來啓動該線程
package waking.test.xc;
/**
 * 線程實現:實現Runnable接口
 * @author waking
 *
 */
public class Demo03 {
	public static void main(String[] args) {
		A a = new A();
		Thread t1 = new Thread(a,"a");
		t1.start();
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
}
class A implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
  • 模擬買票
package waking.test.xc;
/**
 * 實現Runnable買票
 * @author waking
 *
 */
public class Demo04 {
	public static void main(String[] args) {
		RunnableTest r = new RunnableTest();
		
		//啓動線程
		new Thread(r, "A").start();
		new Thread(r, "B").start();
		new Thread(r, "C").start();
		new Thread(r, "D").start();
	}
}
class RunnableTest implements Runnable{

	static int num = 100;
	@Override
	public void run() {
		while(num>0) {
			System.out.println(Thread.currentThread().getName()+"===>"+num--);
		}
	}
	
}
兩種方法的比較
  • 實現Runnable接口的方法
a.線程類只是實現Runnable接口,還可以繼承其他類【java特性,一個類在實現接口的同時還可以繼承另一個類】
b.可以多個線程共享同一個target對象,所以非常適合多線程來處理同一份資源的情況
c.弊端:編程稍微複雜,不直觀,如果要訪問當前線程,必須使用Thread.currentThread()
  • 繼承Thread類的方式
a.編寫簡單,如果要訪問當前線程,除了可以通過Thread.currentThread()方法之外,還可以使用super關鍵字
b.弊端:因爲線程類已經繼承了Thread類,則不能再繼承其他類【單繼承】
  • 注意:實際上大多數的是、多線程應用都可以採用實現Runnable接口的方法來實現【推薦使用匿名內部類】
Callable接口
  • 創建FutureTask對象,創建Callable子類對象,複寫call(相當於run)方法,將其傳遞給FutureTask對象(相當於一 個Runnable)。
  • 創建Thread類對象,將FutureTask對象傳遞給Thread對象。
  • 調用start方法開啓線程。這種方式可以 獲得線程執行完之後的返回值。
  • 示例
package waking.test.xc;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * 多線程實現Callable接口
 * @author waking
 *
 */
public class Demo14 {
	public static void main(String[] args) {
		I i = new I();
		FutureTask<Integer> f = new FutureTask<Integer>(i);
		new Thread(f).start();
		
		for (int j = 0; j < 100; j++) {
			System.out.println(Thread.currentThread().getName()+"===>"+j);
		}
	}

}
/**
 * 實現Callable接口
 * @author waking
 *
 */
class I implements Callable<Integer>{

	
	@Override
	public Integer call() throws Exception {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
		return null;
	}
	
}
調用start()與run()方法的區別
  • 當調用start()方法時將創建新的線程,並且執行run()方法裏的代碼,但是如果直接調用start()方法,不會創建新的線程也不會執行調用線程的代碼

線程常用方法

線程休眠
  • 使用當前正在執行的線程休眠一段時間,釋放時間片,導致線程進行阻塞狀態
  • sleep(1000),1000的單位是毫秒,設置了sleep就相當於將當前線程掛起1秒,這個操作跟線程的優先級無關
package waking.test.xc;
/**
 * 線程sleep()方法
 * @author waking
 *
 */
public class Demo05 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i%20==0) {
				try {
					//休眠100毫秒
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
	public static void main(String[] args) {
		Demo05 d = new Demo05();
		new Thread(d,"A").start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
設置線程優先級
  • 可以通過設置優先級來改變線程搶到時間片的概率,優先級高的線程獲得較多的執行機會
  • 默認情況下,每個線程的優先級都與創建它的父線程具有相同的優先級,列如:main線程具有普通優先級,則由main線程創建的子線程也有相同的普通優先級
  • 注意:所傳的參數範圍1~10,默認爲5,對應的數值越大,說明優先級越高,這個方法的設置一定要在start之前線程的優先級低並不意味着爭搶不到時間片,只是搶到時間片的概率比較低而已
package waking.test.xc;
/**
 * 線程,設置線程優先級
 * @author waking
 *
 */
public class Demo06 implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
	public static void main(String[] args) {
		Demo06 d = new Demo06();
		Thread t = new Thread(d,"A");
		//設置優先級爲10
		t.setPriority(10);
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
}
合併線程
  • 在執行原來線程的過程中,如果遇到了合併線程,則優先執行合併進來的線程,執行完合併進來的線程後,再回到原來的任務中,繼續執行原來的線程
  • 特點:
    • 線程合併,當前線程一定會釋放cpu時間片,cpu會將時間片分給要joio的線程
    • 哪個線程需要合併就在當前線程中,添加要合併的線程
    • join之前,一定要將線程處於準備狀態start
package waking.test.xc;
/**
 * 合併線程
 * @author waking
 *
 */
public class Demo07 {
	public static void main(String[] args) throws InterruptedException {
		B b = new B();
		Thread t = new Thread(b,"B");
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
			if(i==20) {
				//合併線程
				t.join();
			}
		}
		
	}
}
class B implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
後臺線程
  • 隱藏起來一直在默默運行的線程,直到進程介紹,又被稱爲守護線程或精靈線程,JVM的垃圾回收線程就是典型的後臺線程
  • 特徵:如果所有的前臺線程都死亡,後臺線程會自動死亡,必須要在start之前執行
package waking.test.xc;
/**
 * 守護線程
 * @author waking
 *
 */
public class Demo08 {
	public static void main(String[] args) {
		C  c = new C();
		Thread t = new Thread(c,"C");
		//設置守護線程
		t.setDaemon(true);
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}

}
class C implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
	
}
線程讓步
  • 可以讓當前正在執行的線程暫停,但它不會阻礙該線程,他只是將該線程轉入就緒狀態,完全可能出現的情況是:當某個線程用了yield方法暫停之後,線程調度器又將其調度出來重新執行
  • 實際上,當某個線程調用了yield方法暫停之後,只有優先級與線程相同,或者優先級比當前線程更高的就緒狀態的線程纔會獲得執行的機會
package waking.test.xc;
/**
 * yield()線程
 * @author waking
 *
 */
public class Demo09 {
	public static void main(String[] args) {
		D d = new D();
		Thread t = new Thread(d,"D");
		t.start();
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"==>"+i);
		}
	}
}
class D implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName()+"===>"+i);
			if(i==50) {
				//禮讓
				Thread.yield();
			}
		}
	}
	
}

線程的生命週期

  • 對於線程,當線程被創建並啓動之後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態,在線程的生命週期中,它會經歷各種不同的狀態
New(新生):線程被實例化,但是還沒有開始執行
Runnable(就緒):沒有搶到時間片
Running(運行):搶到了時間片,cpu開始處理這個線程的任務
Blocked(阻塞):線程在執行過程中遇到特殊情況,使得其他線程就可以獲得執行機會,被阻塞的線程會等待合適的時機重新進入就緒狀態
Dead(死亡):線程終止
	a.run方法執行完成,線程正常結束【正常的死亡】
	b.直接調用該線程的stop方法強制終止這個線程

alt

多線程訪問臨界資源

多線程訪問臨界資源時的數據安全問題
  • 產生原因:有多個線程在同時訪問一個資源,如果一個線程在取值的過程中,時間片又被其它線程搶走了,臨界資源問題就產生了
解決臨界資源問題
  • 解決方案:一個線程在訪問臨界資源的時候,如果給這個資源“上一把鎖”,這個時候如果其它線程也要訪問這個資源,就得在“鎖”外面等待
  • 對象鎖:任意的對象都可以被當做鎖來使用
  • 類鎖:把一個類當做鎖
同步代碼塊
語法: 
	synchronized(鎖){
		//需要訪問的臨界資源
	}
說明:
a.程序走到代碼段中,就用鎖來鎖住了臨界資源,這個時候,其它線程不能執行代碼段中的代碼,只能在鎖外面等待
b.執行完成代碼段中的這段代碼,會自動解鎖,然後剩下的其它線程開始爭搶cpu時間片
c.一定要保證不同的線程看到的是同一把鎖,否則同步代碼塊就沒有意義
  • 示例
package waking.test.xc;
/**
 * 同步代碼塊
 * @author waking
 *
 */
public class Demo10 {
	public static void main(String[] args) {
		M m = new M();
		Thread t = new Thread(m, "A");
		Thread t1 = new Thread(m, "B");
		Thread t2 = new Thread(m, "C");
		Thread t3 = new Thread(m, "D");
		t.start();
		t1.start();
		t2.start();
		t3.start();
	}
	
}
class M implements Runnable{

	static int num = 100;
	
	@Override
	public void run() {
		while(num>0) {
			//加鎖
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized (M.class) {
				if(num<0) {
					break;
				}
				System.out.println(Thread.currentThread().getName()+"===>"+num--);
			}
		}
	}
	
}


  • 同步代碼塊
package waking.test.xc;

import javax.swing.text.AbstractDocument.BranchElement;

/**
* 同步方法
* @author waking
*
*/
public class Demo11 {
   public static void main(String[] args) {
   	MM m = new MM();
   	Thread t = new Thread(m,"A");
   	Thread t1 = new Thread(m,"B");
   	Thread t2 = new Thread(m,"C");
   	Thread t3 = new Thread(m,"D");
   	
   	t.start();
   	t1.start();
   	t2.start();
   	t3.start();
   }

}
class MM implements Runnable{
   
   static int num = 100;
   
   @Override
   public void run() {
   	while(num>0) {
   		try {
   			test();
   		} catch (InterruptedException e) {
   			// TODO Auto-generated catch block
   			e.printStackTrace();
   		}
   		
   	}
   }
   //同步方法
   public synchronized void test() throws InterruptedException {
   	if(num<0) {
   		return;
   	}
   	Thread.sleep(100);
   	System.out.println(Thread.currentThread().getName()+"===>"+num--);
   
   }
   
}
ReentrantLock類
  • 通過顯示定義同步鎖對象來實現同步,同步鎖提供了比synchronized代碼塊更廣泛的鎖定操作
  • 注意:最好將unlock的操作放到finally塊中
  • 通過使用ReentrantLock這個類來進行鎖的操作,它實現了Lock接口,使用ReentranrLock可以顯示地加鎖,釋放鎖
package waking.test.xc;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock
 * @author waking
 *
 */
public class Demo12 {
	public static void main(String[] args) {
		H h =new H();
		Thread t = new Thread(h,"A");
		Thread t1 = new Thread(h,"B");
		Thread t2 = new Thread(h,"C");
		Thread t3 = new Thread(h,"D");
		
		t.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
class H implements Runnable{
	
	static ReentrantLock lock = new ReentrantLock();
	static int num = 100;
	@Override
	public void run() {
		
			while (num > 0) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				try {
					//加鎖
					lock.lock();
					if (num < 0) {
						return;
					}
					System.out.println(Thread.currentThread().getName() + "===>" + num--);
				} finally {
					//解鎖
					lock.unlock();
				}
			} 
		
	}
	
}
死鎖
  • 每個人都擁有其它人需要的資源,同時又等待其它人擁有的資源,並且每個人在獲得所有需要的資源之前都不會放棄已經擁有的資源
  • 當多個線程完成功能需要同時獲取多個共享資源的時候可能導致死鎖
package waking.test.xc;
/**
 * 死鎖
 * @author waking
 *
 */
public class Demo13 {
	public static void main(String[] args) {
		WW w = new WW();
		new Thread(w,"A").start();
		new Thread(w,"B").start();
		new Thread(w,"C").start();
		new Thread(w,"D").start();
	}
}
class WW implements Runnable{
	
	static boolean floge = true;
	static Object o1 = new Object();
	static Object o2 = new Object();

	@Override
	public void run() {
		if(floge){
			synchronized (o1) {
				floge=!floge;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized(o2) {
					for (int i = 0; i < 100; i++) {
						System.out.println(Thread.currentThread().getName()+"===>"+i);
					}
				}
			}
		}else {
			synchronized (o2) {
				floge=!floge;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				synchronized (o1) {
					for (int i = 0; i < 100; i++) {
						System.out.println(Thread.currentThread().getName()+"===>"+i);
					}
				}
			}
		}
	}
	
}
  • 原理:
實現多個鎖之間的嵌套產生死鎖。
分析:線程0 想要得到obj2 鎖進行下面的操作,而obj2鎖被線程1 所佔有。 線程1想得到obj1鎖 進行下面的操作,而
obj1鎖被線程0 所佔有。
多線程先將到這裏,下個博客會介紹單例模式、生產者、消費者模式中的多線程問題
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章