Synchornized使用及原理

Synchornized 方法

當一個線程試圖訪問同步代碼塊或對象的方法時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。Java中的每一個對象都可以作爲鎖,具體表現爲:
1. 對於普通同步方法,鎖是當前實例對象
2. 對於同步方法塊,鎖是Synchonized括號裏配置的對象
3. 對於靜態同步方法,鎖是當前類的Class對象

對於普通同步方法,鎖是當前實例對象

不同線程對於同一個對象的鎖互斥同步

  • 鎖是當前實例對象,A線程獲得了object對象的鎖,B線程想在訪問該同步方法必須等A線程將object對象鎖釋放。
package objective2.action1;

public class IncreaseHander {

    public static void main(String[] args) {
        IncrementRunner incrementRunner1 = new IncrementRunner("a");
        new Thread(incrementRunner1).start();
        new Thread(incrementRunner1).start();
    }
}

class IncrementRunner implements Runnable {
    private int num;
    private String name;

    public IncrementRunner(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        add(name);
    }

    public synchronized void add(String name) {
        if (name.equals("a")) {
            num = 100;
            System.out.println(Thread.currentThread().getName() + " a set over ");

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } else {
            num = 200;
            System.out.println(Thread.currentThread().getName() + " other set over");
        }
        System.out.println(Thread.currentThread().getName() + " " + name + " num=" + num);
    }

}
Thread-0 a set over 
Thread-0 a num=100
Thread-1 a set over 
Thread-1 a num=100

可以看出,上面程序按線程0和線程1是按同步方式運行的。首先線程0獲得對象incrementRunner1 的鎖,在完成設置值和休眠後釋放該對象鎖,由線程1獲得,繼續做相同的操作。

不同線程對於不同對象的鎖可以異步

  • synchornized獲得的是對象鎖,若是多個對象,JVM會創建多個鎖
package objective2.action1;

public class IncreaseHander {

