由於CPU執行速度和內存數據讀寫速度差距很大,所以CPU每每包含高速緩存
結構。 html
執行下面的代碼:java
int i = 0;
i = i + 1;
複製代碼
當線程執行這個語句時,會先從主存當中讀取i的值i = 0
,而後複製一份到高速緩存
當中,而後CPU執行指令對i
進行加1操做,而後將數據寫入高速緩存,最後將高速緩存中i
最新的值刷新到主存
當中。數據庫
可能存在狀況:初始時,兩個線程分別讀取i的值存入各自所在的CPU的高速緩存當中,而後線程1
進行加1操做,而後把i的最新值1寫入到內存。此時線程2的高速緩存當中i的值仍是0,進行加1操做以後,i的值爲1,而後線程2
把i的值寫入內存。編程
也就是說,若是一個變量在多個CPU中都存在緩存(多線程狀況),那麼就可能存在緩存不一致的問題。數組
通常有兩種解決辦法:緩存
由於CPU和其餘部件進行通訊都是經過總線來進行的,若是對總線加鎖的話,也就是說阻塞了其餘CPU對其餘部件訪問(如內存),從而使得只能有一個CPU能使用這個變量的內存。安全
因爲在鎖住總線期間,其餘CPU沒法訪問內存,致使效率低下。因此就出現了緩存一致性協議。最出名的就是Intel的
MESI協議
,MESI協議
保證了每一個緩存中使用的共享變量的副本是一致的。MESI協議
核心思想是:當CPU寫數據時,若是發現操做的變量是共享變量,即在其餘CPU中也存在該變量的副本,會發出信號通知其餘CPU將該變量的緩存行置爲無效狀態,所以當其餘CPU須要讀取這個變量時,發現本身緩存中緩存該變量的緩存行是無效的,那麼它就會從內存從新讀取。bash
從前面的分析,在併發編程(多線程編程)中,可能出現線程安全的問題:多線程
多個線程在操做共享的數據。併發
操做共享數據的線程代碼有多條。
當一個線程在執行操做共享數據的多條代碼過程當中,其餘線程參與了運算。
三個核心概念:原子性、可見性、順序性。
鎖和同步
(同步方法和同步代碼塊)、CAS
(CPU級別的CAS指令cmpxchg
)。
volatile
關鍵字來保證可見性。
指令重排序
。
volatile
在必定程序上保證順序性,另外還能夠經過synchronized
和鎖
來保證順序性。
Java對象能夠做爲併發編程中的鎖。而鎖實際上存在於Java對象頭裏。若是對象是數組類型,則虛擬機用 3 個 Word(字寬)存儲對象頭,若是對象是非數組類型,則用 2 字寬存儲對象頭。在 64 位虛擬機中,一字寬等於八字節,即 64bit
。
Java 對象頭裏的 Mark Word
裏默認存儲對象的 HashCode,分代年齡和鎖標記位。32 位 JVM 的 Mark Word 的默認存儲結構以下:
|-|25 bit|4bit|偏向鎖標誌位(1bit)|鎖標誌位(2bit)| |::|::|::|::|::| |無鎖狀態|對象的hashCode|對象分代年齡| |01|
64 位JVM的存儲結構以下:
鎖狀態 |
25bit |
31bit |
1bit |
4bit |
1bit |
2bit |
||||||||||||||||||||||||||||||||||
</td>
<td>
</td>
<td>
<p><span lang="EN-US">cms_free</span></p>
</td>
<td>
<p><span>分代年齡<span lang="EN-US"></span></span></p>
</td>
<td colspan="2">
<p><span>偏向鎖<span lang="EN-US"></span></span></p>
</td>
<td>
<p><span>鎖標誌位<span lang="EN-US"></span></span></p>
</td>
</tr><tr><td>
<p><span>無鎖<span lang="EN-US"></span></span></p>
</td>
<td>
<p><span lang="EN-US">unused</span></p>
</td>
<td>
<p><span lang="EN-US">hashCode</span></p>
</td>
<td>
</td>
<td>
</td>
<td>
</td>
<td colspan="2">
<p><span lang="EN-US">01</span></p>
</td>
</tr><tr><td>
<p><span>偏向鎖<span lang="EN-US"></span></span></p>
</td>
<td colspan="2">
<p><span lang="EN-US">ThreadID(54bit) Epoch(2bit)</span></p>
</td>
<td>
</td>
<td>
</td>
<td>
<p><span lang="EN-US">1</span></p>
</td>
<td colspan="2">
<p><span lang="EN-US">01</span></p>
</td>
</tr></tbody></table>
複製代碼 在運行期間 在瞭解了相關概念後,接下來介紹Java是如何保證併發編程中的安全的。 4、synchronized用法
synchronized(對象)
{
須要被同步的代碼 ;
}
複製代碼
修飾符 synchronized 返回值 方法名(){
}
複製代碼
鎖對象
實現原理在編譯的字節碼中加入了兩條指令來進行代碼的同步。 monitorenter :每一個對象有一個
monitorexit:執行
好處和弊端好處:解決了線程的安全問題。 弊端:相對下降了效率,由於同步外的線程的都會判斷同步鎖。得到鎖和釋放鎖帶來性能消耗。 編譯器對synchronized優化Java6 爲了減小得到鎖和釋放鎖所帶來的性能消耗,引入了「偏向鎖」和「輕量級鎖」,因此在Java6 裏鎖一共有四種狀態:無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨着競爭狀況逐漸升級。鎖能夠升級但不能降級。
鎖狀態對應的Mark Word以32位JVM爲例:
5、volatile
被修飾的變量包含兩層語義:
**底層實現:**觀察加入 6、Lock應用場景若是一個代碼塊被
若是這個獲取鎖的線程因爲要等待IO或者其餘緣由(好比調用sleep方法)被阻塞了,可是又沒有釋放鎖,會讓程序效率不好。 所以就須要有一種機制能夠不讓等待的線程一直無期限地等待下去(好比只等待必定的時間或者可以響應中斷),經過 源碼分析與Lock相關的接口和類位於 ![]() (1)Lock接口public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
複製代碼
(2)ReentrantLock類
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製代碼
public void lock() {
sync.lock();
}
複製代碼 而 abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
複製代碼 其 public ReentrantLock() {
sync = new NonfairSync();
}
//刪去一些方法
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
複製代碼 而 public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼 嘗試進一步獲取鎖(調用繼承自父類 final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製代碼 首先會判斷 若是 即回到: public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
公平鎖和非公平鎖的釋放流程都是同樣的:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//喚醒被掛起的線程
unparkSuccessor(h);
return true;
}
return false;
}
//嘗試釋放鎖
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製代碼 (3)ReadWriteLock接口與ReentrantReadWriteLock類
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
複製代碼 在
在 static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//獲取讀鎖的佔有次數
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//獲取寫鎖的佔有次數
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//線程的id和對應線程獲取的讀鎖的數量
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = Thread.currentThread().getId();
}
//線程變量保存線程和線程中獲取的讀寫的數量
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
private transient ThreadLocalHoldCounter readHolds;
//緩存最後一個獲取讀鎖的線程
private transient HoldCounter cachedHoldCounter;
//保存第一個獲取讀鎖的線程
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
複製代碼 其中,包含兩個靜態內部類: 獲取讀鎖:
獲取寫鎖:
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
//判斷是否堵塞
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
複製代碼 7、比較Lock和synchronized
synchronized和volatile
7、死鎖問題死鎖有四個必要條件,打破一個便可去除死鎖。 四個必要條件:
死鎖的例子同步嵌套時,兩個線程互相鎖住,都不釋放,形成死鎖。 舉例: 建立兩個字符串a和b,再建立兩個線程A和B,讓每一個線程都用synchronized鎖住字符串(A先鎖a,再去鎖b;B先鎖b,再鎖a),若是A鎖住a,B鎖住b,A就沒辦法鎖住b,B也沒辦法鎖住a,這時就陷入了死鎖。 public class DeadLock {
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args){
Thread a = new Thread(new Lock1());
Thread b = new Thread(new Lock2());
a.start();
b.start();
}
}
class Lock1 implements Runnable{
@Override
public void run(){
try{
System.out.println("Lock1 running");
while(true){
synchronized(DeadLock.obj1){
System.out.println("Lock1 lock obj1");
Thread.sleep(3000);//獲取obj1後先等一下子,讓Lock2有足夠的時間鎖住obj2
synchronized(DeadLock.obj2){
System.out.println("Lock1 lock obj2");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
class Lock2 implements Runnable{
@Override
public void run(){
try{
System.out.println("Lock2 running");
while(true){
synchronized(DeadLock.obj2){
System.out.println("Lock2 lock obj2");
Thread.sleep(3000);
synchronized(DeadLock.obj1){
System.out.println("Lock2 lock obj1");
}
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
複製代碼 8、鎖的概念在 java 中鎖的實現主要有兩類:內部鎖
關於JUC包含了兩個子包:atomic以及lock,另外在concurrent下的阻塞隊列以及executors,之後再深刻學習吧,下面這個圖非常經典: ![]() 參考連接 |