12、synchronized同步方法
阿新 • • 發佈:2018-12-22
ps:最近幾天帶媳婦回了趟家,博文沒有更新。
目錄
A執行緒持有同步方法的鎖,B執行緒可以非同步的呼叫該物件中的非同步方法
方法內部的私有變數為執行緒安全
在使用synchronized同步方法之前,我們先來做多個執行緒訪問方法內部的私有變數,是否存線上程安全的問題。
package com.demo12; public class MyObject { public void addNum(String name){ try { int num = 0; if("a".equals(name)){ num = 100 ; System.out.println("a 賦值完畢"); Thread.sleep(2000); }else{ num = 200; System.out.println("b 賦值完畢"); } System.out.println("執行緒name=" + name + ";num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.demo12;
public class ThreadA extends Thread {
private MyObject myObject;
public ThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.addNum("a");
}
}
package com.demo12; public class ThreadB extends Thread{ private MyObject myObject; public ThreadB(MyObject myObject) { this.myObject = myObject; } @Override public void run() { myObject.addNum("b"); } }
package com.demo12;
public class Run {
public static void main(String[] args) {
MyObject myObject = new MyObject();
ThreadA threadA = new ThreadA(myObject);
ThreadB threadB = new ThreadB(myObject);
threadA.start();
threadB.start();
}
}
執行結果:
a 賦值完畢 執行緒name=a;num=100 b 賦值完畢 執行緒name=b;num=200
由輸出結果可以看出:a執行緒賦值完畢之後,b執行緒才對num進行賦值。哪怕a sleep()了,b執行緒也沒搶佔cpu資源去執行賦值。方法內部的私有變數,不存線上程安全的問題。
例項變數非執行緒安全
package com.demo12;
public class MyObject {
private int num = 0;
public void addNum(String name){
try {
//int num = 0;
if("a".equals(name)){
num = 100 ;
System.out.println("a 賦值完畢");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b 賦值完畢");
}
System.out.println("執行緒name=" + name + ";num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
將上例中的MyObject中的方法內部的變數改為例項變數,執行結果為:
a 賦值完畢
b 賦值完畢
執行緒name=b;num=200
(此處等待兩秒)
執行緒name=a;num=200
執行緒a賦值完畢之後被sleep(),這時候b拿到了Cpu資源,對num進行了賦值,最後a才拿到cpu資源,繼續走列印語句。但是num已經是被b給修改過了的。這就是非執行緒安全的場景。
給方法加鎖
這個時候,我們可以使用synchronized對方法進行加鎖,使上面的場景變成執行緒安全的!
package com.demo12;
public class MyObject {
private int num = 0;
synchronized public void addNum(String name){
try {
//int num = 0;
if("a".equals(name)){
num = 100 ;
System.out.println("a 賦值完畢");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b 賦值完畢");
}
System.out.println("執行緒name=" + name + ";num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行結果:
a 賦值完畢
(此處等待了2秒)
執行緒name=a;num=100
b 賦值完畢
執行緒name=b;num=200
很明顯為執行緒安全的了。
synchronized取得的鎖是物件鎖
修改上述程式碼如下:
package com.demo12;
public class MyObject {
private int num = 0;
synchronized public void addNum(String name){
try {
//int num = 0;
if("a".equals(name)){
num = 100 ;
System.out.println("a 賦值完畢");
Thread.sleep(2000);
}else{
num = 200;
System.out.println("b 賦值完畢");
}
System.out.println("執行緒name=" + name + ";num=" + num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.demo12;
public class ThreadA extends Thread {
private MyObject myObject;
public ThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.addNum("a");
}
}
package com.demo12;
public class ThreadB extends Thread{
private MyObject myObject;
public ThreadB(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.addNum("b");
}
}
package com.demo12;
public class Run {
public static void main(String[] args) {
MyObject myObject1= new MyObject();
MyObject myObject2= new MyObject();
ThreadA threadA = new ThreadA(myObject1);
threadA.start();
ThreadB threadB = new ThreadB(myObject2);
threadB.start();
}
}
執行結果為:
a 賦值完畢
b 賦值完畢
執行緒name=b;num=200
(此處等待了2秒)
執行緒name=a;num=100
分析:雖然結果打印出來的形如非同步,但是結果卻沒有亂。a , b 執行緒在獲取CPU資源之後,分別持有synchronied方法所屬的物件鎖。如果不是分別拿到的兩個物件的鎖,最後的列印結果num=200。出現非執行緒安全的問題。
A執行緒持有同步方法的鎖,B執行緒可以非同步的呼叫該物件中的非同步方法
package com.demo13;
public class MyObject {
synchronized public void methodA(){
try {
System.out.println("同步methodA beigin , 執行緒名稱=" + Thread.currentThread().getName());
Thread.sleep(5000);
System.out.println("同步methodA end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void methodB(){
System.out.println("非同步methodB begin , 執行緒名稱=" + Thread.currentThread().getName());
System.out.println("非同步methodB end");
}
}
package com.demo13;
public class ThreadA extends Thread {
private MyObject myObject;
public ThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.methodA();
}
}
package com.demo13;
public class ThreadB extends Thread {
private MyObject myObject;
public ThreadB(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run(){
myObject.methodB();
}
}
package com.demo13;
public class Run {
public static void main(String[] args) {
MyObject myObject = new MyObject();
ThreadA threadA = new ThreadA(myObject);
ThreadB threadB = new ThreadB(myObject);
threadA.setName("A");
threadB.setName("B");
threadA.start();
threadB.start();
}
}
執行結果:
同步methodA beigin , 執行緒名稱=A
非同步methodB begin , 執行緒名稱=B
非同步methodB end
(此處等待5秒)
同步methodA end
髒讀
上面所述的情況可能導致髒讀
package com.demo14;
public class MyObject {
private String username = "1";
private String password = "11";
synchronized public void setValue(String username , String password){
try {
System.out.println("賦值執行緒名=" + Thread.currentThread().getName());
this.username = username;
Thread.sleep(2000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void getValue(){
System.out.println("取值執行緒名=" + Thread.currentThread().getName());
System.out.println("username=" + username );
System.out.println("password=" + password);
}
}
package com.demo14;
public class ThreadA extends Thread {
MyObject myObject = new MyObject();
public ThreadA(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.setValue("2","22");
}
}
package com.demo14;
public class ThreadB extends Thread {
private MyObject myObject;
public ThreadB(MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
myObject.getValue();
}
}
package com.demo14;
public class Run {
public static void main(String[] args) {
try {
MyObject myObject = new MyObject();
ThreadA threadA = new ThreadA(myObject);
ThreadB threadB = new ThreadB(myObject);
threadA.setName("A");
threadB.setName("B");
threadA.start();
Thread.sleep(500); //給時間 保證讓A將username賦值完畢
threadB.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執行結果:
賦值執行緒名=A
取值執行緒名=B
username=2
password=11
很明顯出現了髒讀。修改MyObject.java檔案
package com.demo14;
public class MyObject {
private String username = "1";
private String password = "11";
synchronized public void setValue(String username , String password){
try {
System.out.println("賦值執行緒名=" + Thread.currentThread().getName());
this.username = username;
Thread.sleep(2000);
this.password = password;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void getValue(){
System.out.println("取值執行緒名=" + Thread.currentThread().getName());
System.out.println("username=" + username );
System.out.println("password=" + password);
}
}
執行結果為:
賦值執行緒名=A
取值執行緒名=B
username=2
password=22
上述說明:B執行緒想要呼叫物件中沒有被A執行緒鎖住的同步方法B,必須等A執行緒釋放A方法的同步鎖。