    public static void main(String[] args) {
        IncrementRunner r1 = new IncrementRunner("a");
        IncrementRunner r2 = new IncrementRunner("a");
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

class IncrementRunner implements Runnable {
    private int num;
    private String name;

    public IncrementRunner(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        add(name);
    }

    public synchronized void add(String name) {
        if (name.equals("a")) {
            num = 100;
            System.out.println(Thread.currentThread().getName() + " a set over ");

            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        } else {
            num = 200;
            System.out.println(Thread.currentThread().getName() + " other set over");
        }
        System.out.println(Thread.currentThread().getName() + " " + name + " num=" + num);
    }

}
Thread-1 a set over 
Thread-0 a set over 
Thread-0 a num=100
Thread-1 a num=100

上面例子,兩個線程分別訪問同一個類的兩個不同的實例的相同名稱的同步方法,效果卻是以異步的方式運行的。 由於創建了兩個對象,在系統中產生了兩個鎖,線程1不需要在線程0將鎖釋放後才能在進入add方法。

對象鎖對於非synchrnoized方法異步

  • A線程先持有object對象的鎖, B線程可以以異步方式調用object對象中的非synchrnoized類型方法
package objective2.action1;

public class Run {

    public static void main(String[] args) {
        MyObject object = new MyObject();
        ObjectRunner A = new ObjectRunner("A", object);
        ObjectRunner B = new ObjectRunner("B", object);
        new Thread(A).start();
        new Thread(B).start();
    }

}

class ObjectRunner implements Runnable {
    private String methodName;
    private MyObject object;

    public ObjectRunner(String methodName, MyObject object) {
        this.methodName = methodName;
        this.object = object;
    }

    @Override
    public void run() {
        if (methodName.equals("A")) {
            System.out.println("start call method A");
            object.methodA();
        } else {
            System.out.println("start call method B");
            object.methodB();
        }

    }

}

class MyObject {
    public synchronized void methodA() {
        try {
            String threadName = Thread.currentThread().getName();
            System.out.println("begin method A ThreadName" + threadName);
            Thread.sleep(2000);
            System.out.println("end method A ThreadName" + threadName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void methodB() {
        try {
            String threadName = Thread.currentThread().getName();
            System.out.println("begin method B ThreadName=" + threadName);
            Thread.sleep(2000);
            System.out.println("end method B ThreadName=" + threadName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
start call method A
start call method B
begin method B ThreadName=Thread-1
begin method A ThreadNameThread-0
end method B ThreadName=Thread-1
end method A ThreadNameThread-0

將上面程序做一個簡單改動,運行結果完全不一樣:程序運行的順序是異步的。

對象鎖對於synchrnoized方法同步

  • A線程先持有object對象的鎖,B線程如果這時調用object對象中的synchrnoized類型方法需要等待
package objective2.action1;

public class Run {

    public static void main(String[] args) {
        MyObject object = new MyObject();
        ObjectRunner A = new ObjectRunner("A", object);
        ObjectRunner B = new ObjectRunner("B", object);
        new Thread(A).start();
        new Thread(B).start();
    }

}

class ObjectRunner implements Runnable {
    private String methodName;
    private MyObject object;

    public ObjectRunner(String methodName, MyObject object) {
        this.methodName = methodName;
        this.object = object;
    }

    @Override
    public void run() {
        if (methodName.equals("A")) {
            System.out.println("start call method A");
            object.methodA();
        } else {
            System.out.println("start call method B");
            object.methodB();
        }

    }

}

class MyObject {
    public synchronized void methodA() {
        try {
            String threadName = Thread.currentThread().getName();
            System.out.println("begin method A ThreadName" + threadName);
            Thread.sleep(2000);
            System.out.println("end method A ThreadName" + threadName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public synchronized void methodB() {
        try {
            String threadName = Thread.currentThread().getName();
            System.out.println("begin method B ThreadName=" + threadName);
            Thread.sleep(2000);
            System.out.println("end method B ThreadName=" + threadName);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

start call method A
start call method B
begin method A ThreadNameThread-0
end method A ThreadNameThread-0
begin method B ThreadName=Thread-1
end method B ThreadName=Thread-1

線程0先持有object對象的鎖,線程1如果這時調用object對象中的synchrnoized類型方法需要等待,即使調用的是不同的方法。
也就是說,對象的鎖能鎖住對象所有的synchrnoized方法。
總之還是那句話,對於普通同步方法,鎖是當前實例對象。方法A,方法B的鎖都是object對象。一個線程訪問該方法(拿到鎖),其它線程必須等待。

synchrnoized鎖可重入

  • synchrnoized鎖可重入
    當某個線程請求一個由其它線程持有的鎖時,發出請求的線程就會堵塞。然而,由於內置鎖是可入的,因此如果某個線程試圖獲得一個已經由自己或自己父類持有的鎖,那麼這個請求就會成功。
package objective2.action1;

public class RunService {

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }

}

class MyThread extends Thread {

    @Override
    public void run() {
        Service service = new Service();
        service.service1();
    }
}

class Service {
    public synchronized void service1() {
        System.out.println(Thread.currentThread().getName() + " call Service 1");
        service2();
    }

    public synchronized void service2() {
        System.out.println(Thread.currentThread().getName() + " call Service 2");
    }
}
Thread-0 call Service 1
Thread-0 call Service 2

“可重入鎖”的概念是:自己可以再次獲取自己的內部鎖。 如上面程序運行,在調用方法service1時,線程0獲取service對象的鎖,此時鎖還沒有釋放,調用方法service2時,又一次獲取到了service對象的鎖。如果同一線程不可重入鎖,就會造成死鎖。

  • 可重入鎖支持在父子繼承環境中
package objective2.action1;

public class RunService {

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }

}

class MyThread extends Thread {

    @Override
    public void run() {
        Service service = new SubService();
        service.service2();
    }
}

class Service {
    public synchronized void service1() {
        System.out.println(Thread.currentThread().getName() + " call Service 1");
    }

    public synchronized void service2() {
        System.out.println(Thread.currentThread().getName() + " call Service 2");
    }
}

class SubService extends Service {
    public synchronized void service2() {
        System.out.println(Thread.currentThread().getName() + " call Sub Service 2");
        super.service1();
    }
}
Thread-0 call Sub Service 2
Thread-0 call Service 1

同步不能繼承

  • 同步不具有繼承性
package objective2.action1;

public class TestService {
    public static void main(String[] args) {
        Sub sub = new Sub();
        ServiceRunner serviceRunner = new ServiceRunner(sub);
        new Thread(serviceRunner, "A").start();
        new Thread(serviceRunner, "B").start();

    }
}

class ServiceRunner implements Runnable {
    private Sub sub;

    public ServiceRunner(Sub sub) {
        this.sub = sub;
    }

    @Override
    public void run() {
        sub.service();
    }

}

class Main {

    public synchronized void service() {
        String threadName = Thread.currentThread().getName();
        System.out.println("in main, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());
        sleep(1000);
        System.out.println("in main, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());

    }

    protected void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

class Sub extends Main {
    public void service() {
        String threadName = Thread.currentThread().getName();
        System.out.println("in Sub, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());
        sleep(1000);
        System.out.println("in Sub, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());
        super.service();
    }
}
in Sub, ThreadName=B beginTime=1496618308264
in Sub, ThreadName=A beginTime=1496618308264
in Sub, ThreadName=B endTime=1496618309266
in Sub, ThreadName=A endTime=1496618309266
in main, ThreadName=B beginTime=1496618309266
in main, ThreadName=B endTime=1496618310267
in main, ThreadName=A beginTime=1496618310267
in main, ThreadName=A endTime=1496618311268

父類Main的方法加synchronized,子類Sub繼承該方法但是不加synchronized,從運行結果看:A,B兩個線程幾乎同時進入子類非同步方法,是異步執行的。但是在主類中由於加了鎖,必須B釋放鎖A才能進入。

  • 子類與父類是同一個鎖
package objective2.action1;

public class TestService {
    public static void main(String[] args) {
        Sub sub = new Sub();
        ServiceRunner serviceRunner = new ServiceRunner(sub);
        new Thread(serviceRunner, "A").start();
        new Thread(serviceRunner, "B").start();

    }
}

class ServiceRunner implements Runnable {
    private Sub sub;

    public ServiceRunner(Sub sub) {
        this.sub = sub;
    }

    @Override
    public void run() {
        sub.service();
    }

}

class Main {

    public synchronized void service() {
        String threadName = Thread.currentThread().getName();
        System.out.println("in main, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());
        sleep(1000);
        System.out.println("in main, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());

    }

    protected void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

class Sub extends Main {
    public synchronized void service() {
        String threadName = Thread.currentThread().getName();
        System.out.println("in Sub, ThreadName=" + threadName + " beginTime=" + System.currentTimeMillis());
        sleep(1000);
        System.out.println("in Sub, ThreadName=" + threadName + " endTime=" + System.currentTimeMillis());
        super.service();
    }
}
in Sub, ThreadName=A beginTime=1496618631925
in Sub, ThreadName=A endTime=1496618632926
in main, ThreadName=A beginTime=1496618632926
in main, ThreadName=A endTime=1496618633926
in Sub, ThreadName=B beginTime=1496618633926
in Sub, ThreadName=B endTime=1496618634927
in main, ThreadName=B beginTime=1496618634927
in main, ThreadName=B endTime=1496618635927

在子類中也加上鎖,A,B兩個線程運行同步,只有A釋放了鎖,B才能拿到鎖進入方法。
不管子類還是父類,在這個例子中,鎖是sub對象,一個線程拿到了該對象的鎖,其它線程必須等待該線程釋放。

這麼多例子,其實就是一句話, 對於普通同步方法,鎖是當前實例對象。

對於同步方法塊,鎖是Synchonized括號裏配置的對象

synchornized 方法的弊端

對於普通同步方法,鎖是當前實例對象,一個線程拿到了該對象的鎖,其它線程必須等待該線程釋放。雖然解決了線程安全的問題,但是,很大程度上也犧牲了響應速度,性能。
還是用【買票例子】:5個售票員出售10張票,不能不同售票員出售同一張票,每買一張耗時1s,直到票出售完爲止。
如下程序使用synchornized 方法實現該功能,雖然解決了線程安全的問題,但弊端很明顯,任何一個線程拿到該對象的鎖,其他線程都得等它釋放鎖才能進入該方法。10張票賣完要10s,性能很差。

package objective2.action1;

import java.util.concurrent.CountDownLatch;

public class Counter {

    private static int tickets = 10;
    private static int threadNum = 5;
    private static CountDownLatch timeLatch = new CountDownLatch(threadNum);

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        Runnable counter = new CounterRunner(tickets, timeLatch);
        Thread[] threads = new Thread[threadNum];

        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(counter, "售票員" + i);

        }
        for (Thread thread : threads) {
            thread.start();
        }

        try {
            timeLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long takeTime = System.currentTimeMillis() - startTime;
        System.out.println("takeTime=" + takeTime + "ms");
    }

}

class CounterRunner implements Runnable {
    private int tickets;
    private CountDownLatch timeLatch;

    public CounterRunner(int tickets, CountDownLatch timeLatch) {
        this.tickets = tickets;
        this.timeLatch = timeLatch;
    }

    @Override
    public void run() {
        while (tickets > -1) {
            if (saleTickets()) {
                break;
            }
        }
    }

    /**
     * 
     * 買票實現
     * 
     * @return 賣完返回true,沒賣完返回false
     */
    private synchronized boolean saleTickets() {
        String threadName = Thread.currentThread().getName();
        if (tickets == 0) {
            System.out.println(threadName + " 沒票了");
            timeLatch.countDown();
            return true;
        } else {
            System.out.println(threadName + " 出售票號" + tickets--);
            sleep(1000);
        }
        return false;
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
售票員0 出售票號10
售票員0 出售票號9
售票員0 出售票號8
售票員3 出售票號7
售票員4 出售票號6
售票員4 出售票號5
售票員2 出售票號4
售票員1 出售票號3
售票員2 出售票號2
售票員2 出售票號1
售票員4 沒票了
售票員3 沒票了
售票員0 沒票了
售票員2 沒票了
售票員1 沒票了
takeTime=10009ms

使用同步代碼塊解決弊端

package objective2.action1;

import java.util.concurrent.CountDownLatch;

public class Counter {

    private static int tickets = 10;
    private static int threadNum = 5;
    private static CountDownLatch timeLatch = new CountDownLatch(threadNum);

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        Runnable counter = new CounterRunner(tickets, timeLatch);
        Thread[] threads = new Thread[threadNum];

        for (int i = 0; i < threadNum; i++) {
            threads[i] = new Thread(counter, "售票員" + i);

        }
        for (Thread thread : threads) {
            thread.start();
        }

        try {
            timeLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long takeTime = System.currentTimeMillis() - startTime;
        System.out.println("takeTime=" + takeTime + "ms");
    }

}

class CounterRunner implements Runnable {
    private int tickets;
    private CountDownLatch timeLatch;

    public CounterRunner(int tickets, CountDownLatch timeLatch) {
        this.tickets = tickets;
        this.timeLatch = timeLatch;
    }

    @Override
    public void run() {
        while (tickets > -1) {
            if (saleTickets()) {
                break;
            }
        }
    }

    /**
     * 
     * 買票實現
     * 
     * @return 賣完返回true,沒賣完返回false
     */
    private boolean saleTickets() {
        String threadName = Thread.currentThread().getName();
        if (tickets == 0) {
            System.out.println(threadName + " 沒票了");
            timeLatch.countDown();
            return true;
        } else {
            synchronized (this) {
                System.out.println(threadName + " 出售票號" + tickets--);
            }
            sleep(1000);
        }
        return false;
    }

    public static void sleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}
售票員0 出售票號10
售票員3 出售票號9
售票員4 出售票號8
售票員1 出售票號7
售票員2 出售票號6
售票員1 出售票號5
售票員0 出售票號4
售票員3 出售票號3
售票員4 出售票號2
售票員2 出售票號1
售票員3 沒票了
售票員0 沒票了
售票員1 沒票了
售票員4 沒票了
售票員2 沒票了
takeTime=2004ms

從運行結果看,使用Synchornized代碼塊,在線程安全情況下,運行時間縮短,性能改善。

將任意對象作爲對象監視器

synchronized(非this對象x) 格式的寫法是將x對象本身作爲“對象監視1器”(和synchronized(this)類似,只不過將當前類
對象改成x對象),也有如下結論:
1、當多個線程同時執行synchroized(x){}同步代碼塊時呈現同步效果。
2、當其他線程執行x對象中synchronized同步方法呈現同步效果
3、當其他線程執行x對象裏面的synchroized(this)代碼塊或方法,也呈現同步效果。
總之一句話,對於同步方法塊,鎖是Synchonized括號裏配置的對象,對象相同就同步,不同就異步。
eg.創建一個只能加入一個元素的List.

package objective2.action1;

import java.util.ArrayList;
import java.util.List;

public class RunOneList {

    public static void main(String[] args) {
        MyOneList myOneList = new MyOneList();
        MyService myService1 = new MyService(myOneList);
        new Thread(myService1, "A").start();
        new Thread(myService1, "B").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("myOneList Size = " + myOneList.getSize());
    }

}

class MyService implements Runnable {
    private MyOneList myOneList;

    public MyService(MyOneList myOneList) {
        this.myOneList = myOneList;
    }

    @Override
    public void run() {
        addService();

    }

    private void addService() {
        if (myOneList.getSize() < 1) {
            String data = getDataFromRemote();
            myOneList.add(data);
        }
    }

    private String getDataFromRemote() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "data";
    }
}

class MyOneList {
    private List<String> list = new ArrayList<String>();

    public synchronized void add(String data) {
        list.add(data);
    }

    public synchronized int getSize() {
        return list.size();
    }
}
myOneList Size = 2

從運行結果看,雖然MyOneList的方法都使用synchronized保證同步,但是add和getSize非原子操作,兩線程以異步方式返回了MyOneList Size大小,導致髒讀。

package objective2.action1;

import java.util.ArrayList;
import java.util.List;

public class RunOneList {

    public static void main(String[] args) {
        MyOneList myOneList = new MyOneList();
        MyService myService1 = new MyService(myOneList);
        new Thread(myService1, "A").start();
        new Thread(myService1, "B").start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("myOneList Size = " + myOneList.getSize());
    }

}

class MyService implements Runnable {
    private MyOneList myOneList;

    public MyService(MyOneList myOneList) {
        this.myOneList = myOneList;
    }

    @Override
    public void run() {
        addService();

    }

    private void addService() {
        String data = getDataFromRemote();
        synchronized (myOneList) {
            if (myOneList.getSize() < 1) {
                myOneList.add(data);
            }
        }

    }

    private String getDataFromRemote() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return "data";
    }
}

class MyOneList {
    private List<String> list = new ArrayList<String>();

    public synchronized void add(String data) {
        list.add(data);
    }

    public synchronized int getSize() {
        return list.size();
    }
}
myOneList Size = 1

Synchornized 的實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章