對象在內存中的內存佈局
用sychronized
鎖住對象後該對象的鎖狀態升級過程:new
- 無鎖態 - 偏向鎖 - 輕量級鎖/自旋鎖/無鎖 (CAS)- 重量級鎖 - GC標記信息
線程的幾個狀態
- NEW(新建狀態)
- Runnable
- Ready(就緒狀態,線程被放在等待隊列中,等着被CPU執行)
- Running(運行狀態,被扔到CPU中執行)
- Blocked
- Waiting
- TimedWaiting
- Terminated(終止態)
三種新建線程的方法
- 實現
Thread
類
- 實現
Runnable
接口
- 線程池
線程的常用方法:
sleep()
,沉睡一段時間(當前線程回到就緒狀態),這段時間CPU執行其它線程
yield()
,和sleep()
類似,讓出CPU,當前線程回到就緒狀態。使用很少見
join()
,通知其它線程獲得CPU執行,比如在t1
線程內運行t2.join()
,意思就是t1
線程通知t2
線程執行,自己回到就緒狀態。
Synchronized
講解
synchronized
實現過程:(不能禁止指令重排)
- Java代碼:
synchronized
monitorenter
、moniterexit
- 執行過程中自動升級(偏向鎖、自旋鎖、重量級鎖)
- 更底層的實現
lock comxchg
volatile
講解:
- 保證變量的各線程可見性/數據一致性 (多個線程要用到變量時,重新去內存拿)
- 禁止CPU指令重排(在單線程沒問題,多線程就會出現問題。爲什麼要指令重排,其實就是因爲CPU太快了,而訪問內存比訪問緩存又慢了太多)
- 舉個例子:對象的初始化三個步驟
Person p = new Person("zeng", 24);
- 申請對象
Person
的內存,這個時候給實例變量設置了默認值,比如name = null; age = 0;
- 調用該對象的構造函數進行真正的初始化實例變量
name = "zeng"; age = 24;
- 返回對象
Person
給p
volatile
不能實現synchronized
的原子性操作
- 比如定義一個變量
volatile int count = 0;
10個線程分別count++
加1000次,最終的count
不一定會是10000,因爲這裏的count++
並不是一個原子性操作,它包含好幾個指令,所以爲了要實現整個的count++
原子性操作,也就是必須要使用sychronized
對count++
加鎖。
再注意一些問題:
- 在用
synchronized
鎖住一個對象時,這個時候不能將這個引用去指向另一個對象
- 不要用
synchronized
去鎖一個String、Integer
等基本數據類型的封裝類的對象
CAS(無鎖優化/自旋):
CompareAndSwap
Java
裏面java.util.concurrnet.atomic.AtomicXXX
開頭的類都是使用CAS自旋鎖實現的。內部都是使用UnSafe
這個類的compareAndSet
等操作實現線程安全地修改值
- 舉個例子:
AtomicInteger count = new AtomicInteger(0);
在上面的volatile
的討論中,count++
如果不加sychronized
鎖會導致非原子性操作,但這裏直接使用AtomicInteger
即可實現線程可見、原子性操作,將count++
到10000。並且不需要volatile、synchronized
。
- ABA問題(1變爲2又被變爲1),加版本號
version
- 所有的
Java
中CAS
的操作基本上都是用的UnSafe
這個類,這個UnSafe
使Java
語言有了像C++
的直接操作JVM
內存的能力。
ReentrantLock(可重入鎖,公平鎖(默認是非公平鎖))本身底層也是CAS
- 可以替代
synchronized
,替換方法:lock.lock();
- 可以通過
lock.interupt
的方法將該鎖設置爲可以通過interup
方法喚醒正在wait
的線程
- 相比上個特點,
synchronized
的線程,wait
之後必須通過其它線程的notify()
才能喚醒
- 如果設置爲公平鎖,那麼線程在搶一個資源時,會進入優先隊列排隊按先後順序等待
synchronized
是非公平鎖
synchronized
自動加鎖解鎖,ReentrantLock
手動加鎖解鎖lock.lock()
- 底層實現:
ReentrantLock
是CAS的實現,synchronized
底層是有鎖的升級過程(4種)
CountDownLatch鎖(倒計時完了繼續執行(門栓))
CyclicBarrier鎖(當線程數目到達某個數目(柵欄值)時,繼續執行後面的事物)
Phase鎖(階段鎖,CyclicBarrier的升級版本,有多個階段,比如結婚現場有7個人,先7人到達現場,再7人喫完飯,再xxxxx)
ReadWriteLock(共享鎖、排他鎖、多個線程可以一起執行)
Semaphore(信號量,用於限流(僅允許幾個線程同時工作))
Exchanger(兩個線程運行時交換值)
LockSupport(可以通過park()
方法隨時將線程停止,並通過unpark()
方法隨時讓某線程就緒)
面試題1:定義兩個線程,A線程往容器裏放數據,B線程監測容器容量爲5時,停止運行
- 有3種方法
- 使用
wait()
與notify()
方法的組合。這個很重要
- 使用門栓鎖
CountDownLatch
- 使用
LockSupport
直接park()
與unpark()
面試題2:順序打印A1B2C3……
面試題3:生產者消費者問題
版本1 通過synchronized、wait()、notify()
實現
package zr.thread;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
/*
生產者與消費者實現1
寫一個固定容量同步容器,擁有put和get方法, 以及getCount方法
能夠支持2個生產者線程以及10個消費者線程的阻塞調用
使用wait()和notifyAll()來實現
這個方法是有瑕疵的,因爲使用notifyAll()會喚醒所有的其它等待隊列的線程,包括生產者、消費者
有沒有辦法只喚醒生產者,或者只喚醒消費者?
*/
/**
* @author ZR
* @Classname MyContainer1
* @Description 生產者消費者最簡單寫法
* @Date 2020/9/12 21:02
*/
public class MyContainer1<T> {
final private LinkedList<T> lists = new LinkedList<>();
// 最多10個元素
final private int MAX = 10;
private int count = 0;
// 因爲++count所以要加synchronized
public synchronized void put(T t){
// 想想爲什麼用while而不是if
while(lists.size() == MAX){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
lists.add(t);
++count;
// 通知所有消費者線程消費
// 這個方法其實是有點小瑕疵的,因爲notifyAll()會叫醒所有的其它wait()線程,也包括了另一個生產者
this.notifyAll();
}
// 因爲--count所以要加synchronized
public synchronized T get(){
T t = null;
while(lists.size() == 0){
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
t = lists.removeFirst();
--count;
// 通知生產者進行生產
// 這個方法其實是有點小瑕疵的,因爲notifyAll()會叫醒所有的其它wait()線程,也包括了其它消費者
this.notifyAll();
return t;
}
public static void main(String[] args){
MyContainer1<String> c = new MyContainer1<>();
// 啓動消費者線程
for(int i = 0; i < 10; i++){
new Thread(()->{
for(int j = 0; j < 5; j++)
System.out.println(c.get());
}, "customer" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 啓動生產者線程
for(int i = 0; i < 2; i++){
new Thread(()->{
for(int j = 0; j < 25; j++)
c.put(Thread.currentThread().getName() + " " + j);
}, "producer" + i).start();
}
}
}
版本2 通過ReentrantLock
實現
package zr.thread;
import com.sun.org.glassfish.external.statistics.CountStatistic;
import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author ZR
* @Classname MyContainer2
* @Description TODO
* @Date 2020/9/12 21:27
*/
public class MyContainer2<T> {
final private LinkedList<T> lists = new LinkedList<>();
// 最多10個元素
final private int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
// Condition的本質就是等待隊列,在這裏生產者在生產者的隊列,消費者在消費者的隊列
// 在Container1例中,等待隊列只有一個,生產者和消費者都在裏邊兒
private Condition producer = lock.newCondition();
private Condition customer = lock.newCondition();
public void put(T t){
try {
// 需要手動加鎖
lock.lock();
while(lists.size() == MAX)
producer.await();
lists.add(t);
++count;
// 通知消費者線程進行消費
customer.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 手動解鎖
lock.unlock();
}
}
public T get(){
T t = null;
try {
lock.lock();
while(lists.size() == 0)
customer.await();
t = lists.removeFirst();
--count;
// 通知生產者線程生產
producer.signalAll();
} catch (InterruptedException e){
e.printStackTrace();
} finally {
lock.unlock();
}
return t;
}
public static void main(String[] args){
MyContainer2<String> c = new MyContainer2<>();
// 啓動消費者線程
for(int i = 0; i < 10; i++){
new Thread(()->{
for(int j = 0; j < 5; j++)
System.out.println(c.get());
}, "customer" + i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 啓動生產者線程
for(int i = 0; i < 2; i++){
new Thread(()->{
for(int j = 0; j < 25; j++)
c.put(Thread.currentThread().getName() + " " + j);
}, "producer" + i).start();
}
}
}