1、什么是“線程安全”與“非線程安全”?
“非線程安全”會在多個線程對同一對象總的實例變量進行並發訪問時發生,產生的后果是“臟讀”,也就是取到的數據其實是被更改過的。
“線程安全”是以獲得的實例變量的值是經過同步處理的,不會出現臟讀的現象。
2、非線程安全例子?怎么解決?
| 非線程安全 |
| package com.jvm.thread;
public class HasSelfPrivateNum { private int num = 0; public void add(String username){ try{ if(username.equals("a")){ num = 100; System.out.println("a set over!"); Thread.sleep(2000); }else{ num = 200; System.out.println("b set over!"); } System.out.println(username + " num = " + num); }catch(InterruptedException e){ e.printStackTrace(); } } } |
| package com.jvm.thread;
public class MyThreadA extends Thread { private HasSelfPrivateNum obj; public MyThreadA(HasSelfPrivateNum obj){ this.obj = obj; }
@Override public void run() { super.run(); obj.add("a"); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private HasSelfPrivateNum obj; public MyThreadB(HasSelfPrivateNum obj){ this.obj = obj; }
@Override public void run() { super.run(); obj.add("b"); } }
|
| package com.jvm.thread;
public class MyThread06 { public static void main(String[] args) { HasSelfPrivateNum obj = new HasSelfPrivateNum(); MyThreadA a = new MyThreadA(obj); a.start(); MyThreadB b = new MyThreadB(obj); b.start(); } }
|
| a set over! b set over! b num = 200 a num = 200 |
|
|
| 解決方法:在 add() 前加關鍵字synchronized |
| a set over! a num = 100 b set over! b num = 200 |
3、synchronized 關鍵字
當A線程調用anyObject 對象加入synchronized關鍵字的X方法時,A線程就獲得了X方法鎖,更准確地講,獲得的是對象的鎖,所以,其他線程必須等待A線程執行完畢才能調用X方法,但線程B可以隨意調用其他的非synchronized同步方法。
當A線程調用anyObject 對象加入synchronized關鍵字的X方法時,A線程就獲得了X方法所在的對象的鎖,所以其他線程必須等待A線程執行完畢才可以調用X方法,而B如果調用聲明了synchronized關鍵字的非X方法時,必須等待A線程將X方法執行完,也就是釋放對象鎖后才可以調用。
4、synchronized鎖重入
“可重入鎖”:自己可以再次獲得自己的內部鎖。可重入鎖也支持在父子繼承的環境中。
| package com.jvm.thread;
public class Service extends Thread { synchronized public void service1(){ System.out.println("service1"); service2(); } synchronized public void service2(){ System.out.println("service2"); service3(); } synchronized public void service3(){ System.out.println("service3"); }
@Override public void run() { super.run(); Service service = new Service(); service.service1(); }
public static void main(String[] args) { Service service = new Service(); service.start(); } }
|
| service1 service2 service3 |
|
|
5、鎖自動釋放——出現異常
當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放。
6、synchronized同步方法的缺點?解決辦法?
Synchronized同步方法在某些情況下是有弊端的,比如A線程調用同步方法執行一個長時間的任務,那么B線程則必須等待比較長時間。
在這樣的情況下可以使用synchronized同步語句塊來解決。
7、synchronized同步語句塊的使用?
當兩個並發線程訪問同一個對象object中的synchronized(this)同步代碼塊時,一段時間內只能有一個線程被執行,另外一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。
| package com.jvm.thread;
public class MyTask { public void taskMethod(){ try { synchronized (this) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end time=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
|
| package com.jvm.thread;
public class MyThreadA extends Thread { private MyTask task; public MyThreadA(MyTask task){ this.task = task; }
@Override public void run() { task.taskMethod(); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private MyTask task; public MyThreadB(MyTask task){ this.task = task; }
@Override public void run() { task.taskMethod(); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { MyTask task = new MyTask(); MyThreadA a = new MyThreadA(task); a.setName("a"); a.start(); MyThreadB b = new MyThreadB(task); b.setName("b"); b.start(); } }
|
| begin time=1498358631251 end time=1498358633252 begin time=1498358633252 end time=1498358635252 |
8、synchronized代碼塊間的同步性?
在使用不同synchronized(this)代碼塊時需要注意的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對同一個object中所有其他synchronized(this)同步代碼塊的訪問將被阻塞,這說明synchronized使用的“對象監視器”是一個。
9、將任意對象作為對象監視器?優點?
使用synchronized(this)格式來同步代碼塊,其實Java還支持對“任意對象”作為“對象監視器”來實現同步的功能。這個“任意對象”大多數是實例變量及方法的參數,使用格式為synchronized(非this對象)。
優點:如果在一個類中有很多個synchronized方法,這時雖然能實現同步,但是會受到阻塞,所以影響運行效率;但如果使用同步代碼塊鎖非this對象,則synchronized(非this)代碼塊中程序與同步方法是異步的,不與其他鎖this同步方法爭搶this鎖,則可大大提高效率。
| package com.jvm.thread;
public class Service extends Thread { private String username; private String password; private String anyString = new String();
public void setUsernamePassword(String username, String pwssword){ try { synchronized (anyString) { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in to synchronized block"); username = username; Thread.sleep(3000); pwssword = pwssword; System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave synchronized block"); } } catch (InterruptedException e) { e.printStackTrace(); } } } |
| package com.jvm.thread;
public class MyThreadA extends Thread { private Service service; public MyThreadA(Service service){ this.service = service; }
@Override public void run() { service.setUsernamePassword("a", "aa"); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private Service service; public MyThreadB(Service service){ this.service = service; }
@Override public void run() { service.setUsernamePassword("b", "bb"); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service = new Service(); MyThreadA a = new MyThreadA(service); a.setName("A"); a.start(); MyThreadB b = new MyThreadB(service); b.setName("B"); b.start(); } }
|
| Thread name:A at 1498744309904 go in to synchronized block Thread name:A at 1498744312906 leave synchronized block Thread name:B at 1498744312906 go in to synchronized block Thread name:B at 1498744315906 leave synchronized block
|
|
|
|
|
|
|
10、3個結論
“synchronized(非this對象x)”格式的寫法是將x對象本身作為“對象監視器”,這樣就可以得出以下3個結論:
1)當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果。
2)當其他線程執行x對象中synchronized同步方法時呈同步效果。
3)當其他線程執行x對象方法里面的synchronized(this)代碼塊時也呈同步效果。
原因:使用同一個“對象監視器”。
11、靜態同步synchronized方法與synchronized(class)代碼塊
關鍵字synchronized還可以應用在static靜態方法上,如果這樣寫,那是對當前的 *.java文件對應的Class類進行持鎖。
| package com.jvm.thread;
public class Service extends Thread { synchronized public static void printA() { try { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()"); Thread.sleep(3000); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()"); } catch (InterruptedException e) { e.printStackTrace(); } }
synchronized public static void printB() { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()"); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()"); } } |
| package com.jvm.thread;
public class MyThreadA extends Thread { private Service service; public MyThreadA(Service service){ this.service = service; }
@Override public void run() { service.printA(); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private Service service; public MyThreadB(Service service){ this.service = service; }
@Override public void run() { service.printB(); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service = new Service(); MyThreadA a = new MyThreadA(service); a.setName("A"); a.start(); MyThreadB b = new MyThreadB(service); b.setName("B"); b.start(); } }
|
| Thread name:A at 1498746369790 go in printA() Thread name:A at 1498746372791 leave printA() Thread name:B at 1498746372792 go in printB() Thread name:B at 1498746372792 leave printB()
|
| 分析:從運行結果來看,和synchronized加到非static方法上使用效果一樣。其實有本質上的不同,synchronized加到static靜態方法上是給Class類加鎖,而synchronized加到非static方法上是給對象加鎖。 |
| package com.jvm.thread;
public class Service extends Thread { synchronized public static void printA() { try { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()"); Thread.sleep(3000); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()"); } catch (InterruptedException e) { e.printStackTrace(); } }
synchronized public static void printB() { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()"); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()"); }
synchronized public void printC() { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printC()"); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printC()"); } } |
| package com.jvm.thread;
public class MyThreadC extends Thread { private Service service; public MyThreadC(Service service){ this.service = service; }
@Override public void run() { service.printC(); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service = new Service(); MyThreadA a = new MyThreadA(service); a.setName("A"); a.start(); MyThreadB b = new MyThreadB(service); b.setName("B"); b.start(); MyThreadC myThreadC = new MyThreadC(service); myThreadC.setName("C"); myThreadC.start(); } }
|
| Thread name:A at 1498746943314 go in printA() Thread name:C at 1498746943315 go in printC() Thread name:C at 1498746943315 leave printC() Thread name:A at 1498746946315 leave printA() Thread name:B at 1498746946315 go in printB() Thread name:B at 1498746946315 leave printB()
|
| 分析:異步的原因是持有不同的鎖,一個是對象鎖,另外一個是Class鎖,而Class鎖可以對類的所有對象實例起作用。 |
| package com.jvm.thread;
public class Service extends Thread { synchronized public static void printA() { try { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()"); Thread.sleep(3000); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()"); } catch (InterruptedException e) { e.printStackTrace(); } }
synchronized public static void printB() { System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()"); System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()"); } } |
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service1 = new Service(); Service service2 = new Service(); MyThreadA a = new MyThreadA(service1); a.setName("A"); a.start(); MyThreadB b = new MyThreadB(service2); b.setName("B"); b.start(); } }
|
| Thread name:A at 1498747202057 go in printA() Thread name:A at 1498747205057 leave printA() Thread name:B at 1498747205057 go in printB() Thread name:B at 1498747205057 leave printB()
|
同步代碼塊synchronized(class)代碼塊的作用和synchronized static方法的作用一樣。
12、數據類型String的常量池特性
| 在JVM中具有String常量池緩沖的功能 |
| package com.jvm.thread;
public class Test { public static void main(String[] args) { String a = "a"; String b = "a"; System.out.println(a == b); //true } }
|
將synchronized(string)同步塊與String聯合使用時,要注意常量池帶來的一些例外。
| package com.jvm.thread;
public class Service extends Thread { public static void print(String str) { try { synchronized (str) { while(true){ System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } } |
| package com.jvm.thread;
public class MyThreadA extends Thread { private Service service; public MyThreadA(Service service){ this.service = service; }
@Override public void run() { service.print("AA"); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private Service service; public MyThreadB(Service service){ this.service = service; }
@Override public void run() { service.print("AA"); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service = new Service(); MyThreadA a = new MyThreadA(service); a.setName("A"); a.start(); MyThreadB b = new MyThreadB(service); b.setName("B"); b.start(); } }
|
| A A A A A A A
|
| 分析:死循環,原因是兩個值都是AA兩個線程持有相同的鎖,所以造成線程B不能執行。這就是String常量池所帶來的問題。因此,在大多數情況下,同步synchronized代碼塊都不適用String作為鎖對象,而改用其他,比如 new Object()實例化一個Oject對象。 |
13、同步synchronized方法無限等待與解決
| package com.jvm.thread;
public class Service extends Thread { synchronized public void methodA() { System.out.println("methodA begin"); boolean isContinueRun = true; while (isContinueRun) {
} System.out.println("methodA end"); }
synchronized public void methodB() { System.out.println("methodB begin"); System.out.println("methodB end"); } } |
| package com.jvm.thread;
public class MyThreadA extends Thread { private Service service; public MyThreadA(Service service){ this.service = service; }
@Override public void run() { service.methodA(); } }
|
| package com.jvm.thread;
public class MyThreadB extends Thread { private Service service; public MyThreadB(Service service){ this.service = service; }
@Override public void run() { service.methodB(); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) { Service service = new Service(); MyThreadA a = new MyThreadA(service); a.start(); MyThreadB b = new MyThreadB(service); b.start(); } }
|
| methodA begin |
| 分析:線程A不釋放鎖,線程B永遠得不到運行的機會,鎖死了。 |
| 解決:同步代碼塊 |
| package com.jvm.thread;
public class Service extends Thread { Object object1 = new Object(); public void methodA() { synchronized (object1) { System.out.println("methodA begin"); boolean isContinueRun = true; while (isContinueRun) {
} System.out.println("methodA end"); } }
Object object2 = new Object(); public void methodB() { synchronized (object2) { System.out.println("methodB begin"); System.out.println("methodB end"); } } } |
| methodA begin methodB begin methodB end |
14、多線程的死鎖
Java線程死鎖是一個經典的多線程問題,因為不同的線程都在等待根本不可能釋放的鎖,從而導致所有的任務都無法繼續完成。在多線程技術中心,“死鎖”是必需避免的,因為這會造成線程的“假死”。
| 死鎖例子: |
| package com.jvm.thread;
public class DeadThread implements Runnable { public String username; public Object lock1 = new Object(); public Object lock2 = new Object();
public void setFlag(String username){ this.username = username; }
@Override public void run() { if(username.equals("a")){ synchronized (lock1) { try { System.out.println("username = " + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("lock1 -> lock2"); } } }
if(username.equals("b")){ synchronized (lock2) { try { System.out.println("username = " + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("lock2 -> lock1"); } } } }
public static void main(String[] args) throws InterruptedException { DeadThread deadThread = new DeadThread(); deadThread.setFlag("a"); Thread thread1 = new Thread(deadThread); thread1.start(); Thread.sleep(1000);
deadThread.setFlag("b"); Thread thread2 = new Thread(deadThread); thread2.start(); } }
|
| username = a username = b |
| 注意:死鎖的實現與嵌套不嵌套沒有關系。 |
1/ valotile 關鍵字的作用是什么?缺點是什么?
使變量在多個線程間可見。但valotile關鍵字最致命的缺點是不支持原子性。
| package com.jvm.thread;
public class PrintString { private boolean isContinuePrint = true;
public void setContinuePrint(boolean isContinuePrint) { this.isContinuePrint = isContinuePrint; }
public boolean isContinuePrint() { return isContinuePrint; }
public void printStringMethod(){ try { while (isContinuePrint) { System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName()); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }
public static void main(String[] args) { PrintString printString = new PrintString(); printString.printStringMethod(); System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName()); printString.setContinuePrint(false); } }
|
| run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main run printStringMethod threadName=main |
| 分析:main線程一直在處理while循環,沒辦法執行后面的代碼。 解決:使用多線程 |
| package com.jvm.thread;
public class PrintString implements Runnable { private boolean isContinuePrint = true;
public void setContinuePrint(boolean isContinuePrint) { this.isContinuePrint = isContinuePrint; }
public boolean isContinuePrint() { return isContinuePrint; }
public void printStringMethod(){ try { while (isContinuePrint) { System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName()); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }
@Override public void run() { printStringMethod(); }
public static void main(String[] args) { PrintString printString = new PrintString(); new Thread(printString).start(); System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName()); printString.setContinuePrint(false); }
}
|
| I will stop it! stopThread=main run printStringMethod threadName=Thread-0 |
|
| package com.jvm.thread;
public class PrintString implements Runnable { volatile private boolean isContinuePrint = true;
public void setContinuePrint(boolean isContinuePrint) { this.isContinuePrint = isContinuePrint; }
public boolean isContinuePrint() { return isContinuePrint; }
public void printStringMethod(){ try { while (isContinuePrint) { System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName()); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } }
@Override public void run() { printStringMethod(); }
public static void main(String[] args) { PrintString printString = new PrintString(); new Thread(printString).start(); System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName()); printString.setContinuePrint(false); }
}
|
| I will stop it! stopThread=main run printStringMethod threadName=Thread-0 |
| 使用volatile關鍵字,強制從公共內存中讀取變量的值。
|
|
|
2/ 線程安全包含哪些方面?
原子性和可見性。Java的同步機制都是圍繞這兩個方面來確保線程安全的。
3/關鍵字synchronized和valotile比較?
a/關鍵字valotile是線程同步的輕量級實現,因此valotile性能更好。Valotile只能修飾變量,synchronized修飾方法和代碼塊。
b/多線程訪問valotile不會發生阻塞,而synchronized會。
c/valotile能保證數據的可見性,但不能保證原子性;而synchronized可以保證原子性,也可以間接保證可見性,因為它會將私有內存和公共內存中的數據做同步。
d/valotile解決的是變量在多個線程之間的可見性,而synchronized解決的是多個線程之間訪問資源的同步性。
3/valotile非原子性?怎么解決?
| package com.jvm.thread;
public class MyThread extends Thread { volatile public static int count; private static void addCount(){ for(int i = 0; i < 100; i++){ count++; } System.out.println("count=" + count); }
@Override public void run() { addCount(); }
public static void main(String[] args) { MyThread[] myThreadArr = new MyThread[100]; for(int i = 0; i < 100; i++){ myThreadArr[i] = new MyThread(); } for(int i = 0; i < 100; i++){ myThreadArr[i].start(); } } }
|
| count=100 count=300 count=200 count=400 count=600 count=600 count=700 count=800 count=900 count=1000 count=1100 count=1200 count=1400 count=1300 count=1500 count=1600 count=1700 count=1800 count=2000 count=1900 count=2200 count=2300 count=2200 count=2400 count=2500 count=2600 count=2900 count=2800 count=2700 count=3000 count=3100 count=3200 count=3300 count=3400 count=3600 count=3700 count=3500 count=4100 count=4000 count=3900 count=4300 count=3800 count=4400 count=4600 count=4200 count=4500 count=4800 count=4700 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6605 count=6705 count=6805 count=7005 count=7005 count=7205 count=7305 count=7405 count=7105 count=7505 count=7605 count=7705 count=7805 count=7905 count=8005 count=8105 count=8205 count=8305 count=8405 count=8505 count=8605 count=8705 count=8805 count=8905 count=9005 count=9105 count=9205 count=9305 count=9405 count=9505 count=9605 count=9705 count=9805 count=9905 |
| 分析:用圖來演示使用關鍵字valotile時出現非線程安全的原因。 a/ read和load階段:從主內存復制變量到當前線程工作內存; b/ use和assign階段:執行代碼,改變共享變量值; c/ store和write階段:用工作內存數據刷新主存對應變量的值。 在多線程環境中,use和assign是多次出現的,但這一操作並不是原子性,也就是在read和assign之后,如果主內存count變量發生修改之后,線程工作內存中的值由於已經加載,不會產生對應的變化,導致私有內存和公共內存中的變量不同步,因此,計算出來的結果和預期不一樣,也就出現了非線程安全問題。
|
| 解決:valotile關鍵字解決的是變量讀取時的可見性問題,但無法保證原子性,因此,對於多個線程訪問同一實例變量還是需要加鎖同步。 |
| package com.jvm.thread;
public class MyThread extends Thread { public static int count; private synchronized static void addCount(){ for(int i = 0; i < 100; i++){ count++; } System.out.println("count=" + count); }
@Override public void run() { addCount(); }
public static void main(String[] args) { MyThread[] myThreadArr = new MyThread[100]; for(int i = 0; i < 100; i++){ myThreadArr[i] = new MyThread(); } for(int i = 0; i < 100; i++){ myThreadArr[i].start(); } } }
|
| count=100 count=200 count=300 count=400 count=500 count=600 count=700 count=800 count=900 count=1000 count=1100 count=1200 count=1300 count=1400 count=1500 count=1600 count=1700 count=1800 count=1900 count=2000 count=2100 count=2200 count=2300 count=2400 count=2500 count=2600 count=2700 count=2800 count=2900 count=3000 count=3100 count=3200 count=3300 count=3400 count=3500 count=3600 count=3700 count=3800 count=3900 count=4000 count=4100 count=4200 count=4300 count=4400 count=4500 count=4600 count=4700 count=4800 count=4900 count=5000 count=5100 count=5200 count=5300 count=5400 count=5500 count=5600 count=5700 count=5800 count=5900 count=6000 count=6100 count=6200 count=6300 count=6400 count=6500 count=6600 count=6700 count=6800 count=6900 count=7000 count=7100 count=7200 count=7300 count=7400 count=7500 count=7600 count=7700 count=7800 count=7900 count=8000 count=8100 count=8200 count=8300 count=8400 count=8500 count=8600 count=8700 count=8800 count=8900 count=9000 count=9100 count=9200 count=9300 count=9400 count=9500 count=9600 count=9700 count=9800 count=9900 count=10000
|
4/驗證synchronized具有將線程工作內存的私有變量與公共內存中的變量同步的功能?
關鍵字synchronized可以保證在同一時刻,只有一個線程可以執行某個方法或者代碼快。它包含兩個特性:互斥性和可見性。同步synchronized不僅可以解決一個線程看到對象處於不一致的狀態,還可以保證進入同步方法或者同步代碼塊的每個線程,都能看到由同一個鎖保護之前所有的修改效果。
| package com.jvm.thread;
public class Service { private boolean isCoutinueRun = true;
public void runMethod(){ while(isCoutinueRun){
} System.out.println("have stoped!"); }
public void stopMethod(){ isCoutinueRun = false; } }
|
| package com.jvm.thread;
public class ThreadA extends Thread{ private Service service;
public ThreadA(Service service) { this.service = service; }
@Override public void run() { service.runMethod(); } }
|
| package com.jvm.thread;
public class ThreadB extends Thread{ private Service service;
public ThreadB(Service service) { this.service = service; }
@Override public void run() { service.stopMethod(); } }
|
| package com.jvm.thread;
public class Run { public static void main(String[] args) throws InterruptedException { Service service = new Service(); ThreadA threadA = new ThreadA(service); threadA.start();
Thread.sleep(1000);
ThreadB threadB = new ThreadB(service); threadB.start(); System.out.println("have start stop commad"); } }
|
| have start stop commad
|
| 分析:出現死循環,各線程間的數據值沒有可視性造成的 |
| 解決:synchronized可以具有可視性 |
| package com.jvm.thread;
public class Service { private boolean isCoutinueRun = true;
public void runMethod(){ String anyString = new String(); while(isCoutinueRun){ synchronized (anyString) {
} } System.out.println("have stoped!"); }
public void stopMethod(){ isCoutinueRun = false; } }
|
5/總結?
着重“外練互斥,內修可見”,是掌握多線程並發的重要技術。


