1.java中實現(xiàn)多線程的幾種方式
java中實現(xiàn)多線程的方式主要有兩種,第一種是繼承Thread類,第二種是實現(xiàn)Runnable接口。
class Thread1 extends Thread{
@Override
public void run{
System.out.println("thread1 do something");
}
}
class Thread2 implements Runnable{
@Override
public void run{
System.out.println("thread2 do something");
}
}
上面的代碼估計大家都非常熟悉,平時項目中主要使用實現(xiàn)Runnable接口的方式,這是因為java是單繼承特性,實現(xiàn)接口比繼承更加靈活,而且一個Runnable類可以被多個線程使用。這部分知識點比較簡單,就不做重點研究了。
2.synchronized關(guān)鍵字
1.synchronized對象鎖
public class Test{
public synchronized void method1(){}
public void method2(){
synchronized(this){
}
}
}
上面代碼中method1方法和method2方法都持有Test對象的鎖,這兩種寫法本質(zhì)上是一樣的。
2.synchronized線程間通信
public class Main{
public static void main(String[] args){
MyObj obj = new MyObj();
new ThreadA(obj).start();
new ThreadB(obj).start();
}
}
class MyObj{
public synchronized void methodA(){}
public synchronized void methodB(){}
}
class ThreadA extends Thread{
private MyObj obj;
public ThreadA(MyObj obj){
this.obj = obj;
}
@Override
public void run(){
obj.methodA();
}
}
class ThreadB extends Thread{
private MyObj obj;
public ThreadB(MyObj obj){
this.obj = obj;
}
@Override
public void run(){
obj.methodB();
}
}
上面這段代碼Myobj類有methodA和methodB這兩個同步方法,持有的都是MyObj的對象鎖。ThreadA和ThreadB都持有MyObj對象實例,是同一份內(nèi)存。執(zhí)行上面的代碼,你會發(fā)現(xiàn)只有當(dāng)ThreadA中執(zhí)行完了methodA方法后,ThreadB才能執(zhí)行methodB方法。這是因為,ThradA執(zhí)行methodA先獲得的MyObj對象鎖,獲得了訪問內(nèi)存的權(quán)限,當(dāng)ThreadA執(zhí)行完method方法后,釋放了MyObj的對象鎖,而ThreadB方法在執(zhí)行methodB方法時,MyObj對象鎖被ThreadA持有,所以只能先進入等待狀態(tài),ThreadA釋放MyObj對象鎖后,ThreadB獲得MyObj對象鎖,也獲得了這塊內(nèi)存的訪問權(quán)限。言而總之,誰獲得了對象鎖,誰就獲得了這塊內(nèi)存的訪問權(quán)限。這種多線程共享對象進行鎖的管理就是synchronized線程間通信。
3.synchronized/volatile
class Test{
private int a;
private volatile int b;
private int c;
public int getA(){ return a;}
public int getB(){ return b;}
public synchronized int getC(){ return c;}
}
上面這段代碼中,如果有多個線程去調(diào)用getA方法,那么所獲得到的a的值很有可能是不一樣的,這是因為獲得是線程空間中主內(nèi)存的copy,如果一個線程改變了a的值,并不會去通知其他線程a的值改變了。如果有多線程去調(diào)用getB方法,獲得到值是一樣的。這是由于b屬性被volatile修飾了,被volatile修飾的屬性相當(dāng)于告訴jvm,這個值是不確定的,需要從主內(nèi)存去獲取,所以b能保證一致性。而多線程調(diào)用getC方法時,會造成后面線程的堵塞等待,也能保證值得一致性。當(dāng)一個線程執(zhí)行g(shù)etC方法時,線程空間會從主內(nèi)存中copy數(shù)據(jù)到本地,然后執(zhí)行修改,完成操作后會將線程空間的數(shù)據(jù)與主內(nèi)存中的數(shù)據(jù)同步,然后釋放對象鎖,交由下個線程處理。
總結(jié)下synchronized和volatile的區(qū)別,第一,在多線程下,volatile不堵塞的,數(shù)據(jù)直接從主內(nèi)存中獲取,synchronized是堵塞的,線程處理完數(shù)據(jù)要與主內(nèi)存數(shù)據(jù)同步,并通知其他線程數(shù)據(jù)的改變。第二,volatile僅能使用在變量級別,synchronized則可以使用在變量,方法。
4.synchronized/lock
synchronized和lock的區(qū)別主要從兩個方面講,從用法上,synchronized可以加載類上,方法上,屬性上,要指定鎖住的對象。而lock要指定起始位置和終止位置。從性能上,synchronized是依賴于jvm,而lock依賴于我們的java代碼,比起synchronized屬于輕量級操作,所以在并發(fā)量比較小的情況下,使用synchronized是個不錯的選擇,但是在并發(fā)量比較高的情況下,其性能下降很嚴(yán)重,此時Lock是個不錯的方案。從本質(zhì)上講,synchronized采用的是cpu的悲觀鎖機制,線程獲得的是獨占鎖,其他線程要通過堵塞的方式等待;lock采用的是cpu的樂觀鎖機制,不加鎖,假設(shè)沒有沖突去執(zhí)行操作,如果失敗就重復(fù)試,值到成功為止
5.sleep/wait
兩者最主要的區(qū)別是sleep沒有釋放鎖,只是讓當(dāng)前線程進入睡眠狀態(tài),wait會交出鎖進入等待狀態(tài),別的線程可以工作
6.wait/notify
兩者必須在同步代碼塊中執(zhí)行。當(dāng)線程執(zhí)行wait()時,會把當(dāng)前的鎖釋放,然后讓出CPU,進入等待狀態(tài)。當(dāng)執(zhí)行notify方法時,會喚醒一個處于等待該 對象鎖 的線程,然后繼續(xù)往下執(zhí)行,直到執(zhí)行完退出對象鎖鎖住的區(qū)域(synchronized修飾的代碼塊)后再釋放鎖。