## **線程安全**
多個線程同時共享一個全局變量或靜態變量時,在進行寫入操作時可能會引發的數據沖突。
比如多窗口賣票,結果令人吃驚
~~~
public class ThreadDemo implements Runnable {
private int count= 10;
@Override
public void run() {
while (count> 0) {
if (count> 0) {
...
Thread.sleep(100);
...
System.out.println(Thread.currentThread().getName() + "賣了第" + (10 - count+ 1) + "張票");
count--;
}
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1號窗口");
Thread t2 = new Thread(t, "2號窗口");
t1.start();
t2.start();
}
}
~~~
## **內置鎖(synchronized)**
解決線程安全問題可以使用synchronized內置鎖
**方式一**:同步代碼塊
```
public class ThreadDemo implements Runnable {
private int count = 10;
private static Object obj = new Object();// 鎖的對象,可以是任意的對象
@Override
public void run() {
while (count > 0) {
synchronized (obj) {
if (count > 0) {
...
Thread.sleep(1000);
...
System.out.println(Thread.currentThread().getName() + "賣了第" + (10 - count + 1) + "張票");
count--;
}
}
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1號窗口");
Thread t2 = new Thread(t, "2號窗口");
t1.start();
t2.start();
}
}
```
**方式二**:同步函數
```
public class ThreadDemo implements Runnable {
private int count = 50;
@Override
public void run() {
while (count > 0) {
sale();
}
}
public synchronized void sale() {
if (count > 0) {
...
Thread.sleep(1000);
...
System.out.println(Thread.currentThread().getName() + "賣了第" + (50 - count + 1) + "張票");
count--;
}
}
public static void main(String[] args) {
ThreadDemo t = new ThreadDemo();
Thread t1 = new Thread(t, "1號窗口");
Thread t2 = new Thread(t, "2號窗口");
t1.start();
t2.start();
}
}
```
* 方式一鎖的對象是obj,也可以是其它任意的對象
* 方式二鎖的對象是當前類的字節碼
* 如果將方式一中鎖的對象換成this,那么他就和方式二中的鎖是同一對象
## **多線程的三大特性**
* 原子性
一個操作的執行過程不會被其它因數打斷,要么就不執行。原子性保證了線程數據一致性。
* 可見性
當多個線程訪問同一個變量時,一個線程修改了變量的值,其他的線程能立即看到。
jmm模型中,共享變量存在于**主內存**中,**本地私有內存**存放的是共享變量的副本,線程執行過程是先寫入本地內存, 再將本地內存中值刷新到主內存中,此時主內存會通知其它線程數據發生了改變,其它線程本地私有緩存就會失效。
* 有序性
程序執行的順序按照代碼的先后順序執行,一般情況下,處理器由于要提高執行效率,對代碼進行重排序,運行的順序可能和代碼先后順序不同,但是結果一樣。單線程下不會出現問題,多線程就會出現問題了。
```
int a = 2 //語句1
int r =4 //語句2
a = a + 1 //語句3
r = a*a //語句4
則因為重排序,他還可能執行順序為 2-1-3-4,1-3-2-4
但絕不可能 2-1-4-3,因為這打破了依賴關系。
```
## **volatile**
* 保障多線程可見性
volatile修飾的變量可使線程本地緩存中的值及時刷新到主內存中去,從而保障可見性
```
private volatile int count = 50;
```
volatile雖然能保證線程可見性,但是無法保證線程的原子性。
```
自增共享變量i++可拆分以下3步
1.從主內存取值;
2.執行+1;
3.值重新寫回主內存
```
假設A線程執行完步驟2,在多線程的情況下可能讓出時間片,其它線程將i的值賦成5寫入到主內存中去,線程A再繼續執行將新的值寫如主內存,此時就會出現線程安全問題。
* 禁止指令重排序優化
volatile修飾的變量,賦值后多執行了一個“load addl $0x0, (%esp)”操作,這個操作相當于一個內存屏障(指令重排序時不能把后面的指令重排序到內存屏障之前的位置)