多線程深入理解

一:線程概述

        1,線程和進程

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

                  線程:進程的執行單元,執行路徑。

        2,多線程和多進程的意義

                 多進程:提高CPU的使用率
                 多線程: 提高應用程序的使用率

         3,線程的生命週期

 

 

二:實現多線程

       實現方式一;編寫一個類繼承Thread類,,重寫run()方法

public class MyThread extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 200; x++) {
			System.out.println(x);
		}
	}
}
public class MyThreadDemo {
	public static void main(String[] args) {
		MyThread my1 = new MyThread();
		MyThread my2 = new MyThread();
		my1.start();
		my2.start();
	}

  實現方式二:編寫一個類實現Runnable接口;重寫run()方法;創建MyRunnable對象;創建Thread類對象,將MyRunnable對象作爲形參進行傳遞

public class RunnableDemo {
	public static void main(String[] args) {
		MyRunnable mr1 = new MyRunnable();
		Thread t1 = new Thread (mr1);
		Thread t2 = new Thread (mr1);
		Thread t1 = new Thread (mr1,"線程一");
		Thread t2 = new Thread (mr1,"線程二");
		t1.start();
		t2.start();
	}
 
}
class MyRunnable implements Runnable{
	public void run(){
		System.out.println(Thread.currentThread().getName()+":"+"yangjinbiao");
	}
}

         實現方式三: 實現Callable 接口和使用FutureTask接收返回值

          Callable方式依賴線程池,線程的的submit方法可以接收一個Callable接口的實現對象,同時放回一個FutureTask,如果Callable的call方法出錯,將會拋出一個異常。

首先查看API

Interface Callable<V>

發現這個接口有個泛型,在查看接口內的方法:

V call() 計算一個結果,如果不能這樣做,就會拋出一個異常。 

發現接口方法的返回值有個泛型值,可以得出該接口的泛型是call()的放回值。在通過API查看FutureTask類。

Class FutureTask<V>

這個類泛型也是接收Callable接口的call()方法的放回值,通過查看FutureTask類的方法。

V get() 等待計算完成,然後檢索其結果。  
protected void set(V v) 將此未來的結果設置爲給定值,除非此未來已被設置或已被取消。 
protected void setException(Throwable t) 
導致這個未來報告一個ExecutionException與給定的可拋棄的原因,除非這個未來已經被設置或被取消。 

第一個方法表示獲取call()方法的放回值存貯在FutureTask的值,下面是實現代碼。

public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 創建線程池對象
		ExecutorService pool = Executors.newFixedThreadPool(2);

		// 可以執行Runnable對象或者Callable對象代表的線程
		Future<Integer> f1 = pool.submit(new MyCallable(100));
		Future<Integer> f2 = pool.submit(new MyCallable(200));

		// V get()
		Integer i1 = f1.get();
		Integer i2 = f2.get();

		System.out.println(i1);
		System.out.println(i2);

		// 結束
		pool.shutdown();
	}
}
public class MyCallable implements Callable<Integer> {

	private int number;

	public MyCallable(int number) {
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}

}

三:與線程相關的注意事項

        1,run和start的區別:

                run():僅僅是封裝被線程執行的代碼,直接調用是普通方法

                start():首先啓動了線程,然後再由jvm去調用該線程的run()方法。

        2,爲何需要重寫run方法

                 不是類中的所有代碼都需要被線程執行的。而這個時候,爲了區分哪些代碼能夠被線程執行,java提供了Thread                 類中的run()用來包含那些被線程執行的代碼。

       3,多次啓動線程會怎麼樣?

                出現異常:在第二次調用start()方法的時候,線程可能處於終止或者其它(非NEW)狀態,但是不論如何,都是不可以再次啓動的。

四:與線程相關的方法

        1,線程名的獲取和設置

           設置名稱: public final String setName()或者構造方法來設置線程的名稱。

          獲取名稱: public final String getName():獲取線程的名稱。public static Thread currentThread():返回當前正在執行的線程對象

         2,線程優先級:線程優先級默認爲5,範圍爲1至10;

              通過: public final int getPriority():獲取優先級

               通過:public final void setPriority(int newPriority):設置優先級

五:線程控制

        1,線程休眠:  public static void sleep(long millis)

