對象及變量的併發訪問
1.synchronized同步方法
synchronized關鍵字可用來保障原子性、可見性、和有序性。
我們需要掌握的是:
1)synchronized對象監視器爲Object時的使用方法
2)synchronized對象監視器爲Class的使用方法
1.1方法內的變量爲線程安全
非線程安全問題存在於實例變量中,對於方法內部的私有變量,則不存在線程安全問題。
1.2 實例變量非線程安全問題
如果多個線程共同訪問一個對象中的實例變量,則有可能會出現非線程安全問題。
兩個線程同時訪問同一個對象中的同步方法時一定是線程安全的。
1.3 多個對象多個鎖
創建類HasSelfPrivateNum:
public class HasSelfPrivateNum {
private int num = 0;
synchronized public void add(String username){
try {
if(username.equals("a")){
num =100;
System.out.println("a set over");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b set over");
}
System.out.println(username+"num="+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
創建兩個線程類:
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.add("a");
}
}
ThreadB:
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef){
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.add("b");
}
}
最後的運行結果爲:
a set over
b set over
bnum=200
anum=100
總結:本示例創建了兩個業務對象(numRef1,numRef2),在系統中產生了兩個鎖,線程和業務對象屬於一對一的關係,每個線程執行自己所屬業務對象中的同步方法,不存在爭搶關係,所以運行結果是異步的,更具體的講,在上面的這個示例中創建了兩個業務對象,所以產生兩份實例變量,每個線程訪問自己的實例變量,所以加不加synchronized關鍵字都是線程安全的。
如果運行結果爲a set over,anum =100,b set over,bnum = 200,才說明線程是同步進行的。
1.4 關於synchronized同步鎖方法的結論
1.4.1 A線程持有Object對象的lock鎖,B線程可以以異步的方式調用object對象中的非synchronized類型的方法
1.4.2 A線程持有object對象的lock鎖,B線程如果在這時調用object對象中的synchronized類型的方法,則需要等待。
1.4.3 在方法聲明處添加synchronized並不是鎖方法,而是鎖當前類的對象
1.4.4 在Java中只有“將對象作爲鎖”這種說法,並沒有“鎖方法”這種說法
1.4.5 在Java中,“鎖”就是對象,“對象”可以映射成“鎖”,哪個線程拿到這把鎖,哪個線程就可以執行這個對象中的synchronized同步方法
1.4.6 如果在X對象中使用了synchronize的關鍵字聲明非靜態方法,則X對象就被當成鎖。
1.5 髒讀
通過synchronized關鍵字可以解決髒讀的問題。
拿一個對象A的setValue()和getValue()爲例,其中setValue()被synchronized修飾,而getValue()沒有。
現在有兩個線程A和線程B
A先執行,setValue(),A線程未執行完,B線程無法調用setValue(),但是getValue()可被B線程調用,此時出現髒讀
解決方案:給getValue()也加上synchronized修飾。
1.6 爲了解決synchronized同步方法的等待時間過長的方案------->synchronized同步代碼塊synchronized(this)
synchronized同步代碼塊的作用:(synchronized同步代碼塊也是一樣)
1.對其他synchronized同步方法或synchronized(this)同步代碼塊調用呈同步效果
2.同一時間只有一個線程可以執行synchronized同步方法中代碼
1.7 synchronized(非this對象)
1.7.1 除了使用synchronized(this)格式來創建同步代碼塊,其實Java還支持將“任意對象”作爲鎖來實現同步的功能,這個“任意對象”大多數是實例變量及方法的參數,使用格式爲
1.7.2 synchronized(非this對象x)同步代碼塊的作用:當多個線程爭搶相同的“非this對象x”的鎖時,同一時間只有一個線程可以執行synchronized(非this 對象x)同步代碼塊中的代碼s
1.7.3 鎖非this 對象具有一定的優點:如果一個類中有很多個synchronized方法,則這時雖然能實現同步,但影響運行效率,如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中的程序與同步方法是異步的,因爲有兩把鎖,不與其他鎖this同步方法爭搶this 鎖,可大大提高運行效率。
1.8 多個鎖就是異步執行
使用synchronized(非this對象x)同步代碼“代碼塊”操作時,鎖必須是同一個,否則就是異步調用,交叉執行。
下面使用個例子證明多個鎖時是異步執行的結果:
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
this.service = service;
}
@Override
public void run() {
service.a();
}
}
ThreadB:
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
this.service = service;
}
@Override
public void run() {
service.b();
}
}
創建個模擬業務類:Service
public class Service {
private String anyString = new String();
public void a(){
try {
synchronized (anyString){
System.out.println("a begain");
}
Thread.sleep(3000);
System.out.println("a end!");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void b(){
System.out.println("b begain");
System.out.println("b end!");
}
}
最後運行我們的測試類:
public class RunTest {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
最後的運行結果我們看到當A線程還沒跑完的時候,B線程也已經在開始了,由於鎖不同,所以運行結果是異步調用的;
a begain
b begain
b end!
a end!
和synchronized(非this對象X)和synchronized(this對象或者方法)不同的是,後者多個線程時,得等先拿到鎖的那個線程跑完了才能執行其他synchronized的方法而synchronized(非this對象x)可以和其他synchronized方法異步執行。
1.9 細化synchronize(非this對象x)的三個結論:synchronize的(非this對象x)是將x對象本身作爲“對象監視器”
1.9.1 當多個線程同時執行synchronized(x){}同步代碼塊時呈現同步效果
1.9.2 當其他線程執行x對象中synchronized同步方法時呈現同步效果
1.9.3 當其他線程執行x對象方法裏面的synchronized(this)代碼塊時呈現同步效果
需要注意的是,如果其他線程調用不加synchronized關鍵字的方法,則還是異步調用,而且鎖只是類繼承的環境。
1.20 靜態同步synchronized方法與synchronized(class) 代碼塊
關鍵字synchronized應用在static靜態方法上時,那是對當前的*.java問對應的Class類對象進行持鎖,Class類的對象是單例的。更具體的說,在靜態static方法上使用synchronized關鍵字聲明同步方法時,使用當前靜態方法所在類對應Class類的單例對象作爲鎖。
而將synchronized加在非static方法上是將方法所在類的對象作爲鎖。
下面驗證兩個不是同一個鎖的。
Service.java:
public class Service {
synchronized public static void printA(){
try {
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"進入了printA");
Thread.sleep(3000);
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"離開了printA");
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public static void printB(){
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"進入了printB");
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"離開了printB");
}
synchronized public void printC(){
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"進入了printC");
System.out.println("線程的名稱爲:"+Thread.currentThread().getName()+
"在"+System.currentTimeMillis()+"離開了printC");
}
}
創建三個線程:
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.printA();
}
}
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.printB();
}
}
public class ThreadC extends Thread{
private Service service;
public ThreadC(Service service){
super();
this.service = service;
}
@Override
public void run() {
service.printC();
}
}
最後運行測試類:
public class RunTest {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
ThreadC c = new ThreadC(service);
c.setName("c");
c.start();
}
}
最後的運行結果如圖:方法C是異步執行,同步synchronized放在static方法上可以對類的所有對象的實例起作用,比如此時的線程A和B,不同的service對象,但是對printA和printB依然是同步執行
線程的名稱爲:a在1592551267059進入了printA
線程的名稱爲:c在1592551267062進入了printC
線程的名稱爲:c在1592551267062離開了printC
線程的名稱爲:a在1592551270059離開了printA
線程的名稱爲:b在1592551270059進入了printB
線程的名稱爲:b在1592551270060離開了printB
1.21 同步synchronized(A.class)和synchronized A中的static方法是同樣作用,可以對類的所有對象的實例起作用,但是非static的synchronized只能對同一個實例有作用
1.22 String常量池特性與同步相關的問題與解決方案
問題:當synchronized(String對象)時,假設兩個線程的值都是“AA”,String的特性會判斷“AA"=="AA"爲true,則此時兩個線程的鎖相同,B線程將不會被執行。
解決:所以大多數情況下,不適用String作爲鎖對象,而改用其他,而用new Object()實例化一個新的Object對象,它並不放入緩存池中,或者執行new String ()創建不同的字符串對象,形成不同的鎖。