【Synchronized】對象鎖 & 類鎖(二)

1、synchronized關鍵字

synchronized關鍵字有以下幾種用法:javascript

  1. 非靜態方法或靜態方法上加入關鍵字synchronized;
  2. 使用synchronized(對象/this/類.class)靜態快;

下面對上述兩種狀況進行區分。java

2、對象鎖和類鎖

多線程的線程同步機制其實是靠鎖的概念來控制的。編程

在Java程序運行時環境中,JVM須要對兩類線程共享的數據進行協調:安全

  1. 保存在堆中的實例變量
  2. 保存在方法區中的類變量

這兩類數據是被全部線程共享的。 (程序不須要協調保存在Java 棧當中的數據。由於這些數據是屬於擁有該棧的線程所私有的) 下面說說在虛擬機中內存的分配多線程

  • 方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作Non-Heap(非堆),目的應該是與Java堆區分開來。
  • :在Java中,JVM中的棧記錄了線程的方法調用。每一個線程擁有一個棧。在某個線程的運行過程當中,若是有新的方法調用,那麼該線程對應的棧就會增長一個存儲單元,即幀(frame)。在frame中,保存有該方法調用的參數、局部變量和返回地址。
  • 是JVM中一塊可自由分配給對象的區域。當咱們談論垃圾回收(garbage collection)時,咱們主要回收堆(heap)的空間。

Java的普通對象存活在堆中。與棧不一樣,堆的空間不會隨着方法調用結束而清空。所以,在某個方法中建立的對象,能夠在方法調用結束以後,繼續存在於堆中。這帶來的一個問題是,若是咱們不斷的建立新的對象,內存空間將最終消耗殆盡。異步

在java虛擬機中,每一個對象和類在邏輯上都是和一個監視器相關聯的。ide

  • 對於對象來講,相關聯的監視器保護對象的實例變量。
  • 對於類來講,監視器保護類的類變量。

(若是一個對象沒有實例變量,或者一個類沒有變量,相關聯的監視器就什麼也不監視。)性能

爲了實現監視器的排他性監視能力,java虛擬機爲每個對象和類都關聯一個鎖。表明任什麼時候候只容許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。this

可是若是線程獲取了鎖,那麼在它釋放這個鎖以前,就沒有其餘線程能夠獲取一樣數據的鎖了。(鎖住一個對象就是獲取對象相關聯的監視器)spa

類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會建立一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。

一個線程能夠屢次對同一個對象上鎖。對於每個對象,java虛擬機維護一個加鎖計數器,線程每得到一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值爲0時,鎖就被徹底釋放了。

java編程人員不須要本身動手加鎖,對象鎖是java虛擬機內部使用的。

在java程序中,只須要使用synchronized塊或者synchronized方法就能夠標誌一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。

3、synchronized關鍵字各類用法與實例

synchronized修飾非靜態方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象,線程想要執行對應同步代碼,須要得到對象鎖。

synchronized修飾靜態方法以及同步代碼塊的synchronized (類.class)用法鎖的是類,線程想要執行對應同步代碼,須要得到類鎖。

一、同一個對象中的方法鎖 ---> 對象鎖

先來看一個非線程安全實例:

public class Run {

