Java 多線程操作

以前一聽到多線程操作就感到好膩害好膩害的,如果你現在也是這種情況或許這篇文章能夠幫助到你。
這裏寫圖片描述

1、什麼是多線程?

先了解兩個概念
進程:正在運行的程序,是系統進行資源分配和調用的獨立單位,有自己的內存空間和系統資源。
線程:是進程中的單個順序控制流,是一條執行路徑,線程是應用程序中執行的基本單元。

某位大神的總結:進程就相當於工廠,線程就是工廠裏的流水線,線程不能獨立存在,必須存在於進程中。

多進程:系統中同時存在多個並行的進程,則稱爲多進程。可通過電腦任務管理器查看正在運行的進程,比如用電腦聊QQ的同時看電影,就是多進程的體現。
多線程:線程是進程中的單個順序控制流,是一條執行路徑,一個進程如果有多條執行路徑,則稱爲多線程。比如給某人聊QQ的同時還可以接收到其他人的消息。

2、多線程實現方式

1.繼承Thread類

Java提供了Thread類,讓我們對線程進行操作。

    class MyThread extends Thread {
    
	@Override
	public void run() {
		/* 多線程執行的邏輯 */
		super.run();
	}
}
    //開啓線程
    new MyThread().start();

2.實現Runable接口

   class MyRunnable implements Runnable {
   
	@Override
	public void run() {
		/* 多線程執行的邏輯 */
	}
}

  //將實現了Runnable接口的類,以參數的形式傳遞給Thread類
  new Thread(new MyRunnable()).start();

3.實現Callable接口

這是一種有返回值的線程,但是必須通過線程池使用。

	public class Test {

	public static void main(String[] args) {

		// 生成線程池對象
		ExecutorService pool = Executors.newCachedThreadPool();
		// 提交Callable線程
		pool.submit(new MyCallable());
	}
}

   class MyCallable implements Callable<String> {

	// 通過指定範型 可以通過線程方法返回該類型的數據 而其他兩種實現方式都沒有返回值
	@Override
	public String call() throws Exception {
		/* 多線程執行的邏輯 */
		return "hello";
	}
}

3、線程調度

分時調度模型 :所有線程輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間片。
搶佔式調度模型:搶佔CPU使用權,其中優先級高的線程搶到CPU執行權的概率會越大,存在很大隨機性。Java採用的是搶佔式調度模型。

查看搶佔式調度效果:

    public class Test {

	public static void main(String[] args) throws InterruptedException {

		// 生成線程對象
		MyThread thread = new MyThread();
		MyThread thread2 = new MyThread();
		// 啓動線程
		thread.start();
		thread2.start();
		
		//第一次輸出      //第二次輸出
		//Thread-0:0     //Thread-1:0
        //Thread-1:0     //Thread-0:0
        //Thread-1:1     //Thread-1:1
        //Thread-1:2     //Thread-0:1
        //Thread-0:1     //Thread-0:2
        //Thread-0:2     //Thread-1:2
	}
}

   // 線程類
   class MyThread extends Thread {
   
	@Override
	public void run() {
		for (int i = 0; i <3; i++) {
			// 輸出線程名和i的值
			System.out.println(this.getName() + ":" + i);
		}
		super.run();
	}
}

通過輸出可以看出線程搶佔是隨機的無規律可言(不設置優先級的情況下)。

如何設置線程優先級呢?

        //Thread提供了相關方法
   		// 設置線程的優先級
		void setPriority(int newPriority)
		// 返回線程的優先級
		int getPriority()
		
	    // 生成線程對象
		MyThread thread = new MyThread();
		// 獲取默認線程優先級
		int priority = thread.getPriority();
		System.out.println(priority);// 5
		// 設置線程優先級
		thread.setPriority(6);
		// 輸出最小線程優先級
		System.out.println(thread.MIN_PRIORITY);// 1
		// 輸出最大線程優先級
		System.out.println(thread.MAX_PRIORITY);// 10

可以看出線程的優先級的是1-10

4、線程控制

Thread類提供了相關方法對線程進行控制。

1.線程休眠sleep

    // 生成線程對象
	MyThread thread = new MyThread();
	//線程休眠3000毫秒
	thread.sleep(3000);
	//獲取當前線程對象並設置其休眠200毫秒2000納秒
	Thread.currentThread().sleep(200, 2000);

