Java的高併發程式設計系列(一)synchronized鎖
private int count = 10;
public void test(){
synchronized (this) { //任何執行緒要執行下面的程式碼,必須先拿到Demo02物件例項的鎖
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
}
public synchronized void test(){//等同於synchronized(this),鎖定的是Demo03物件的例項
count --;
System.out .println(Thread.currentThread().getName() + " count =" + count);
}
synchronized是對當前物件Object加了一把鎖,在當前執行緒使用過程中其他執行緒無法呼叫,只有上一個執行緒釋放鎖後,其他執行緒才可以使用,所以synchronized是互斥鎖。在這裡需要注意的是,snchronized鎖定的是一個物件,而不是一個類或者程式碼,這個物件是可以自己自定義的,這裡是用this鎖定了自己。以上兩個等同。
public static void test2(){ //考慮一下這裡寫synchronize(this)是否可以
synchronized (Demo04.class) {
count --;
}
}
synchronized當鎖定靜態方法時,那麼此時鎖定的當前類的class。
public class Demo05 implements Runnable{
private int count = 10;
@Override
public /*synchronized*/ void run(){
count --;
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
public static void main(String[] args) {
Demo05 demo05 = new Demo05();
for (int i = 0; i < 5; i++) {
new Thread(demo05,"THREAD" + i).start();
}
}
}
以上程式碼Demo05執行緒,當定義5個執行緒時,共同呼叫count,造成的重用,無法進行資源的共享,當新增 synchronized 時,對run方法加了把鎖,一次只能一個執行緒進行呼叫,不允許其他執行緒訪問,可避免count 的重用,執行緒的重入。synchronized修飾的程式碼塊是原子操作。
注意:在多執行緒中,同步方法和非同步方法時可以同時呼叫的,這兩者之間互不影響。
案例:當對業務寫方法加鎖,而不對讀方法加鎖時,容易產生髒讀問題。
public class Demo08 {
String name;
double balance;
public synchronized void set(String name, double balance){
this.name = name;
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = balance;
}
public synchronized double getBalance(String name){
return this.balance;
}
pblic static void main(String[] args) {
Demo08 demo08 = new Demo08();
new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(demo08.getBalance("zhangsan"));
}
}
在某些業務情況下,當對讀取的資料要求不高時,允許髒讀的情況下,可以不使用synchronized的情況下,提升效能。
一個同步方法可以呼叫另一個同步方法,一個執行緒已經擁有某個物件的鎖,再次申請的時候 仍然會得到該物件的鎖也就是說synchronized獲得的鎖是可重入的。
public class Demo09 {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
test2();
}
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
Demo09 demo09 = new Demo09();
demo09.test1();
}
}
繼承中有可能發生的情形,子類呼叫父類的同步方法
public class Demo10 {
synchronized void test(){
System.out.println("test start........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test end........");
}
public static void main(String[] args) {
new Demo100().test();
}
}
class Demo100 extends Demo10{
@Override
synchronized void test() {
System.out.println("child test start.......");
super.test();
System.out.println("child test end.......");
}
}
總而言之:synchronized修飾的鎖是可重入的,可以被其他synchronized方法呼叫。
死鎖:簡而言之,假如有一個執行緒需要依次鎖定A和B,另一個執行緒需要依次鎖定B和A,此時,第一個執行緒無法鎖定B,第二個執行緒無法鎖定A,這樣就產生了死鎖。
程式在執行過程中,如果出現異常,預設情況鎖會被釋放,所以,在併發處理的過程中,有異常要多加小心,不然可能會發生不一致的情況。比如,在一個web app處理過程中,多個servlet執行緒共同訪問通一個資源,這是如果異常處理不合適,在第一個執行緒中丟擲異常,其他執行緒就會進入同步程式碼去,有可能訪問到異常產生是的資料,因此要非常小心的處理同步業務邏輯中的異常。
public class Demo11 {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1/0; //此處丟擲異常,鎖將被釋放,要想不被釋放,可以在這裡進行catch處理,然後讓迴圈繼續
}
}
}
public static void main(String[] args) {
Demo11 demo11 = new Demo11();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}