Java 併發(入門梳理)

>多線程創建方式

繼承一個類 Thread

或者實現一個接口 Runnable

重寫其中的 run() 方法(對於實現接口的形式,並沒有繼承run() 方法,要通過構造扔到一個Thread對象中去運行)

使用 start() 方法

 

>原子性操作

多線程環境下,如果一個操作不是原子性操作,那麼需要考慮它的安全性。

什麼是原子性操作?

不可分割的操作

++i;是不是原子性操作?

不是,在 jvm 中分爲三步:取出 i 的值;讓 i 的值加一; 將自加後的值賦給 i 。

 

>舉個例子,四個線程,每個線程加一萬次

public class ReTh {

	int i = 0;
        //加鎖,因爲 i 是非原子性操作,可能第一線程自加未賦值,後續線程又進行了操作
	public synchronized void add() {
		++i;
	}

	public static void main(String[] args) {
		ReTh rt = new ReTh();
		for (int i = 0; i < 4; i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int i = 0; i < 10000; i++) {
						rt.add();
					}
				}
			}).start();
		}

		while (Thread.activeCount() > 1) {
                //等待,併發就是多個分支一併往下走,如果這裏不等待,主線程就在四個線程沒加完
                //的時刻就進行了打印 i,得不到40000
		}

		System.out.println(rt.i);//40000
	}

}

 

>線程控制

首先要明確,併發的線程,搶佔式運行。

三種線程控制:

join()  加塞:

一個線程過來,有"急事",讓他獨立佔用cpu,等它執行完了,我再執行,我們並不是並行關係。

sleep()  睡眠 :

讓當前線程睡眠多久。當前線程醒來的時候,是重新競爭CPU,還是?

當這個線程"睡覺"的時候,是抱着cpu"睡覺",醒來的時候直接獲得執行權力。

yield()  讓步/放棄:

當一個線程獲得cpu執行權力,調用yield,放棄執行權力,退一步,大家再重新搶。

 

>線程的生命週期

新建:當我們創建了線程對象的時候。

就緒:當我們調用了線程的 start 方法之後。

運行:當獲取了cpu的時候

阻塞:單說。

死亡:運行完。或者運行的時候拋出異常。

 

>阻塞

線程進入阻塞狀態的情況:

  • 線程調用了一個阻塞方法,方法返回之前該線程一直阻塞。
  • 線程調用 sleep() 方法進入阻塞。
  • 線程嘗試獲取 同步監視器 ,但該同步監視器被其他線程持有。
  • 線程調用 suspend() 方法掛起。

 

>線程優先級(不可靠,不要依賴優先級控制線程)

new Thread().setPriority(Thread.MAX_PRIORITY);

 

>線程安全(重點)

多個線程操作的同一個類的同一個對象時,會存在線程安全問題。

解決:同步代碼塊/同步方法

public class TestSafe {

	V v = new V();

	public void go() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
				}

			}
		}).start();

		new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true) {
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
				}

			}
		}).start();
	}

	class V {

		public synchronized void printString(String s) {
			/**
			 * 假設這裏有大量業務代碼,如何降低 鎖 的粒度?
			 */
			synchronized (String.class) {
				
			}
			for (int i = 0; i < s.length(); i++) {
				System.out.print(s.charAt(i));
			}
			System.out.println();
			
			/**
			 * 假設這裏有大量業務代碼
			 */
		}
	}

	public static void main(String[] args) {
		TestSafe t = new TestSafe();
		t.go();
	}

}

 

>wait / notify,notifyAll(重點)

1.首先明確一點,這三個方法不是Thread類的方法,是Object類的native方法。

2.要調用這些方法,必須獲得同步監視器(鎖),否則會報異常(換句話說我們線程在調用這些方法的地方,要有synchronized關鍵字,這樣執行,就會獲取鎖)。

補充:

1)靜態方法上的鎖

靜態方法是屬於“類”,不屬於某個實例,是所有對象實例所共享的方法。也就是說如果在靜態方法上加入synchronized,那麼它獲取的就是這個類的鎖,鎖住的就是這個類。

2)實例方法(普通方法)上的鎖,鎖住的就是這個對象實例,synchronized(this){}也是鎖的對象。

3.wait 的意思:等待時機成熟再執行,否則就等待。

再執行的時候,必須被別的線程喚醒,它自己醒不了。

4.notify/notifyAll ,專門喚醒等待隊列中的線程(隨機喚醒某一個/喚醒所有的),喚醒之後再判斷條件是否滿足。

 

>輪詢執行的問題

這種問題的思路:同一時刻只能讓一個線程在運行,其他線程wait() 等待喚醒,如果同一時刻多個線程搶佔CPU就失去了控制;線程的運行和喚醒要通過額外的 條件/flag 進行輪詢的控制。

寫一個程序,讓兩個線程輪詢執行 ?

public class TestWaitNotify {
	public static void main(String[] args) {
		TestWaitNotify t = new TestWaitNotify();
		t.go();
	}

	V v = new V();

	private void go() {
		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					v.f1();
				}
			}
		}, "A線程").start();

		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					v.f2();
				}
			}
		}, "B線程").start();

	}

	class V {
		public boolean flag=true;
		
		public synchronized void f1() {
			while(flag){
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("F1 " + Thread.currentThread().getName());
			flag=true;
			notifyAll();
		}

		public synchronized void f2() {
			while(!flag){
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("F2 " + Thread.currentThread().getName());
			flag=false;
			notifyAll();
		}
	}
}

三個線程輪詢執行?

public class ThreeThreadInTurn {
	public static void main(String[] args) {
		ThreeThreadInTurn t = new ThreeThreadInTurn();
		t.go();
	}

	V v = new V();

	public void go() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					v.f1();
				}
			}
		}, "線程1").start();

		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					v.f2();
				}
			}
		}, "線程2").start();

		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					v.f3();
				}
			}
		}, "線程3").start();
	}

	class V {
		int flag = 1;

		public synchronized void f1() {
			while (flag != 1) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("F1 " + Thread.currentThread().getName());
			flag = 2;
			notifyAll();
		}

		public synchronized void f2() {
			while (flag != 2) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("F1 " + Thread.currentThread().getName());
			flag = 3;
			notifyAll();
		}

		public synchronized void f3() {
			while (flag != 3) {
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("F1 " + Thread.currentThread().getName());
			flag = 1;
			notifyAll();
		}
	}

}

 

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