        public static void main(String[] args) {

            HasSelfPrivateNum numRef = new HasSelfPrivateNum();

            ThreadA athread = new ThreadA(numRef);
            athread.start();

            ThreadB bthread = new ThreadB(numRef);
            bthread.start();
        }
    }

     
    class HasSelfPrivateNum {

        private int num = 0;

        public void addI(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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

     

    class ThreadA extends Thread {

        private HasSelfPrivateNum numRef;

        public ThreadA(HasSelfPrivateNum numRef) {
            super();
            this.numRef = numRef;
        }
     
        @Override
        public void run() {
            super.run();
            numRef.addI("a");
        }
     
    }

     

     class ThreadB extends Thread {

        private HasSelfPrivateNum numRef;

        public ThreadB(HasSelfPrivateNum numRef) {
            super();
            this.numRef = numRef;
        }
     
        @Override
        public void run() {
            super.run();
            numRef.addI("b");
        }
    }
運行結果爲:

    a set over!
    b set over!
    b num=200
    a num=100

修改HasSelfPrivateNum以下,方法用synchronized修飾以下:

class HasSelfPrivateNum {

        private int num = 0;

        public synchronized void addI(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) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }    
    }
運行結果是線程安全的:

    b set over!
    b num=200
    a set over!
    a num=100

實驗結論:兩個線程訪問同一個對象中的同步方法是必定是線程安全的。本實現因爲是同步訪問,因此先打印出a,而後打印出b

這裏線程獲取的是HasSelfPrivateNum的對象實例的鎖——對象鎖。

二、多個對象多個鎖 ---> 不一樣步

就上面的實例,咱們將Run改爲以下:

public class Run {

        public static void main(String[] args) {

            HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
            HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
     
            ThreadA athread = new ThreadA(numRef1);
            athread.start();
     
            ThreadB bthread = new ThreadB(numRef2);
            bthread.start();
        }
    }
運行結果爲:

    a set over!
    b set over!
    b num=200
    a num=200

這裏是非同步的,由於線程athread得到是numRef1的對象鎖,而bthread線程獲取的是numRef2的對象鎖,他們並無在獲取鎖上有競爭關係,所以,出現非同步的結果 。

三、同步塊synchronized (this) ---> 同步,對象鎖

public class Run {

        public static void main(String[] args) {

            ObjectService service = new ObjectService();

            ThreadA a = new ThreadA(service);
            a.setName("a");
            a.start();

            ThreadB b = new ThreadB(service);
            b.setName("b");
            b.start();
        }
    }

    class ObjectService {

        public void serviceMethod() {
            try {
                synchronized (this) {
                    System.out.println("begin time=" + System.currentTimeMillis());
                    Thread.sleep(2000);
                    System.out.println("end    end=" + System.currentTimeMillis());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    class ThreadA extends Thread {

        private ObjectService service;

        public ThreadA(ObjectService service) {
            super();
            this.service = service;
        }

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

    class ThreadB extends Thread {

        private ObjectService service;

        public ThreadB(ObjectService service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            super.run();
            service.serviceMethod();
        }
    }
運行結果:

    begin time=1466148260341
    end    end=1466148262342
    begin time=1466148262342
    end    end=1466148264378

這樣也是同步的,線程獲取的是同步塊synchronized (this)括號()裏面的對象實例的對象鎖,這裏就是ObjectService實例對象的對象鎖了。須要注意的是synchronized (){}的{}先後的代碼依舊是異步的。

四、synchronized (非this對象) ---> 對象鎖

public class Run {

        public static void main(String[] args) {

            Service service = new Service("ss");

            ThreadA a = new ThreadA(service);
            a.setName("A");
            a.start();

            ThreadB b = new ThreadB(service);
            b.setName("B");
            b.start();
        }
    }

    class Service {

        String anyString = new String();

        public Service(String anyString){
            this.anyString = anyString;
        }

        public void setUsernamePassword(String username, String password) {
            try {
                synchronized (anyString) {
                    System.out.println("線程名稱爲:" + Thread.currentThread().getName()
                            + "在" + System.currentTimeMillis() + "進入同步塊");
                    Thread.sleep(3000);
                    System.out.println("線程名稱爲:" + Thread.currentThread().getName()
                            + "在" + System.currentTimeMillis() + "離開同步塊");
                }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    class ThreadA extends Thread {

        private Service service;

        public ThreadA(Service service) {
            super();
            this.service = service;
        }

        @Override
        public void run() {
            service.setUsernamePassword("a", "aa");
        }
    }

    class ThreadB extends Thread {

        private Service service;

        public ThreadB(Service service) {
            super();
            this.service = service;
        } 

        @Override
        public void run() {
            service.setUsernamePassword("b", "bb");
        }
    }

不難看出,這裏線程爭奪的是anyString的對象鎖,兩個線程有競爭同一對象鎖的關係,出現同步。如今有一個問題:一個類裏面有兩個非靜態同步方法,會有影響麼?

答案是:若是對象實例A,線程1得到了對象A的對象鎖,那麼其餘線程就不能進入,須要得到對象實例A的對象鎖才能訪問的同步代碼(包括同步方法和同步塊)。

五、靜態synchronized同步方法 ---> 類鎖

public class Run {

        public static void main(String[] args) {

            ThreadA a = new ThreadA();
            a.setName("A");
            a.start();

            ThreadB b = new ThreadB();
            b.setName("B");
            b.start();
        }
    }

    class Service {

        public synchronized 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();
            }
        }

        public synchronized static void printB() {
            System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在"
                    + System.currentTimeMillis() + "進入printB");
            System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在"
                    + System.currentTimeMillis() + "離開printB");
        }
    }

    class ThreadA extends Thread {

        @Override
        public void run() {
            Service.printA();
        }
    }

    class ThreadB extends Thread {

        @Override
        public void run() {
            Service.printB();
        }
    }
運行結果:

    線程名稱爲:A在1466149372909進入printA
    線程名稱爲:A在1466149375920離開printA
    線程名稱爲:B在1466149375920進入printB
    線程名稱爲:B在1466149375920離開printB

兩個線程在爭奪同一個類鎖,所以同步

六、synchronized (class) ---> 類鎖

對上面Service類代碼修改爲以下:

class Service {

        public static void printA() {

            synchronized (Service.class) {
                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();
                }
            }
        }

        public static void printB() {

            synchronized (Service.class) {
                System.out.println("線程名稱爲:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "進入printB");
                System.out.println("線程名稱爲:" + Thread.currentThread().getName()
                        + "在" + System.currentTimeMillis() + "離開printB");
            }
        }
    }
運行結果:

    線程名稱爲:A在1466149372909進入printA
    線程名稱爲:A在1466149375920離開printA
    線程名稱爲:B在1466149375920進入printB
    線程名稱爲:B在1466149375920離開printB

兩個線程依舊在爭奪同一個類鎖,所以同步。

須要特別說明:對於同一個類A,線程1爭奪A對象實例的對象鎖,線程2爭奪類A的類鎖,這二者不存在競爭關係。也就說對象鎖和類鎖互補干預內政

靜態方法則必定會同步,非靜態方法需在單例模式才生效,可是也不能都用靜態同步方法,總之用得很差可能會給性能帶來極大的影響。另外,有必要說一下的是Spring的bean默認是單例的。

~~~~~~~~~~~~~~~end~~~~~~~~~~~~~~~~~

留到做業題,下面的同步代碼是否會爭鎖?

public class TestThreadLock {

	public static void main(String[] args) {
		Service service1 = new Service("service1");
		Service service2 = new Service("service2");
		Thread a = new ThreadA(service1);
		a.start();
		Thread b = new ThreadB(service2);
		b.start();
	}
}

class Service{
	String name = null;
	public Service(String name){
		this.name = name;
	}
	public synchronized void add1(String num){
		try {
				System.out.println(num+"(1):"+System.currentTimeMillis());
				Thread.sleep(3000);
				System.out.println(num+"(1):"+System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public synchronized void add2(String num){
		try {
				System.out.println(num+"(2):"+System.currentTimeMillis());
				Thread.sleep(3000);
				System.out.println(num+"(2):"+System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

class ThreadA extends Thread{
	public Service service;
	public ThreadA(Service service){
		this.service = service;
	}
	@Override
	public void run(){
		service.add1("A");
	}
}

class ThreadB extends Thread{
	public Service service;
	public ThreadB(Service service){
		this.service = service;
	}
	@Override
	public void run(){
		service.add2("B");
	}
}
相關文章
相關標籤/搜索