2.線程加入join

   		public static void main(String[] args) throws InterruptedException {

		// 生成線程對象
		MyThread thread = new MyThread("線程一");
		MyThread thread2 = new MyThread("線程二");
		// 啓動線程
		thread.start();
		// 線程加入 當調用了線程加入了之後在該放後啓動的線程會等這個線程執行完 在搶佔CPU執行權
		thread.join();
		thread2.start();
		
		//未添加前輸出
		//		線程一:0
		//		線程一:1
		//		線程二:0
		//		線程一:2
		//		線程二:1
		//		線程二:2
		
		//添加後輸出
		//		線程一:0
		//		線程一:1
		//		線程一:2
		//		線程二:0
		//		線程二:1
		//		線程二:2

	}
}

    // 線程類
    class MyThread extends Thread {

	public MyThread(String name) {
		// 設置線程名
		this.setName(name);
	}

	@Override
	public void run() {
		for (int i = 0; i < 3; i++) {
			// 輸出線程名和i的值
			System.out.println(this.getName() + ":" + i);
		}
		super.run();
	}
}

3.線程禮讓yield

   		// 生成線程對象
		MyThread thread = new MyThread("線程一");
		MyThread thread2 = new MyThread("線程二");
		//暫停當前正在執行的線程對象,並執行其他線程,讓線程執行更和諧
		thread.yield();
		// 啓動線程
		thread.start();
		thread2.start();
		
		//輸出
		//		線程一:0
		//		線程二:0
		//		線程一:1
		//		線程二:1
		//		線程二:2
		//		線程一:2

4.線程守護setDaemon

   		// 生成線程對象
		MyThread thread = new MyThread("線程一");
		MyThread thread2 = new MyThread("線程二");
		//將該線程標記爲守護線程或用戶線程  在啓動線程前調用
		//設置thread爲守護線程   即當線程thread2執行完成後,如果thread未執行完則不再繼續執行
		//因爲線程具有隨機性,所以存在thread在thread2執行完的情況
		thread.setDaemon(true);
		// 啓動線程
		thread.start();
		thread2.start();

5.線程中斷stop、interrupt

   		// 生成線程對象
		MyThread thread = new MyThread("線程一");
		MyThread thread2 = new MyThread("線程二");
		// 啓動線程
		thread.start();
		thread2.start();

		// 立即終止某個線程,沒有任何反饋,存在不安全性
		//thread.stop();
		
		// 中斷線程
		thread2.interrupt();
		//輸出:
		//線程二線程終止!!!
        //線程二------------

		// 線程類
		class MyThread extends Thread {

			public MyThread(String name) {
				// 設置線程名
				this.setName(name);
			}

			@SuppressWarnings("static-access")
			@Override
			public void run() {
				try {
					//當前線程休眠3秒
					this.sleep(3000);
				} catch (InterruptedException e) {
					System.out.println(this.getName()+"線程終止!!!");
				}
				System.out.println(this.getName()+"------------");
				super.run();
			}
		}

stop()方法會立刻終止線程並沒有任何反饋,而interrupt()會有後續的輸出。

5、線程生命週期

圖示:
這裏寫圖片描述

6、線程安全問題

1.線程安全問題

模擬賣票情況:兩個線程對同一數據進行操作

    public class Test {

	public static void main(String[] args) throws InterruptedException {

		MyRunnable runnable = new MyRunnable();

		new Thread(runnable, "賣票口一:").start();
		new Thread(runnable, "賣票口二:").start();

	}
}

	// 線程類
	class MyRunnable implements Runnable {

		private int ticket = 100;

		@Override
		public void run() {
			// 多次執行
			while (true) {
				if (ticket > 0) {

					try {
						// 線程休眠模擬網絡延遲
						Thread.sleep(300);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "張票");
				}
			}
		}

	}

部分輸出情況:
這裏寫圖片描述
這裏寫圖片描述

1.出現同票情況?根據輸出可以推斷,當ticket爲40的時候,賣票窗口一線程搶到CPU執行權,並執行,但是執行run方法輸出語句的時候, ticket–只執行完賦值操作並未執行–操作的時候,CPU執行權被賣票窗口二搶到,輸出賣票窗口二:正在出售第40張票,然後賣票窗口一又搶到了CPU執行權輸出賣票窗口一:正在出售第40張票。

2.出現0票情況?根據輸出可以推斷,當ticket爲1的時候,賣票窗口一線程搶到CPU執行權,並執行,但是執行run方法未執行到輸出語句的時候,CPU執行權並執行完run方法輸出賣票窗口二:正在出售第1張票,然後賣票窗口一又搶到CPU執行權,此刻ticket已經爲0了, 所以輸出賣票窗口一:正在出售第0張票。

注:一般多線程環境、存在共享數據、多條語句操作共享數據的情況下會出現線程安全問題。那麼如何解決呢?就用到了線程同步。

2.2.線程同步