public class ThreadSleepDemo {
	public static void main(String[] args) {
		ThreadSleep ts1 = new ThreadSleep();
		ThreadSleep ts2 = new ThreadSleep();
		ThreadSleep ts3 = new ThreadSleep();
		ts1.setName("林青霞");
		ts2.setName("林志玲");
		ts3.setName("林志穎");
		ts1.start();
		ts2.start();
		ts3.start();
	}
}
public class ThreadSleep extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x + ",日期:" + new Date());
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

       2,加入線程:public final void join():等待該線程終止。

public class ThreadJoinDemo {
	public static void main(String[] args) {
		ThreadJoin tj1 = new ThreadJoin();
		ThreadJoin tj2 = new ThreadJoin();
		ThreadJoin tj3 = new ThreadJoin();
		tj1.setName("李淵");
		tj2.setName("李世民");
		tj3.setName("李元霸");
		tj1.start();
		try {
			tj1.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		tj2.start();
		tj3.start();
	}}
public class ThreadJoin extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}}}

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

public class ThreadYieldDemo {
	public static void main(String[] args) {
		ThreadYield ty1 = new ThreadYield();
		ThreadYield ty2 = new ThreadYield();
		ty1.setName("林青霞");
		ty2.setName("劉意");
		ty1.start();
		ty2.start();
	}}
public class ThreadYield extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
			Thread.yield();
		}}}

4, 守護線程: public final void setDaemon(boolean on):將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,Java 虛擬機退出。 該方法必須在啓動線程前調用。 
         

public class ThreadDaemonDemo {
	public static void main(String[] args) {
		ThreadDaemon td1 = new ThreadDaemon();
		ThreadDaemon td2 = new ThreadDaemon();
		td1.setName("關羽");
		td2.setName("張飛");
		// 設置收穫線程
		td1.setDaemon(true);
		td2.setDaemon(true);
		td1.start();
		td2.start();
		Thread.currentThread().setName("劉備");
		for (int x = 0; x < 5; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}}}
public class ThreadDaemon extends Thread {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(getName() + ":" + x);
		}}}

5,線程中斷: 
          public final void stop():讓線程停止,過時了,但是還可以使用。
          public void interrupt():中斷線程。 把線程的狀態終止,並拋出一個InterruptedException。

public class ThreadStopDemo {
	public static void main(String[] args) {
		ThreadStop ts = new ThreadStop();
		ts.start();	
		try {
			Thread.sleep(3000);
			// ts.stop();
			ts.interrupt();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}}
public class ThreadStop extends Thread {
	@Override
	public void run() {
		System.out.println("開始執行:" + new Date());
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			// e.printStackTrace();
			System.out.println("線程被終止了");
		}
		System.out.println("結束執行:" + new Date());
	}
}

六:線程組

線程組是用於對功能相同的進行管理,可以對線程組內的全部線程進行統一管理。實現方式如下所示,只要是通過線程的構造方法實現的

查看API

java.lang.ThreadGroup 

構造方法

ThreadGroup(String name) 構造一個新的線程組。  
ThreadGroup(ThreadGroup parent, String name) 創建一個新的線程組。 

主要方法:

void destroy() 銷燬此線程組及其所有子組。 
int getMaxPriority() 返回此線程組的最大優先級。  
String getName() 返回此線程組的名稱。  
void setMaxPriority(int pri) 設置組的最大優先級。  

代碼代碼如下:

public class ThreadGroupDemo {
	public static void main(String[] args) {
		method2();
	}
	private static void method2() {
		// ThreadGroup(String name)
		ThreadGroup tg = new ThreadGroup("這是一個新的組");
		MyRunnable my = new MyRunnable();
		// Thread(ThreadGroup group, Runnable target, String name)
		Thread t1 = new Thread(tg, my, "林青霞");
		Thread t2 = new Thread(tg, my, "劉意");
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		//通過組名稱設置後臺線程,表示該組的線程都是後臺線程
		tg.setDaemon(true);
	}
 
}

七:線程池

    1,線程池概述

            A,爲什麼使用線程池:因爲每次啓動一個新的線程,系統都會給它開闢一個新的資源空間,會耗費資源,特別是在需要開闢大量的線程的時候,更是如此,所以使用線程池。

            B:線程池:系統會開闢一個空間,專門用於存在被執行之後的線程,等到下次需要調用的時候,無需重新創建線程就可以從線程池內建對象拿出來使用。

 2,實現

JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法

public static ExecutorService newCachedThreadPool() :創建具有緩存功能的線程

public static ExecutorService newFixedThreadPool(int nThreads):創建存貯多個線程的線程池

public static ExecutorService newSingleThreadExecutor():創建存貯單個線程的線程池

