死鎖
死鎖是指兩個或兩個以上的執行緒在執行程序中,由于競爭同步鎖而產生的一種阻塞的現象,若無外力作用,它們都將無法推進下去,此時稱系統處于死鎖狀態或系統產生了死鎖,這些永遠在互相等待的執行緒稱為死鎖,
死鎖的案例 : 同步代碼塊的嵌套
創建鎖物件:
public class Lock {
public static final Lock lockA = new Lock();
public static final Lock lockB = new Lock();
}
測驗類:
public class DeadLockTest {
public static void main(String[] args) {
while(true){
new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock.lockA){
System.out.println("getlockA...");
synchronized (Lock.lockB){
System.out.println("getlockB...");
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (Lock.lockB){
System.out.println("getlockB...");
synchronized (Lock.lockA){
System.out.println("getlockA...");
}
}
}
}).start();
}
}
}
生產者與消費者
創建2個執行緒,一個執行緒表示生產者,另一個執行緒表示消費者
import java.util.ArrayList;
import java.util.List;
public class Demo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
Object o = new Object();
// 生產者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (o) {
if (list.size() > 0) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("aaaa");
System.out.println(list);
// 喚醒消費執行緒
o.notify();
}
}
}
}).start();
// 消費者
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (o) {
if (list.size() == 0) {
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove(0);
System.out.println(list);
o.notify();
}
}
}
}).start();
}
}
執行緒方法sleep和wait的區別
- sleep()是Thread類靜態方法,不需要物件鎖,
- wait()方法是Object類的方法,被鎖物件呼叫,而且只能出現在同步中,
- 執行sleep()方法的執行緒不會釋放同步鎖,
- 執行wait()方法的執行緒要釋放同步鎖,被喚醒后還需獲取鎖才能執行,
案例性能問題
wait()方法和notify()方法, 本地方法呼叫OS的功能,和作業系統互動,JVM找OS,把執行緒停止. 頻繁等待與喚醒,導致JVM和OS互動的次數過多.
Condition介面
java.util.concurrent.locks.Condition
是一個介面類, 因此要使用其實作類創建物件
Condition 將 Object 監視器方法(wait、notify 和 notifyAll)分解成截然不同的物件
以便通過將這些物件與任意 Lock 實作組合使用,為每個物件提供多個等待 set(wait-set)
其中,Lock 替代了 synchronized 方法和陳述句的使用,Condition 替代了 Object 監視器方法的使用
// Condition常用方法:
public void await() // 執行緒等待
public void signal() // 喚醒一個等待的執行緒
public void singalAll() // 喚醒所有等待的執行緒
// 使用其實作類 ReentrantLock 的 newCondition方法獲取 Condition
public Condition newCondition()
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo {
public static void main(String[] args) throws InterruptedException {
// 創建Lock
Lock l = new ReentrantLock();
// 獲取 Condition 物件
Condition con = l.newCondition();
new Thread(new Runnable() {
@Override
public void run() {
l.lock();
System.out.println("開始等待");
try {
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
l.unlock();
}
}
}).start();
Thread.sleep(2000);
System.out.println("準備喚醒");
l.lock();
con.signal();
l.unlock();
}
}
Condition介面方法和Object類方法比較
- Condition可以和任意的Lock組合,也就是實作了執行緒的分組管理,
- 一個執行緒的案例中,可以使用多個Lock鎖,每個Lock鎖上可以結合Condition物件
- synchronized同步中做不到執行緒分組管理
- Object類wait()和notify()都要和作業系統互動,并通知CPU掛起執行緒,喚醒執行緒,效率低,
- Condition介面方法await()不和作業系統互動,而是讓執行緒釋放鎖,并存放到執行緒佇列容器中,當被signal()喚醒后,從佇列中出來,從新獲取鎖后在執行,
- 因此使用Lock和Condition的效率比Object要快很多
生產者和消費者案例改進
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class OverWriteWakeUpWaiting {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Lock l = new ReentrantLock();
// 對執行緒進行分組管理
Condition con1 = l.newCondition(); // 生產執行緒, 物件監視器
Condition con2 = l.newCondition(); // 消費執行緒, 物件監視器
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
l.lock();
if (list.size() > 0) {
try {
con1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.add("abc");
System.out.println(list);
con2.signal();
l.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
l.lock();
if (list.size() == 0) {
try {
con2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
list.remove(0);
System.out.println(list);
con1.signal();
l.unlock();
}
}
}).start();
}
}
java并發編程的三大特性
原子性
原子性,即一個操作或多個操作,要么全部執行并且在執行的程序中不被打斷,要么全部不執行
下面具有原子性的操作有?
x = 1;
// x = 1,是一個單純的賦值操作,滿足原子性,
y=x;
// 實際是兩個操作,分別是 讀取x變數 ,將x賦值給y,這兩個操作分別來看都是原子性的,但是合起來就不是了
x++;
// 實際是三個操作 ,先讀取變數 ,在進行+1操作 ,再賦值給x,不滿足原子性
x=x+1;
// 同上,不滿足原子性
JAVA提供了原子性的技術保障有如下:
1、synchronized (互斥鎖)
2、Lock(互斥鎖)
3、原子類(CAS)
synchronized 和 Lock 都是通過互斥鎖實作,即同一時刻只允許一個執行緒操作該變數,保障了原子性
原子類AtomicInteger
/*
java.util.concurrent.atomic.AtomicInteger
構造方法
public AtomicInteger()創建具有初始值 0 的新 AtomicInteger,
public AtomicInteger(int initialValue) 創建具有給定初始值的新 AtomicInteger,
方法
int incrementAndGet() 以原子方式將當前值加 1,
int getAndIncrement() 以原子方式將當前值加 1,
int decrementAndGet() 以原子方式將當前值減 1,
int getAndIncrement() 以原子方式將當前值減 1,
int getAndAdd(int delta) 以原子方式將給定值與當前值相加,
int addAndGet(int delta) 以原子方式將給定值與當前值相加,
int get() 獲取當前值,
*/
public class Test02 {
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(1);
ai.incrementAndGet(); //++ai 2
ai.getAndIncrement(); //ai++ 3
System.out.println(ai.get());// 3
System.out.println(ai.getAndIncrement()); // 3
System.out.println(ai.get()); // 4
System.out.println(ai.incrementAndGet()); // 5
System.out.println(ai.get()); //5
}
}
CAS無鎖機制
CAS是英文單詞Compare And Swap的縮寫,翻譯過來就是比較并替換,當多條執行緒嘗試使用CAS同時更新同一個變數時,只有其中一條執行緒能更新變數的值,而其他執行緒都失敗,失敗的執行緒并不會被掛起,而是告知這次競爭失敗,并可以再次嘗試.
CAS的缺點:
CAS雖然很高效的解決了原子操作問題,但是CAS仍然存在三大問題,
-
回圈時間長開銷很大,
CAS 通常是配合無限回圈一起使用的,如果 CAS 失敗,會一直進行嘗試,如果 CAS 長時間一直不成功,可能會給 CPU 帶來很大的開銷,
-
只能保證一個變數的原子操作,
當對一個變數執行操作時,我們可以使用回圈 CAS 的方式來保證原子操作,但是對多個變數操作時,CAS 目前無法直接保證操作的原子性,
-
ABA問題,
第一條執行緒獲取到V位置的值 假設是 1 第二條執行緒獲取到V位置的值 也是1 第一條執行緒cas成功 將值改為 0 第一條執行緒又cas成功 將值改回 1 這時第二條執行緒cas 發現值沒變 還是1 cas成功 實際上當第二條執行緒cas時 V位置的值已經從 1-0-1 這就是ABA問題 如何解決 每次獲取V位置的值時,帶上一個版本號.這樣就可以避免ABA問題 java中AtomicStampedReference這個類在cas時就是通過版本號來解決的
可見性
當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒應該能夠立即看得到修改的值
public class Test {
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1號執行緒啟動....執行while回圈");
long num = 0;
while(flag){
num++;
}
System.out.println(num);
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2號執行緒啟動....修改flag的值為false,停止回圈");
flag = false;
}
}).start();
}
}
通過如上案例 發現修改flag 的值并沒有使回圈結束
1.加鎖,比如使用synchronized.
JMM關于synchronized的兩條規定:
1)執行緒解鎖前,必須把共享變數的最新值重繪到主記憶體中
2)執行緒加鎖時,將清空作業記憶體中共享變數的值,從而使用共享變數時需要從主記憶體中重新獲取最新的值
public class Test {
//使用同步方法獲取flag的值
public static synchronized boolean getFlag(){
return flag;
}
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1號執行緒啟動....執行while回圈");
long num = 0;
/*
執行緒呼叫getFlag方法時 先獲取鎖 也就是加鎖
這時會先清空本地記憶體中共享副本的值,那么在使用值就需要從
主記憶體中重新獲取 ,執行緒釋放鎖時,也就是解鎖,會把共享變數flag
的值重新更新到主記憶體中
*/
while(getFlag()){
num++;
}
System.out.println(num);
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2號執行緒啟動....修改flag的值為false,停止回圈");
flag = false;
}
}).start();
}
}
2.使用volatile關鍵字保證可見性
public class Test {
public static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("1號執行緒啟動....執行while回圈");
long num = 0;
while(flag){
num++;
}
System.out.println(num);
}
}).start();
Thread.sleep(2000);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("2號執行緒啟動....修改flag的值為false,停止回圈");
flag = false;
}
}).start();
}
}
volatile快取可見性實作原理
底層實作主要是通過匯編lock前綴指令,會鎖住這塊區域的快取,并寫回主記憶體.
1.會將當前處理器快取的行資料立即寫回系統記憶體
2.這個寫回記憶體的操作導致CPU的快取該記憶體地址的數值失效(MESI協議)
volatile只能保證可見性,但是不能保證原子性,如果要保證原子性,請使用鎖
有序性
一般來說,程式的執行順序按照代碼的先后順序執行.但是處理器為了提高程式的效率,可能會對代碼的執行順序進行優化,它不保證程式中各個陳述句的執行先后順序一致,但是保證程式的最終結果和代碼順序執行的結果一致.
int a = 10; //陳述句1
int b = 20; //陳述句2
int c = 20; //陳述句3
c= a + b; //陳述句4
CPU可能會對沒有依賴關系的陳述句進行重排,比如 2134,3124 但是不會對有依賴關系的資料進行重排比如 3和4 改為4和3 這樣就會對結果造成影響.這種重排對單執行緒是沒有任何影響的,但是如果是多執行緒就可能會出現問題.
驗證CPU是否會進行指令重排:
public class Test {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 500000; i++) {
Test.State state = new Test.State();
ThreadA t1 = new ThreadA(state);
ThreadB t2 = new ThreadB(state);
t1.start();
t2.start();
}
}
static class ThreadA extends Thread{
private final Test.State state;
ThreadA(Test.State state){
this.state =state;
}
public void run(){
state.a=1;
state.b=1;
state.c=1;
state.d=1;
}
}
static class ThreadB extends Thread{
private final Test.State state;
ThreadB(Test.State state){
this.state =state;
}
public void run(){
if( state.b== 1 && state.a ==0){
System.out.println("b= " + state.b);
}
if(state.c == 1 &&(state.b==0|| state.a ==0)){
System.out.println("c = " + state.c);
}
if(state.d==1 &&(state.a==0||state.b==0||state.c==0)){
System.out.println("d " + state.d);
}
}
}
static class State{
int a = 0;
int b = 0;
int c = 0;
int d = 0;
}
}
/*
c = 1
說明,CPU進行了重排,讓c在b或者a前面進行了賦值.
改變順序可能導致執行結果不同,因此需要禁止重排序,
*/
使用volatile關鍵字后 就不會出現剛才的情況
static class State{
volatile int a = 0;
volatile int b = 0;
volatile int c = 0;
volatile int d = 0;
}
由此可見:volatile關鍵字有兩個作用1.保證可見性.2禁止重排序
單例設計模式
設計模式 : 不是技術,是以前的人開發人員,為了解決某些問題實作的寫代碼的經驗.
Java的設計模式有23種,分為3個類別,創建型,行為型,功能型
單例代表單個實體,保證一個類的物件永遠只有一個!
餓漢式
優點: 簡單 多執行緒下沒有任何問題
缺點:
- 當類加載時 物件就會被直接創建
- 若不被使用 物件就白創建了
public class danliDemo1 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Single1.getInstance());
}
}).start();
}
}
}
class Single1 {
private static Single1 s = new Single1();
public Single1() {
}
public static Single1 getInstance(){
return s;
}
}
懶漢式
優點: 延遲加載 什么時候呼叫方法 什么時候創建物件
缺點: 多執行緒時 代碼有問題
public class danliDemo2 {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Single2.getInstance());
}
}).start();
}
}
}
class Single2 {
private static Single2 s;
public Single2() { }
public static Single2 getInstance(){
if (s == null) {
s = new Single2();
}
return s;
}
}
安全問題
一個執行緒判斷完變數 s=null,還沒有執行new物件,被另一個執行緒搶到CPU資源,同時有2個執行緒都進行判斷變數,物件創建多次
性能問題
第一個執行緒獲取鎖,創建物件,回傳物件. 第二個執行緒呼叫方法的時候,變數s已經有物件了,根本就不需要在進同步,不要在判斷空,直接return才是最高效的.
雙重的if判斷,提高效率 Double Check Lock(DCL)
DCL雙檢查鎖機制單例,效率高,執行緒安全,多執行緒操作原子性,
class Single2 {
private static Single2 s;
public Single2() { }
public static Single2 getInstance(){
if (s == null) {
synchronized (Single2DCL.class) {
if (s == null) {
s = new Single2DCL();
}
}
}
return s;
}
}
面試題
DCL單例是否需要使用volatile關鍵字?
需要,單例的模式, 不使用volatile關鍵字,可能執行緒會拿到一個尚未初始化完成的物件(半初始化)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/550108.html
標籤:其他
上一篇:day01-專案介紹與環境搭建