1.同步代碼塊

 class MyRunnable implements Runnable {

	private int ticket = 100;

	@Override
	public void run() {
		// 多次執行
		while (true) {
			// 同步代碼塊(添加任意對象)
			synchronized (this) {
				if (ticket > 0) {
					try {
						// 線程休眠模擬網絡延遲
						Thread.sleep(300);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + ticket-- + "張票");
				}
			}
		}
	}

}

注:同步代碼塊格式synchronized(對象){同步代碼;};同步可以解決安全問題的根本原因就在那個對象上,該對象就是鎖,多個線程操作同一代碼塊,一定要保證那個鎖對象相同。
###2.同步方法
就是在方法上同步關鍵字。

  class MyRunnable implements Runnable {

		private int ticket = 100;

		@Override
		public void run() {
			// 多次執行
			while (true) {
				sellTicket();
			}
		}

		/**
		 * 同步方法
		 */
		private synchronized void sellTicket() {
			if (ticket > 0) {
				try {
					// 線程休眠模擬網絡延遲
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ ticket-- + "張票");
			}
		}
	   
		/**
		 * 同步靜態方法
		 */
		private static synchronized void sellTicket() {
			if (ticket > 0) {
				try {
					// 線程休眠模擬網絡延遲
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticket-- + "張票");
			}
		}

注:同步方法的鎖對象其實是當前對象this,同步靜態方法的鎖對象是當前類的字節碼文件。

3.Lock鎖的使用

JDK5提供了Lock鎖接口,提供了比使用synchronized方法和語句可獲得的更廣泛的鎖定操作。

         // 獲取鎖
	     void lock()
	     // 釋放鎖
	     void unlock()
class MyRunnable implements Runnable {

	private int ticket = 100;
	//實現類對象實例化鎖接口
	Lock lock = new ReentrantLock();

	@Override
	public void run() {
		// 多次執行
		while (true) {
			// 獲取鎖
			lock.lock();

			if (ticket > 0) {
				try {
					// 線程休眠模擬網絡延遲
					Thread.sleep(000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第"
						+ ticket-- + "張票");
			}
			// 釋放鎖
			lock.unlock();
		}
	}

線程同步總結:

7、線程死鎖問題

死鎖就是線程間因相互等待對方資源,而不能繼續執行的情況。線程同步嵌套就非常容易產生死鎖問題。

修改之前的代碼:

 class MyRunnable implements Runnable {

	private int ticket = 100;
	// 鎖對象1
	private Object object1 = new Object();
	// 鎖對象2
	private Object object2 = new Object();

	@Override
	public void run() {
		// 多次執行
		while (true) {

			if (ticket > 0) {

				if (ticket % 2 == 0) {
					synchronized (object1) {// 這裏出現線程死鎖
						synchronized (object2) {
							System.out.println(Thread.currentThread().getName()
									+ "正在出售第" + ticket-- + "張票");
						}
					}
				} else {
					synchronized (object2) {// 這裏出現線程死鎖
						synchronized (object1) {
							System.out.println(Thread.currentThread().getName()
									+ "正在出售第" + ticket-- + "張票");
						}
					}
				}

			}
		}
	}

該程序因爲進行了同步嵌套所以會線程死鎖問題,會出現輸出不全的情況。

注:那麼如何解決死鎖問題呢?給資源排序,在所有的線程中,決定次序並始終遵照這個次序獲取鎖。

8、線程間通信問題

1.生產消費模式

多線程間除了線程安全問題外,還存在線程間通信問題。比如線程A執行完,操作了某些數據,而線程B需要這些數據做某些操作,這時線程A需要通知線程B已經有數據了,然後線程B做某些操作,當沒有數據時線程B需要反饋給線程A。線程A和線程B之間就冥冥中存在了聯繫,這就是生成消費模式。
這個類似於Android中的Work線程和UI線程通過Handler通信一樣,而java則提供了等待喚醒機制進行線程通信。

2.等待喚醒機制

Object類提供了notify、wait方法,對線程進行喚醒和等待。

 	// 喚醒在此對象監視器上等待的單個線程
	void notify()
	// 喚醒在此對象監視器上等待的所有線程
	void notifyAll()
	// 在其他線程調用此對象的notify()方法或 notifyAll()方法前,導致當前線程等待
	void wait()
	// 在其他線程調用此對象的notify()方法或 notifyAll()方法,或者超過指定的時間量前,導致當前線程等待
	void wait(long timeout)
	public class User {

	String name;
	int age;

	boolean b = false;// 判斷是否有數據

	// 設置數據
	public synchronized void setUser(String name, int age)
			throws InterruptedException {

		if (this.b) {
			//如果存在數據就等待
			// 線程等待
			this.wait();
		} else {
			//沒有數據則設置數據並喚醒某個線程
			setName(name);
			setAge(age);
			this.b = true;
			// 喚醒等待的單個線程
			this.notify();
		}
	}

	// 獲取數據
	public synchronized void getUser() throws InterruptedException {
		if (this.b) {
			// 相當於消費數據
			System.out.println(getName() + ":" + getAge());
			// 消費後相當資源消失
			this.b = false;
			// 喚醒線程
			this.notify();
		} else {
			// 線程等待
			this.wait();
		}
	}

	/** 實現get set方法 **/
}

	public class Test {

		public static void main(String[] args) throws InterruptedException {

			User user = new User();
			SetUserThread setUserThread = new SetUserThread(user);
			GetUserThread getUserThread = new GetUserThread(user);

			getUserThread.start();
			setUserThread.start();

		}
	}

	class GetUserThread extends Thread {

		private User user;

		public GetUserThread(User user) {
			super();
			this.user = user;
		}

		@Override
		public void run() {

			while (true) {

				try {
					user.getUser();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				super.run();
			}
		}

	}

	class SetUserThread extends Thread {

		private User user;

		public SetUserThread(User user) {
			super();
			this.user = user;
		}

		@Override
		public void run() {
			while (true) {
				try {
					user.setUser("Hello", 18);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				super.run();
			}
		}

	}

9、線程組的使用

Java提供了線程組ThreadGroup對線程進行統一管理和調度。
ThreadGroup:線程組就是一個線程的集合。

   		// 生成線程組對象,並設置名字
		ThreadGroup threadGroup = new ThreadGroup("線程組一");

		// 使用Thread的構造設置線程歸宿的線程組
		new Thread(threadGroup, new MyRunnable(), "線程一").start();
		new Thread(threadGroup, new MyRunnable(), "線程二").start();
		new Thread(threadGroup, new MyRunnable(), "線程三");

		// 獲取線程組名字
		System.out.println(threadGroup.getName());// 線程組一
		// 獲取線程組活動線程的估計數
		Thread[] threads = new Thread[threadGroup.activeCount()];
		// 線程組中的所有活動線程複製到指定數組中
		threadGroup.enumerate(threads);
		System.out.println(threads.length);//運行多次輸出 0 1 2
		//通過輸出可以看到 threadGroup.activeCount()結果所固有的不精確特性
		// 中斷線程組中所有線程
		threadGroup.interrupt();

10、線程池的使用

線程池簡述:開啓一條線程是非常浪費資源的,因爲它涉及到要與操作系統進行交互;因此JDK5之後Java提供了線程池讓我們提高性能,線程池裏的線程執行完後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。

相關對象
Executors:創建線程池的工廠類。

    創建方法:
	// 創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們
	static ExecutorService newCachedThreadPool()
	// 創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程
	static ExecutorService newFixedThreadPool(int nThreads)
	// 創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們,並在需要時使用提供的ThreadFactory創建新線程
	static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)
	// 創建一個可重用固定線程數的線程池,以共享的無界隊列方式來運行這些線程,在需要時使用提供的ThreadFactory創建新線程
	static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory)
	// 創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行
	static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
	// 創建一個線程池,它可安排在給定延遲後運行命令或者定期地執行
	static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory)
	//創建一個使用單個worker線程的Executor,以無界隊列方式來運行該線程
	public static ExecutorService newSingleThreadExecutor()

ExecutorService:線程池管理接口,提供了線程的操作方法。

    // 啓動一次順序關閉,執行以前提交的任務,但不接受新任務
	void shutdown()
	// 試圖停止所有正在執行的活動任務,暫停處理正在等待的任務,並返回等待執行的任務列表
	List<Runnable> shutdownNow()
	// 提交一個返回值的任務用於執行,返回一個表示任務的未決結果的 Future
	<T> Future<T> submit(Callable<T> task)
	// 提交一個 Runnable任務用於執行,並返回一個表示該任務的 Future
	Future<?> submit(Runnable task)
	// 提交一個 Runnable任務用於執行,並返回一個表示該任務的 Future
	<T> Future<T> submit(Runnable task, T result)
        ExecutorService threadpool= Executors.newCachedThreadPool();
		//提交任務
		threadpool.submit(new Runnable() {
			
			@Override
			public void run() {
				/**線程中執行邏輯**/
			}
		});
        
		//這是實現線程的第三種方式
		threadpool.submit(new Callable<String>() {

			@Override
			public String call() throws Exception {
				/**線程中執行邏輯**/
				return null;
			}
		});

		//停止任務
		threadpool.shutdown();

注:線程池可以很大程度上提高性能,如存在多線程環境建議使用 。

更多方法查看API

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