目錄
緒論
一:執行緒安全問題
1.1 提出問題
1.2 不安全的原因
1.2.1 原子性
1.2.2 代碼“優化”
二:如何解決執行緒不安全的問題
2.1 通過synchronized關鍵字
2.2 volatile
三:wait和notify關鍵字
3.1 wait方法
3.2 notify方法
3.3 wait和sleep對比(面試常考)
四:多執行緒案例
4.1 餓漢模式單執行緒
4.2 懶漢模式單執行緒
4.3 懶漢模式多執行緒低性能版
4.4懶漢模式-多執行緒版-二次判斷-性能高
總結
緒論
上期介紹了多執行緒的概念、優勢、創建方法以及幾個常用的關鍵字,有了之前的基礎過后,我們來討論討論執行緒安全問題以及其他執行緒進階知識,
一:執行緒安全問題
1.1 提出問題
首先,給大家看一下這個代碼:
public class yy1 {
private static class Counter {
private long n = 0;
public void increment() {
n++;
}
public void decrement() {
n--;
}
public long value() {
return n;
}
}
public static void main(String[] args) throws InterruptedException {
final int COUNT = 1000_0000;
Counter counter = new Counter();
Thread thread = new Thread(() -> {
for (int i = 0; i < COUNT; i++) {
counter.increment();
}
}, "李四");
thread.start();
for (int i = 0; i < COUNT; i++) {
counter.decrement();
}
thread.join();
// 期望最終結果應該是 0
System.out.println(counter.value());
}
}
大家看結果:

大家觀察下是否適用多執行緒的現象是否一致?同時嘗試思考下為什么會有這樣的現象發生呢?
想給出一個執行緒安全的確切定義是復雜的,但我們可以這樣認為:
如果多執行緒環境下代碼運行的結果是符合我們預期的,即在單執行緒環境應該的結果,則說這個程式是執行緒安全的,
1.2 不安全的原因
1.2.1 原子性
舉個簡單的例子,當我i們買票的時候,如果車站剩余票數大于0,就可以買,反之,買完一張票后,車站的票數也會自動減一,假設出現這種情況,兩個人同時來買票,只剩最后一張票,前面那個人把最后一張票買了,但是短時間內票數還沒減一也就是清零,這時另外一個人看到還有一張票,于是提交訂單,但是其實已經沒有多余的票了,那么問題就來了,這時我們引入原子性:
1.2.2 代碼“優化”
二:如何解決執行緒不安全的問題
2.1 通過synchronized關鍵字
public class SynchronizedDemo {public synchronized static void methond() {}public static void main(String[] args) {method();// 進入方法會鎖 SynchronizedDemo.class 指向物件中的鎖;出方法會釋放SynchronizedDemo.class 指向的物件中的鎖}}
鎖的 SynchronizedDemo 類的物件
public class SynchronizedDemo {public synchronized static void methond() {}public static void main(String[] args) {method();// 進入方法會鎖 SynchronizedDemo.class 指向物件中的鎖;出方法會釋放SynchronizedDemo.class 指向的物件中的鎖}}
明確鎖的物件
public class SynchronizedDemo {public void methond() {// 進入代碼塊會鎖 this 指向物件中的鎖;出代碼塊會釋放 this 指向的物件中的鎖synchronized (this) {}}public static void main(String[] args) {SynchronizedDemo demo = new SynchronizedDemo();demo.method();}}
public class SynchronizedDemo {public void methond() {// 進入代碼塊會鎖 SynchronizedDemo.class 指向物件中的鎖;出代碼塊會釋放SynchronizedDemo.class 指向的物件中的鎖synchronized (SynchronizedDemo.class) {}}public static void main(String[] args) {SynchronizedDemo demo = new SynchronizedDemo();demo.method();}}
2.2 volatile
這里提一下volatile:
首先,被volatile關鍵字修飾的變數,編譯器與運行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作一起重排序,volatile變數不會被快取在暫存器或者對其他處理器不可見的地方,因此在讀取volatile型別的變數時總會回傳最新寫入的值,
在訪問volatile變數時不會執行加鎖操作,因此也就不會使執行執行緒阻塞,因此volatile變數是一種比sychronized關鍵字更輕量級的同步機制,當對非 volatile 變數進行讀寫的時候,每個執行緒先從記憶體拷貝變數到CPU快取中,如果計算機有多個CPU,每個執行緒可能在不同的CPU上被處理,這意味著每個執行緒可以拷貝到不同的 CPU cache 中,而宣告變數是 volatile 的,JVM 保證了每次讀變數都從記憶體中讀,跳過 CPU cache 這一步
三:wait和notify關鍵字
3.1 wait方法
public static void main(String[] args) throws InterruptedException {Object object = new Object();synchronized (object) {System.out.println(" 等待中 ...");object.wait();System.out.println(" 等待已過 ...");}System.out.println("main 方法結束 ...");}
3.2 notify方法
class MyThread implements Runnable {
private boolean flag;
private Object obj;
public MyThread(boolean flag, Object obj) {
super();
this.flag = flag;
this.obj = obj;
}
public void waitMethod() {
synchronized (obj) {
try {
while (true) {
System.out.println("wait()方法開始.. " +
Thread.currentThread().getName());
obj.wait();
System.out.println("wait()方法結束.. " +
Thread.currentThread().getName());
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void notifyMethod() {
synchronized (obj) {
try {
System.out.println("notifyAll()方法開始.. " +
Thread.currentThread().getName());
obj.notifyAll();
System.out.println("notifyAll()方法結束.. " +
Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if (flag) {
this.waitMethod();
} else {
this.notifyMethod();
}
}
}
public class TestThread {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThread waitThread1 = new MyThread(true, object);
MyThread waitThread2 = new MyThread(true, object);
MyThread waitThread3 = new MyThread(true, object);
MyThread notifyThread = new MyThread(false, object);
Thread thread1 = new Thread(waitThread1, "wait執行緒A");
Thread thread2 = new Thread(waitThread2, "wait執行緒B");
Thread thread3 = new Thread(waitThread3, "wait執行緒C");
Thread thread4 = new Thread(notifyThread, "notify執行緒");
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(1000);
thread4.start();
System.out.println("main方法結束!!");
}
}
3.3 wait和sleep對比(面試常考)
四:多執行緒案例
4.1 餓漢模式單執行緒
class Singleton {private static Singleton instance = new Singleton();private Singleton() {}public static Singleton getInstance() {return instance;}
4.2 懶漢模式單執行緒
class Singleton {private static Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
4.3 懶漢模式多執行緒低性能版
class Singleton {private static Singleton instance = null;private Singleton() {}public synchronized static Singleton getInstance() {if (instance == null) {instance = new Singleton();}return instance;}}
4.4懶漢模式-多執行緒版-二次判斷-性能高
class Singleton {private static volatile Singleton instance = null;private Singleton() {}public static Singleton getInstance() {if (instance == null) {synchronized (Singleton.class) {if (instance == null) {instance = new Singleton();}}}return instance;}}
總結
多執行緒的部分暫時分享到這里,但其實還有很多沒有沒有涉及 ,等日后深刻理解后再來分享,碼文不易,多謝大家支持,感激不盡!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/308740.html
標籤:java
上一篇:Java中的資料型別
