java多執行緒物件鎖、類鎖、同步機制詳解
1.在java多執行緒程式設計中物件鎖、類鎖、同步機制synchronized詳解:
物件鎖:在java中每個物件都有一個唯一的鎖,物件鎖用於物件例項方法或者一個物件例項上面的。
類鎖:是用於一個類靜態方法或者class物件的,一個類的例項物件可以有多個,但是隻有一個class物件。
同步機制synchronized:synchronized關鍵字用於修飾方法或者單獨的synchronized程式碼塊,當一個執行緒想執行synchronized中的內容時,必須先獲取到物件鎖,當物件鎖沒有執行緒佔用時,進入synchronized方法會自動獲取到物件鎖,執行完畢後會自動釋放鎖,
舉例說明包含synchronized的方法和synchronized程式碼塊:
package com.test; public class TestThread { public void test1() { synchronized (this) { int i = 0; while (i++<5) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public synchronized void test2() { int i = 0; while (i++<5) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestThread test = new TestThread(); Thread test1 = new Thread(new Runnable() { public void run() { test.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { test.test2(); } }, "test2"); test1.start(); test2.start(); } }
以上程式碼執行結果如下:
以上程式碼是因為執行同一個物件,所以另一個執行緒要等前一個物件,執行完成後釋放掉物件鎖,拿到物件鎖才能繼續執行synchronized裡面的東西。
如果我們去掉synchronized修飾的方法或者synchronized程式碼塊,將會打印出以下的結果:
如何要是去掉一個synchronized後,輸出的語句是交叉執行的。這就說明,對於同一個物件,如果執行緒A得到了物件鎖,執行緒B可以訪問物件沒有同步的方法和程式碼。進行同步的程式碼和沒有同步的程式碼是互不影響的。
舉例類鎖:
執行結果:package com.test; public class MyThreadClass { public static void main(String[] args) { final MyThreadClass my=new MyThreadClass(); Thread thread1=new Thread(new Runnable() { @Override public void run() { try { my.test1(); } catch (InterruptedException e) { e.printStackTrace(); } } },"test1"); Thread thread2=new Thread(new Runnable() { @Override public void run() { try { MyThreadClass.test2(); } catch (InterruptedException e) { e.printStackTrace(); } } },"test2"); thread1.start(); thread2.start(); } public void test1() throws InterruptedException{ synchronized (MyThreadClass.class) { int i=0; while(i++<5){ System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(500); } } } public static synchronized void test2() throws InterruptedException{ int i=0; while(i++<5){ System.out.println(Thread.currentThread().getName()+":"+i); Thread.sleep(500); } } }
類鎖和物件鎖其實是一樣的,只是針對於不同的物件。
如果兩個方法一個被synchronized修飾,一個靜態方法被synchronized修飾(體現類鎖和物件鎖的區別),程式碼如下:
package com.test;
public class MyThreadClass2 {
public static void main(String[] args) {
final MyThreadClass2 my=new MyThreadClass2();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
MyThreadClass2.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
public synchronized void test1() throws InterruptedException{
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
public static synchronized void test2() throws InterruptedException{
int i=0;
while(i++<5){
System.out.println(Thread.currentThread().getName()+":"+i);
Thread.sleep(500);
}
}
}
程式碼執行結果如下:
結果也是交叉輸出的,雖然都是被synchronized修飾,但是一個是屬於物件的,一個是屬於class。所以類鎖和物件鎖是不同的,他們控制著不同的區域,互不干擾。
疑問:java中既然synchronized修飾的方法和synchronized程式碼塊作用是一樣的,為什麼還需要synchronized程式碼塊呢?
使用synchronized修飾方式是直接在方法上面加鎖,synchronized方法塊是在方法裡面加鎖,一個範圍大,一個範圍小。還有一個最主要的在應用場景如下:在Class中建立一個物件,在Class中要執行這個物件的某個方法,為了防止多個執行緒同時執行,採用同步加鎖,但是如果這個方法出現死迴圈或者執行時間很長,其他執行緒也不能執行物件的其他同步方法,需要等待這個執行緒執行完畢,影響系統性能。如果採用synchronized修飾方法和synchronized程式碼塊可能都會出現這種情況,我們來模擬一下這種狀況:
建立一個java物件類,裡面有兩個方法:
package com.test;
public class Test {
public void test1(){
}
public void test2(){
}
}
建立測試類程式碼如下:
package com.test;
public class MyThreadClass4 {
static Test test=new Test();
public static void main(String[] args) {
final MyThreadClass4 my=new MyThreadClass4();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
long startTime;
public synchronized void test1() throws InterruptedException{
test.test1();
System.out.println(Thread.currentThread().getName());
startTime = System.currentTimeMillis();//獲取當前時間
Thread.sleep(5000);
}
public synchronized void test2() throws InterruptedException{
long endTime = System.currentTimeMillis();
test.test2();
System.out.println(Thread.currentThread().getName());
System.out.println("程式執行時間:"+(endTime-startTime)+"ms");
}
}
在class裡面建立物件例項,然後將兩個方法分別呼叫,在呼叫方法上面都加上synchronized修飾方法Thread.sleep(5000)來模擬方法執行時間過長或者死迴圈,但是可以看到時間為我們設定的時間,執行結果不理想執行結果如下:
如果我們採用synchronized程式碼塊的方式如下:
package com.test;
public class MyThreadClass3 {
static Test test=new Test();
public static void main(String[] args) {
final MyThreadClass3 my=new MyThreadClass3();
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test1");
Thread thread2=new Thread(new Runnable() {
@Override
public void run() {
try {
my.test2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"test2");
thread1.start();
thread2.start();
}
long startTime;
public void test1() throws InterruptedException{
synchronized (test) {
test.test1();
System.out.println(Thread.currentThread().getName());
startTime = System.currentTimeMillis();//獲取當前時間
Thread.sleep(5000);
}
}
public synchronized void test2() throws InterruptedException{
long endTime = System.currentTimeMillis();
test.test2();
System.out.println(Thread.currentThread().getName());
System.out.println("程式執行時間:"+(endTime-startTime)+"ms");
}
}
synchronized程式碼塊中我們只是對當前test物件加鎖,和執行這塊程式碼的物件沒有任何關係。執行test1的同時,我照樣可以執行其他的synchronized同步方法,增強系統性能。執行結果如下: