Java記憶體模型
執行緒同步
執行緒同步機制是一套適用於協調執行緒之間的資料訪問機制,該機制可以保障執行緒安全
java平臺提供的執行緒同步機制包括:鎖、volatile關鍵字、final關鍵字,static關鍵字、以及相關API如object.wait/object.notify
鎖概述
執行緒安全問題的產生前提是多個執行緒併發訪問共享資料,將多個資料對共享資料的併發訪問,轉化為序列訪問,即共享資料只能被一個執行緒訪問,鎖就是這種思路。
執行緒訪問資料時必須先獲得鎖,獲得鎖的執行緒稱為鎖的持有執行緒,一個鎖一次只能被一個執行緒持有,持有執行緒在獲得鎖之後和釋放鎖之前鎖執行的程式碼稱之為臨界區。
鎖具有排它性(Exclisive),即一個鎖只能被一個執行緒持有,這種鎖稱為排它鎖或者互斥鎖。
JVM部分把鎖分為內部鎖和顯示鎖,內部鎖通過Synchronized關鍵字實現,顯示鎖通過java.concurrent.locks.Lock介面實現類實現的。
鎖的作用
鎖能夠實現對共享資料的安全,保障執行緒的原子性,可見性與有序性。
鎖是通過互斥保障原子性,一個鎖只能被一個執行緒持有,這就保證了臨界區的程式碼一次只能被一個執行緒執行,使得臨界區程式碼所執行的的操作自然而然的具有不可分割的特性,既具備了原子性。
好比一條路段所有車輛都在跑,併發執行,在經過某一個路段的時候,多車道變為一車道,一次只能通過一輛車,由併發執行改為序列執行。
可見性是通過寫執行緒沖刷處理器的快取和讀執行緒重新整理處理器快取這兩個動作,鎖的獲得隱含著重新整理處理器快取的動作,鎖的釋放隱含著沖刷處理器快取的動作。
鎖能夠保障有序性,寫執行緒在臨界區所執行的臨界區看來像是完全按照原始碼順序執行的。
鎖的相關概念
可重入性:一個執行緒持有該鎖的時候能夠再次/多次申請該鎖
如果一個執行緒持有一個鎖的時候,還沒有釋放,但還能夠繼續成功申請該鎖,稱該鎖可重入,反之。
鎖的爭用與排程
java中內部鎖屬於非公平鎖,顯示鎖支援非公平鎖和公平鎖
鎖的粒度
一個所可以保護的共享資料的數量大小稱為鎖的粒度。
鎖保護共享資料量大,稱為鎖粒度粗,否則稱為粒度細。
鎖的粒度過粗會導致執行緒在申請鎖時會進行不必要的等待,鎖粒度過細會增加鎖排程的開銷。
比如銀行有一個櫃檯一個員工可以辦理開卡、銷戶、取現、貸款那麼所有人都只能去這個櫃檯辦理業務,會需要很長的等待時間。但是如果把業務細分,一個業務一個櫃檯,這時候增加了銀行的開銷,需要三個員工。
內部鎖:Synchronized
Java中每一個物件都有一個與之關聯的內部鎖,這種鎖也叫監視器,是一種排它鎖,可以保障原子性、可見性、排它性。
Synchronized(物件鎖)
{
同步程式碼塊,可以在同步程式碼塊中訪問共享資料
}
修飾例項方法稱為同步例項方法,修飾靜態方法稱為同步靜態方法。
Synchronized同步程式碼塊
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
for (int i = 0; i <2 ; i++) {
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();
}
}.start();
}
}
public void mm()
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
兩個執行緒的程式碼都在併發執行
現在要列印的時候進行同步,同步的原理執行緒在執行的時候要先要獲得鎖
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
for (int i = 0; i <2 ; i++) {
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();//使用鎖的物件是synchronizedLock物件
}
}.start();
}
}
public void mm()
{
synchronized (this)//this作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
因為Synchronized內部鎖是排它鎖,一次只能被一個執行緒持有,現在是Thread-0先取得鎖物件,Thread-1在等待區等待Thread-0執行完畢釋放鎖,Thread-1獲得鎖再執行。
鎖物件不同不能實現同步
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
SynchronizedLock synchronizedLock2=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();//使用鎖的物件是synchronizedLock物件
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock2.mm();//使用鎖的物件是synchronizedLock物件
}
}.start();
}
public void mm()
{
synchronized (this)//this作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
因此想要同步必須使用同一個鎖物件
使用常量作為鎖物件
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
SynchronizedLock synchronizedLock2=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();//使用鎖的物件是synchronizedLock物件
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();//使用鎖的物件是synchronizedLock物件
}
}.start();
}
public static final Object obj=new Object();
public void mm()
{
synchronized (obj)//常量作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步例項方法
使用synchronized修飾例項方法,同步例項方法,預設使用this作為鎖物件
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm2();
}
}.start();
}
//同步例項方法
public synchronized void mm()
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{
synchronized (this)//常量作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步靜態方法
使用synchronized修飾靜態方法,同步靜態方法,預設執行時使用SynchronizedLock class作為鎖物件
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm2();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
SynchronizedLock.mm();//使用鎖的物件是SynchronizedLock.class
}
}.start();
}
//同步靜態方法
public synchronized static void mm()
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{
synchronized (SynchronizedLock.class)//常量作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步程式碼塊和同步方法如何選擇
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
try {
synchronizedLock.mm2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
try {
synchronizedLock.mm2();//使用鎖的物件是SynchronizedLock.class
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
}
//同步例項方法 鎖的粒度粗 執行效率低
public synchronized void mm() throws InterruptedException {
long starttime= System.currentTimeMillis();
System.out.println("start");
Thread.sleep(3000);
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
System.out.println("end");
long Endtime= System.currentTimeMillis();
System.out.println(Endtime-starttime);
}
//同步程式碼塊 鎖的粒度細 併發效率高
public void mm2() throws InterruptedException {
System.out.println("start");
Thread.sleep(3000);
synchronized (this)//常量作為當前物件
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
System.out.println("end");
}
}
在執行同步方法的時候,兩次執行緒呼叫每次都需要休眠三秒,而同步程式碼塊同時啟動執行緒都先準備三秒,效率比較高
髒讀
public class Test06 {
public static void main(String[] args) throws InterruptedException {
User user=new User();
SubThread subThread=new SubThread(user);
subThread.start();
user.GetName();
}
static class SubThread extends Thread
{
public User user;
public SubThread(User user)
{
this.user=user;
}
@Override
public void run() {
user.SetValue("ww","456");
}
}
static class User
{
private String name="ylc";
private String pwd="123";
public void GetName()
{
System.out.println(Thread.currentThread().getName()+"==>"+name+"密碼"+pwd);
}
public void SetValue(String name,String pwd)
{
System.out.println("原來為為name="+this.name+",pwd="+this.pwd);
this.name=name;
this.pwd=pwd;
System.out.println("更新為name="+name+",pwd="+pwd);
}
}
}
在修改資料還沒有完成的時候,就讀取到了原來的資料,而不是修改之後的
出現髒讀的原因是對共享資料的修改和讀取不同步引起的
解決辦法是對修改和讀取的方法進行同步方法上加上synchronized
關鍵字
執行緒出現異常釋放鎖
假如在同步方法中,一個執行緒出現了異常,會不會沒有釋放鎖,其他在等待的執行緒就在一直等待,論證:
public class SynchronizedLock {
public static void main(String[] args) {
SynchronizedLock synchronizedLock=new SynchronizedLock();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm();
}
}.start();
new Thread(new RunnableThread())
{
@Override
public void run() {
synchronizedLock.mm2();
}
}.start();
}
//同步例項方法
public synchronized void mm()
{
for (int i = 0; i <100 ; i++) {
if(i==50)
{
Integer.parseInt("abc");//異常設定
}
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public void mm2()
{
synchronized (this)
{
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
同步過程中執行緒出現異常,會自動釋放鎖物件,以供下一個執行緒繼續執行
死鎖
多執行緒中可能需要使用多個鎖,如果獲取鎖的順序不一致,可能導致死鎖。
public class Text06_5 {
public static void main(String[] args) {
SubThread subThread=new SubThread();
SubThread subThread2=new SubThread();
subThread.setName("a"); subThread2.setName("b");
subThread.start();subThread2.start();
}
static class SubThread extends Thread
{
private static final Object lock1=new Object();
private static final Object lock2=new Object();
@Override
public void run() {
if("a".equals(Thread.currentThread().getName()))
{
synchronized (lock1)
{
System.out.println("a 執行緒 lock1獲得了鎖,再需要獲得lock2");
synchronized (lock2)
{
System.out.println("a 執行緒 lock2獲得了鎖");
}
}
}
if("b".equals(Thread.currentThread().getName()))
{
synchronized (lock2)
{
System.out.println("b 執行緒 lock2獲得了鎖,再需要獲得lock1");
synchronized (lock1)
{
System.out.println(" b 執行緒 lock1獲得了鎖");
}
}
}
}
}
}
程式還在執行,卻進入了卡死狀態,a執行緒得到了lock1,要想把該執行緒釋放的執行下面的程式碼獲取lock2,而lock2被b執行緒獲取無法釋放,出現了鷸蚌相爭的情況。
避免死鎖:當需要獲得鎖時,所有執行緒獲得鎖的順序一致,a執行緒先鎖lock1,再鎖lock2,b執行緒同理,就不會出現死鎖了。