創建線程和synchronized關鍵字
很長時間沒有更新博客,恰好這段時間工作上需要使用線程,就稍微花點時間再次復習(學習)了一下線程知識。在此文中我將圍繞以下幾點對線程進行講解:
1.線程的創建(thread,runnable)
2.Synchronized關鍵字(生產者消費者)
一、線程創建
相信學習過java的同學都知道,創建線程無非是兩種方式:
(1)繼承Thread類;
(2)實現Runnable接口;
那這兩種方式都有什麽區別呢?就實現方式來看還是有差別的,繼承Thread類就需要去重寫Thread父類中的run方法,run方法就是執行的公共代碼塊;而實現Runnable則是對接口中run方法的具體實現,兩種方式的run方法的作用相同。下面一起分析一下代碼:
package com.cct.thread; class MyThread extends Thread{ private int ticket = 10; public void run(){ for(int i =0;i<500;i++){ if(this.ticket>0){ System.out.println(Thread.currentThread().getName()+"在賣票---->"+(this.ticket--)); } } } } (1)測試一 public static void main(String[] args) { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); MyThread mt3 = new MyThread(); //創建了三個對象 mt1.start(); mt2.start(); mt3.start(); } (2)測試二 public static void main(String[] args) { MyThread thr=new MyThread(); Thread a=new Thread(thr,"1號"); Thread b=new Thread(thr,"2號"); Thread c=new Thread(thr,"3號"); //一個對象用三個線程調用 a.start(); b.start(); c.start(); }
同學們應該可以猜到看出上面的兩種測試方式的結果差別,第一個main會給三個線程都分配10張票進行售賣,而第二種則是共用10張票進行售賣,在網上看到很多人描述繼承Thread和實現Runnable接口的差別是是否共同消耗同一份資源,我覺得這種說法是錯誤的,不管是哪種方式實現線程都是可以實現,這點要註意。他們的差別在於除了在類上寫的方式有點不一樣之外,我覺著比較大的差別是, java只支持但繼承,但是可以多實現,所以我認為實現runnable比繼承Thread擴展性更強。
二、synchronized關鍵字
這是使用線程最常用的關鍵字之一,它的作用是在同一時間點只有一個線程獲得鎖並進入執行代碼,其他線程如也需要獲得這把鎖,則須要在外等待,直到這個線程執行完。
一、當兩個並發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
五、以上規則對其它對象鎖同樣適用.
話不多說,先來代碼進行舉例:
一、當兩個並發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
結果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
A synchronized loop 3
A synchronized loop 4
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
B synchronized loop 3
B synchronized loop 4
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 5;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}
結果:
t1 : 4
t2 : 4
t1 : 3
t2 : 3
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
//修改Thread2.m4t2()方法: public void m4t2() { synchronized(this) { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } 結果: t1 : 4 t1 : 3 t1 : 2 t1 : 1 t1 : 0 t2 : 4 t2 : 3 t2 : 2 t2 : 1 t2 : 0
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
//修改Thread2.m4t2()方法如下: public synchronized void m4t2() { int i = 5; while( i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } 結果: t1 : 4 t1 : 3 t1 : 2 t1 : 1 t1 : 0 t2 : 4 t2 : 3 t2 : 2 t2 : 1 t2 : 0
五、以上規則對其它對象鎖同樣適用:
public class Thread3 { class Inner { private void m4t1() { int i = 5; while(i-- > 0) { System.out.println(Thread.currentThread().getName() + " : Inner.m4t1()=" + i); try { Thread.sleep(500); } catch(InterruptedException ie) { } } } private void m4t2() { int i = 5; while(i-- > 0) { System.out.println(Thread.currentThread().getName() + " : Inner.m4t2()=" + i); try { Thread.sleep(500); } catch(InterruptedException ie) { } } } } private void m4t1(Inner inner) { synchronized(inner) { //使用對象鎖 inner.m4t1(); } private void m4t2(Inner inner) { inner.m4t2(); } public static void main(String[] args) { final Thread3 myt3 = new Thread3(); final Inner inner = myt3.new Inner(); Thread t1 = new Thread( new Runnable() {public void run() { myt3.m4t1(inner);} }, "t1"); Thread t2 = new Thread( new Runnable() {public void run() { myt3.m4t2(inner);} }, "t2"); t1.start(); t2.start(); } }
關鍵字synchronizedde的鎖都是對象鎖,而不是扒一段代碼(方法)當做鎖,所以示例代碼中哪個線程先執行帶有synchronized關鍵字的方法,那個線程就執有該方法所述對象的鎖,不同對象獲得的就是不同對象的鎖,它們之間互不影響,有一種情況是相同的,那就是在靜態方法上加synchronized關鍵字,表示鎖定class類,類級別的鎖,可以叫類鎖,在使用的時候註意區分。
synchronized部分借鑒:http://www.wgblogs.com/ 在此表示感謝
創建線程和synchronized關鍵字