一個類中的兩個方法都加了同步鎖,多個線程能同時訪問這個類的兩個方法嗎?

看到這個問題第一反應是不懂,然後查詢了網上的一些說法,感覺略有不一。細看之下發現大家對這個問題的分析角度不太一樣,就會出現不同的結果,在這裏記一下我收集來的不同的角度和自己的例子,感覺這個題目還是蠻有意思的。
首先,同步鎖有兩種,JVM的synchronized和JDK的ReentrantLock;
然後,多個線程訪問這個類的兩個方法也有不同的形式,例如訪問這個類的兩個方法是通過同一個類的實例對象來訪問還是通過不同的類的實例對象訪問;
再者,一個類的兩個方法加了同步鎖,這兩個被同步方法也沒有說明是什麼樣的方法。他可能是類的普通實例方法,也可能是類中Runnable對象的run方法。
看到這裏也許會覺得我對於問題過於的咬文嚼字,但是我想要探討更多的可能,不同的情形有着不同的結果,而且這些不同的情形能開拓思路,讓我們看問題能多個角度,也可以幫我加深多線程的理解。如果本文中有錯誤或者不恰當的例子,或者代碼寫的不嚴謹不規範風格不好,都可以留言提出。
一.synchronized
1.多個線程同時訪問同一個類實例對象的兩個同步方法:

package synchronizedTest;

public class Example1 {
	private int num = 0 ;
	(省略getter.setter,後同)
	public synchronized void method1() {
		System.out.println("同步方法1進入");
		for(int i = 0 ; i<10 ; i++) {
			System.out.print("同步方法1:"+num+"--");
			num++ ;
		}
		System.out.println("同步方法1結束");
	}
	
	public synchronized void method2() {
		System.out.println("method2進入:");
		for(int i = 0 ; i<10 ; i++) {
			System.out.print("method2:"+num+"--");
			num++ ;
		}
		System.out.println("method2結束");
	}
	
	public static void main(String[] args) {
		final Example1 example1 = new Example1() ;
		
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				example1.method1();
			}
		}) ;
		
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				example1.method2();
			}
		}) ;
		try {
			thread2.join();
			thread1.join();			
			thread1.start();
			thread2.start();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}
	
}

輸出結果:

method1進入
同步方法1:0--同步方法1:1--同步方法1:2--同步方法1:3--同步方法1:4--同步方法1:5--同步方法1:6--同步方法1:7--同步方法1:8--同步方法1:9--method1結束
method2進入:
method2:10--method2:11--method2:12--method2:13--method2:14--method2:15--method2:16--method2:17--method2:18--method2:19--method2結束

顯然此時多個線程是不能訪問同個類(的一個實例對象)的兩個同步方法的

2.多個線程同時訪問同一個類的不同實例對象的兩個同步方法:
將上面的代碼稍作修改,主函數中多new一個該類實例

final Example1 example2 = new Example1() ;

再修改thread2的run方法調用的類實例爲example2

Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				example2.method2();
			}
		}) ;

得到結果:

同步方法1進入
method2進入:
method2:0--method2:1--同步方法1:0--同步方法1:1--同步方法1:2--method2:2--同步方法1:3--method2:3--同步方法1:4--method2:4--同步方法1:5--method2:5--同步方法1:6--同步方法1:7--method2:6--同步方法1:8--同步方法1:9--method2:7--同步方法1結束method2:8--method2:9--method2結束

這時候顯然,多個線程是能訪問同個類(的不同實例對象)的兩個同步方法的。
小結:這是因爲synchronized是對象鎖,即線程獲得的鎖是施加在一個實例對象上的,如果不同的線程訪問的是同一對象上的不同的同步方法,那麼顯然不能同時進行。
如果是不同對象上的不同的同步方法,那麼就是可以同時進行的。
3.多個線程同時訪問同一個類實例對象的兩個Runnable對象的run方法:
 

package synchronizedTest;

public class Example2 {
	private int num ;
	public Runnable runnable1 = new Runnable() {
		@Override
		public void run() {
			//同步鎖
			synchronized (this) {
				System.out.println("線程1進入");
				for(int i = 0 ; i < 10 ; i ++) {
					System.out.print("線程1:"+num+"--");
				}
				System.out.println("線程1結束");
			}
		}
	};
	