這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。它提供瞭如下方法

Future<?> submit(Runnable task) <T>

Future<T> submit(Callable<T> task)

實現代碼:

public class ExecutorsDemo {
	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(2);
 
		// 可以執行Runnable對象或者Callable對象代表的線程
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		//結束線程池
		pool.shutdown();
	}
}
 
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
 
}

3,實現原理(來源:瘋狂哈丘 

就是一個線程集合workerSet和一個阻塞隊列workQueue。當用戶向線程池提交一個任務(也就是線程)時,線程池會先將任務放入workQueue中。workerSet中的線程會不斷的從workQueue中獲取線程然後執行。當workQueue中沒有任務的時候,worker就會阻塞,直到隊列中有任務了就取出來繼續執行。
 

八,線程安全

       1,爲什麼會導致線程安全問題

             多線程環境,共享數據,對共享數據進行操作。

       2,解決線程安全問題

           A:  同步代碼塊

                      synchronized(對象){需要同步的代碼;}

           注意:可以是任意鎖,但是必須使用的是同一個對象。同步方法鎖對象爲;this;靜態同步方法鎖對象:類的字節碼文件對象。

         B:Lock鎖(接口):Lock void lock():加鎖; void unlock() :釋放鎖;ReentrantLock(子實現類);

              如果被加鎖的代碼塊出現異常,就不會在執行 unlock(),就無法釋放鎖,這時候需要將釋放鎖的操作早finally中,保證出問題還能將鎖釋放。

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();
	}}
public class SellTicket implements Runnable {
	// 定義票
	private int tickets = 100;
	// 定義鎖對象
	private Lock lock = new ReentrantLock();
	public void run() {
		while (true) {
			try {
				// 加鎖
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
					+ "正在出售第" + (tickets--) + "張票");
				}
			} finally {
				// 釋放鎖
				lock.unlock();
			}}}}

          3,死鎖問題: 是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象,例如同步代碼塊的嵌套

public class DieLockDemo {
	public static void main(String[] args) {
		DieLock dl1 = new DieLock(true);
		DieLock dl2 = new DieLock(false);

		dl1.start();
		dl2.start();
	}
}
public class DieLock extends Thread {

	private boolean flag;

	public DieLock(boolean flag) {
		this.flag = flag;
	}

	@Override
	public void run() {
		if (flag) {
			synchronized (MyLock.objA) {
				System.out.println("if objA");
				synchronized (MyLock.objB) {
					System.out.println("if objB");
				}
			}
		} else {
			synchronized (MyLock.objB) {
				System.out.println("else objB");
				synchronized (MyLock.objA) {
					System.out.println("else objA");
				}
			}
		}
	}
}
public class MyLock {
	// 創建兩把鎖對象
	public static final Object objA = new Object();
	public static final Object objB = new Object();
}

這時候,會出現相互等待的問題。

        解決方案:生產消費者模式之等待喚醒機制

生產方有資源,就通知並等待消費者來消費;消費者需要消費,就通知生產者生產。

 共享的資源:

共同資源
public class Student {
	private String name;
	private int age;
	private boolean flag; // 默認情況是沒有數據,如果是true,說明有數據
 
	public synchronized void set(String name, int age) {
		// 如果有數據,就等待
		if (this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
 
		// 設置數據
		this.name = name;
		this.age = age;
 
		// 修改標記
		this.flag = true;
		this.notify();
	}
 
	public synchronized void get() {
		// 如果沒有數據,就等待
		if (!this.flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
 
		// 獲取數據
		System.out.println(this.name + "---" + this.age);
 
		// 修改標記
		this.flag = false;
		this.notify();
	}
}

生產者:

public class SetThread implements Runnable {
 
	private Student s;
	private int x = 0;
 
	public SetThread(Student s) {
		this.s = s;
	}
	public void run() {
		while (true) {
			if (x % 2 == 0) {
				s.set("林青霞", 27);
			} else {
				s.set("劉意", 30);
			}
			x++;
		}}}

消費者:

 
public class GetThread implements Runnable {
	private Student s;
 
	public GetThread(Student s) {
		this.s = s;
	}
 
	@Override
	public void run() {
		while (true) {
			s.get();
		}
	}

測試:

 
public class StudentDemo {
	public static void main(String[] args) {
		//創建資源
		Student s = new Student();
		//設置和獲取的類
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		//線程類
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//啓動線程
		t1.start();
		t2.start();
	}
}

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章