定義
Java中具有通過synchronized實現的內置鎖,內置鎖獲取鎖和釋放鎖的過程是隱式的,進入synchronized修飾的代碼就獲得鎖,離開相應的代碼就釋放鎖。
作用
當它用來修飾一個方法或一個代碼塊時,能夠保證在同一時刻最多只有一個線程執行該代碼。
使用
synchronized主要有兩種使用方法:synchronized方法和synchronized代碼塊。
- synchronized方法:
public synchronized void foo1() {
System.out.println("synchronized methoed");
}
注意:synchronized方法只能保證被修飾的方法為互斥訪問,而不能保證未被synchronized方法互斥訪問。
public class SynchronizedMethod {
//synchronized方法A
public synchronized void synchronizedMethodA() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
}
//synchronized方法B
public synchronized void synchronizedMethodB() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
}
//非synchronized方法
public void notSynchronizedMethod() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " enter in notSynchronizedMethod");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " get out notSynchronizedMethod");
}
}
public class SynchronizedTest {
public static void main(String[] args){
//設置兩個對象
SynchronizedMethod method = new SynchronizedMethod();
SynchronizedMethod anotherMethod = new SynchronizedMethod();
Thread t1 = new Thread(new MyRunnable(method,0), "thread1");
Thread t2 = new Thread(new MyRunnable(method,1), "thread2");
Thread t3 = new Thread(new MyRunnable(method,2), "thread3");
//使用另一個對象作為對比
Thread t4 = new Thread(new MyRunnable(anotherMethod,0), "thread4");
t1.start();
t2.start();
t3.start();
t4.start();
}
static class MyRunnable implements Runnable{
private SynchronizedMethod method;
private int idx;
public MyRunnable(SynchronizedMethod method, int idx){
this.method = method;
this.idx = idx;
}
@Override
public void run() {
try {
if (idx %3 == 0)
method.synchronizedMethodA();
else if (idx % 3 == 1)
method.notSynchronizedMethod();
else
method.synchronizedMethodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結果:
thread1 enter in synchronizedMethodA
thread2 enter in notSynchronizedMethod
thread4 enter in synchronizedMethodA
thread4 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 get out notSynchronizedMethod
thread3 enter in synchronizedMethodB
thread3 get out synchronizedMethodB
如上,根據結果,對比線程1和線程3可以知道synchronized方法保證了同一對象的互斥訪問。對比線程1和線程2可以知道synchronized方法只會保證synchronized修飾的互斥訪問。對比線程1和4可以知道,synchronized方法為對象鎖,不能保證不同對象的互斥訪問。
- synchronized代碼塊:
public void foo2() {
synchronized (this) {
System.out.println("synchronized methoed");
}
}
synchronized代碼塊中的this是指當前對象。也可以將this替換成其他對象,例如將list替換成obj,則foo2()在執行synchronized(obj)時獲取的就是obj的同步鎖。
public class InsertData {
private ArrayList<Integer> list1 = new ArrayList<>();
private ArrayList<Integer> list2 = new ArrayList<>();
public void insertData1(Thread t){
synchronized (this){
for (int i = 0; i < 5; i ++){
System.out.println(t.getName() + "在插入list1數據" + i);
list1.add(i);
}
}
}
public void insertData2(Thread t){
synchronized (this){
for (int i = 0; i < 5; i ++){
System.out.println(t.getName() + "在插入list2數據" + i);
list2.add(i);
}
}
}
}
public class SychronizedTest2 {
public static void main(String[] args){
InsertData insertData = new InsertData();
Thread t1 = new Thread(new MyRunnable(insertData,0),"thread1");
Thread t2 = new Thread(new MyRunnable(insertData,1),"thread2");
t1.start();
t2.start();
}
static class MyRunnable implements Runnable{
private InsertData insertData;
private int idx;
public MyRunnable(InsertData insertData, int idx){
this.insertData = insertData;
this.idx = idx;
}
@Override
public void run() {
if (idx % 2 == 0)
insertData.insertData1(Thread.currentThread());
else
insertData.insertData2(Thread.currentThread());
}
}
}
輸出結果:
thread2在插入list2數據0
thread2在插入list2數據1
thread2在插入list2數據2
thread2在插入list2數據3
thread2在插入list2數據4
thread1在插入list1數據0
thread1在插入list1數據1
thread1在插入list1數據2
thread1在插入list1數據3
thread1在插入list1數據4
根據結果,我們可以看到兩個線程時互斥訪問InsertData對象的。如果我們只是希望list1和list2分別被互斥訪問,而不是互斥訪問InsertData對象,那么可以修改如下:
public class InsertData {
private ArrayList<Integer> list1 = new ArrayList<>();
private ArrayList<Integer> list2 = new ArrayList<>();
public void insertData1(Thread t){
synchronized (list1){
for (int i = 0; i < 5; i ++){
System.out.println(t.getName() + "在插入list1數據" + i);
list1.add(i);
}
}
}
public void insertData2(Thread t){
synchronized (list2){
for (int i = 0; i < 5; i ++){
System.out.println(t.getName() + "在插入list2數據" + i);
list2.add(i);
}
}
}
}
輸出結果:
thread1在插入list1數據0
thread2在插入list2數據0
thread1在插入list1數據1
thread2在插入list2數據1
thread1在插入list1數據2
thread2在插入list2數據2
thread1在插入list1數據3
thread2在插入list2數據3
thread1在插入list1數據4
thread2在插入list2數據4
根據結果,我們可以看到兩個線程是并行的。
建議:盡量使用synchronized代碼塊,因為synchronized代碼塊可以更精確地控制沖突限制訪問區域,有時候表現地更高效。
public class SynchronizedTest3 {
public synchronized void synMethod(){
for (int i = 0; i < 1000000; i ++);
}
public void synBlock(){
synchronized (this){
for (int i = 0; i < 1000000; i ++);
}
}
public static void main(String[] args){
SynchronizedTest3 test3 = new SynchronizedTest3();
long start,end;
start = System.currentTimeMillis();
test3.synMethod();
end = System.currentTimeMillis();
System.out.println("synMethod() takes " + (end - start) + " ms");
start = System.currentTimeMillis();
test3.synBlock();
end = System.currentTimeMillis();
System.out.println("synBlock() takes " + (end - start) + " ms");
}
}
輸出結果:
synMethod() takes 3 ms
synBlock() takes 2 ms
實例鎖和全局鎖
- 實例鎖:鎖在某一個實例對象上。如果類是單例,那么該鎖也具有全局鎖的概念。其對應的是synchronized關鍵字。
- 全局鎖:該鎖針對的是類,無論實例多少個對象,線程都共享該鎖。全局鎖對應的是static synchronized。
我們先來驗證實例鎖:對于同一實例必須互斥訪問,而不同實例是可以并行。
public class SynchronizedMethod {
//synchronized方法A
public synchronized void synchronizedMethodA() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodA");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodA");
}
//static synchronized方法B
public static synchronized void synchronizedMethodB() throws InterruptedException {
System.out.println(Thread.currentThread().getName() + " enter in synchronizedMethodB");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " get out synchronizedMethodB");
}
}
public class SynchronizedTest {
public static void main(String[] args){
//設置兩個對象
SynchronizedMethod method = new SynchronizedMethod();
SynchronizedMethod anotherMethod = new SynchronizedMethod();
//驗證synchronized
Thread t1 = new Thread(new MyRunnable(method), "thread1");
Thread t2 = new Thread(new MyRunnable(method), "thread2");
Thread t3 = new Thread(new MyRunnable(anotherMethod), "thread3");
//驗證static synchronized
//Thread t4 = new Thread(new MyRunnable(method,1), "thread4");
//Thread t5 = new Thread(new MyRunnable(anotherMethod,1), "thread5");
t1.start();
t2.start();
t3.start();
//t4.start();
//t5.start();
}
static class MyRunnable implements Runnable{
private SynchronizedMethod method;
public MyRunnable(SynchronizedMethod method){
this.method = method;
}
@Override
public void run() {
try {
method.synchronizedMethodA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結果:
thread3 enter in synchronizedMethodA
thread1 enter in synchronizedMethodA
thread3 get out synchronizedMethodA
thread1 get out synchronizedMethodA
thread2 enter in synchronizedMethodA
thread2 get out synchronizedMethodA
我們可以看到線程1和3為不同實例,因此可以并行處理,而線程1和2是同一個實例,必須互斥訪問。
接下來,我們再來驗證全局鎖
public class SynchronizedTest {
public static void main(String[] args){
//設置兩個對象
SynchronizedMethod method = new SynchronizedMethod();
SynchronizedMethod anotherMethod = new SynchronizedMethod();
//驗證static synchronized
Thread t4 = new Thread(new MyRunnable(method), "thread4");
Thread t5 = new Thread(new MyRunnable(anotherMethod), "thread5");
t4.start();
t5.start();
}
static class MyRunnable implements Runnable{
private SynchronizedMethod method;
public MyRunnable(SynchronizedMethod method){
this.method = method;
}
@Override
public void run() {
try {
method.synchronizedMethodB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結果:
thread5 enter in synchronizedMethodB
thread5 get out synchronizedMethodB
thread4 enter in synchronizedMethodB
thread4 get out synchronizedMethodB
我們可以看到雖然線程4和5為不同實例,但兩者卻無法進行并行處理。
基本原則
- 當一個線程訪問“某對象”的synchronized方法“或者”synchronized代碼塊“時,其他線程對”該對象“的”synchronized方法“或者”synchronized代碼塊“將被阻塞。
- 當一個線程訪問”某對象“的”synchronized方法“或者”synchronized方法塊“時,其他線程可以訪問”該對象“的非同步代碼塊。
- 當一個線程訪問”某對象“的”synchronized方法“或者”synchronized方法塊“時,其他線程對”該對象“的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。