	public Runnable runnable2 = new Runnable() {
		@Override
		public void run() {
			//同步鎖
			synchronized (this) {
				System.out.println("thread2進入");
				for(int i = 0 ; i < 10 ; i ++) {
					System.out.print("thread2:"+num+"--");
				}
				System.out.println("thread2結束");
			}
		}
	};
	
	public static void main(String[] args) {
		Example2 example = new Example2() ; //創建一個對象
		new Thread(example.runnable1).start(); //同步方法1
		new Thread(example.runnable2).start(); //同步方法2
	}
}

輸出結果:

thread2進入
線程1進入
thread2:0--線程1:0--線程1:0--thread2:0--線程1:0--線程1:0--線程1:0--thread2:0--線程1:0--thread2:0--thread2:0--線程1:0--thread2:0--線程1:0--thread2:0--thread2:0--線程1:0--thread2:0--線程1:0--thread2:0--線程1結束
thread2結束

可見此時多個線程是能同時訪問同個類的兩個同步方法的。這是因爲
synchronized(this){ //... }中鎖住的不是代碼塊,即這個鎖在run方法中,但是並不是同步了這個run方法,而是括號中的對象this,也就是說,多個線程會拿到各自的鎖,就能夠同時執行run方法。(在run方法前聲明synchronized也是同樣的效果)
new Thread(example.runnable1).start(); //同步方法1
new Thread(example.runnable2).start(); //同步方法2
打印出這個this對象,是兩個不同的類實例對象:
synchronizedTest.Example2$1@65db6dfa
synchronizedTest.Example2$2@471fab
也說明了不同線程的實例對象不同,都是各自對象的鎖,不可以認爲是類似於例子1中的同一實例對象,而應該類似與例子2的不同類的實例對象
總結:分析synchronized同步鎖的核心在於他是個對象鎖,找清楚鎖的對象
二.ReentrantLock鎖
1.多個線程同時訪問同一個類實例對象的兩個同步方法:
將例子1的synchronized改爲引入ReentrantLock

package ReentrantLockTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
	private int num;
	private Lock lock = new ReentrantLock();

	public void method1() {
			lock.lock();
			System.out.println("同步方法1進入");
			for(int i = 0 ; i<10 ; i++) {
				System.out.print("同步方法1:"+num+"--");
				num++ ;
			}
			System.out.println("同步方法1結束");
			lock.unlock();
	}

	public void method2() {
		lock.lock();
		System.out.println("method2進入:");
		for (int i = 0; i < 10; i++) {
			System.out.print("method2:" + num + "--");
			num++;
		}
		System.out.println("method2結束");
		lock.unlock();
	}

	public static void main(String[] args) {
		final LockExample example = new LockExample() ;
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				example.method1();
			}
		}) ;
		
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				example.method2();
			}
		}) ;
		
		try {
			thread2.join();
			thread1.join();
			thread1.start();
			thread2.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

輸出結果:

同步方法1進入
同步方法1:0--同步方法1:1--同步方法1:2--同步方法1:3--同步方法1:4--同步方法1:5--同步方法1:6--同步方法1:7--同步方法1:8--同步方法1:9--同步方法1結束
method2進入:
method2:10--method2:11--method2:12--method2:13--method2:14--method2:15--method2:16--method2:17--method2:18--method2:19--method2結束

可見此時多個線程是不能訪問同個類(的一個實例對象)的兩個同步方法的

2.多個線程同時訪問同一個類的不同實例對象的兩個同步方法:
修改main函數的即可:

public static void main(String[] args) {
		final LockExample example1 = new LockExample() ;//兩個實例
		final LockExample example2 = new LockExample() ;
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				example1.method1(); //實例1的同步方法1
			}
		}) ;
		
		Thread thread2 = new Thread(new Runnable() {
			@Override
			public void run() {
				example2.method2();//實例2的同步方法2
			}
		}) ;
		
		try {
			thread2.join();
			thread1.join();
			thread1.start();
			thread2.start();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

輸出結果:

同步方法1進入
method2進入:
同步方法1:0--method2:0--method2:1--同步方法1:1--method2:2--同步方法1:2--同步方法1:3--method2:3--同步方法1:4--method2:4--同步方法1:5--同步方法1:6--method2:5--同步方法1:7--method2:6--同步方法1:8--同步方法1:9--同步方法1結束
method2:7--method2:8--method2:9--method2結束

可見,多個線程是能訪問同個類(的不同實例對象)的兩個同步方法的。

總結:ReentrantLock和synchronized的前兩個例子結論都相同

3.多個線程同時訪問同一個類實例對象的兩個Runnable對象的run方法:

package ReentrantLockTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Lockexample2 {

	private int num;
	private Lock lock = new ReentrantLock();
	
	public Runnable runnable1 = new Runnable() {
		@Override
		public void run() {
			lock.lock();//上鎖
			System.out.println("線程1進入");
			for(int i = 0 ; i < 10 ; i ++) {
				System.out.print("線程1:"+num+"--");				
			}
			System.out.println("線程1結束");
			lock.unlock();
		}
	};
	
	public Runnable runnable2 = new Runnable() {
		@Override
		public void run() {
			lock.lock();//上鎖
			System.out.println("thread2進入");
			for(int i = 0 ; i < 10 ; i ++) {
				System.out.print("thread2:"+num+"--");
			}
			System.out.println("thread2結束");
			lock.unlock();
		}
	};
	
	public static void main(String[] args) {
		Lockexample2 example = new Lockexample2();
		new Thread(example.runnable1).start(); 
		new Thread(example.runnable2).start(); 
		
	}
}

輸出結果:

線程1進入
線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1:0--線程1結束
thread2進入
thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2結束

這裏可以看到,與synchronized的第三個例子出現了不同的結果。在這個地方,ReentrantLock不允許多線程同時訪問一個類的不同同步方法。
這裏要注意的是ReentrantLock與synchronized不同,ReentrantLock的實現方式是要先創建ReentrantLock對象,然後用這個對象的方法來上鎖。而一個類的實例中只有一個ReentrantLock對象:
private Lock lock = new ReentrantLock();
1
而本例中,線程的創建是建立在同一個類實例上的:
Lockexample2 example = new Lockexample2();
new Thread(example.runnable1).start(); 
new Thread(example.runnable2).start(); 
因此,ReentrantLock對象lock是同一個,因此第一個線程進入同步方法1後就獲取了鎖,第二個線程無法獲取這個鎖,只能等待。
--------------------- 
作者:Geepyyyy 
來源:CSDN 
原文:https://blog.csdn.net/weixin_40616523/article/details/87883267 
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

如果換成是兩個實例對象:

public static void main(String[] args) {
		Lockexample2 example = new Lockexample2();
		Lockexample2 example2 = new Lockexample2();
		new Thread(example.runnable1).start(); 
		new Thread(example2.runnable2).start(); 
		
	}

輸出結果:

線程1進入
thread2進入
線程1:0--線程1:0--線程1:0--線程1:0--thread2:0--線程1:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--thread2:0--線程1:0--thread2:0--線程1:0--線程1:0--thread2:0--線程1:0--thread2結束
線程1:0--線程1結束

可見不同的實例對象中是不同的ReentrantLock對象,因此可以同時訪問
小結:ReentrantLock鎖的核心在與ReentrantLock對象是不是同一個
三.結論
重新看看這個問題:一個類中的兩個方法都加了同步鎖,多個線程能同時訪問這個類的兩個方法嗎?
現在應該比較清楚了,這個問題要分成synchronized和ReentrantLock兩個情況:
一.對於synchronized
1.一個類中的兩個方法都加了同步鎖,多個線程不能同時訪問這個類的同一實例對象的兩個方法
2.一個類中的兩個方法都加了同步鎖,多個線程能同時訪問這個類的不同實例對象的兩個方法
3.一個類中的兩個方法**(Runnable的run方法)都加了同步鎖,多個線程能**同時訪問這個類的兩個方法(不論是不是同一實例對象)
二.對於ReentrantLock
1.一個類中的兩個方法都加了同步鎖,多個線程不能同時訪問這個類的同一實例對象的兩個方法(不論同步加在實例方法中或是run方法中)
2.一個類中的兩個方法都加了同步鎖,多個線程能同時訪問這個類的不同實例對象的兩個方法(不論同步加在實例方法中或是run方法)

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