1.多線程三大特性?
原子性、可見性、有序性
(1)什麼是原子性?
即一個操作或者多個操作 要麼全部執行並且執行的過程不會被任何因素打斷,要麼就都不執行。
一個很經典的例子就是銀行賬戶轉賬問題:
比如從賬戶A向賬戶B轉1000元,那麼必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元。這2個操作必須要具備原子性才能保證不出現一些意外的問題。
我們操作數據也是如此,比如i = i+1;其中就包括,讀取i的值,計算i,寫入i。這行代碼在Java中是不具備原子性的,則多線程運行肯定會出問題,所以也需要我們使用同步和lock這些東西來確保這個特性了。
原子性其實就是保證數據一致、線程安全一部分.
(2) 什麼是可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值,其他線程能夠立即看得到修改的值。
若兩個線程在不同的cpu,那麼線程1改變了i的值還沒刷新到主存,線程2又使用了i,那麼這個i值肯定還是之前的,線程1對變量的修改線程沒看到這就是可見性問題。
這是產生線程不安全最主要的問題,假如我們有A,B兩個線程共享同一變量,變量放在方法區,也就是主內存,JVM體系結構中堆和方法區是線程共享的,A,B線程存放着主內存變量的副本,即各自對自己所在線程的主內存變量的副本讀寫,假如變量是10,A對變量進行減1,這時值爲9,但是沒來及刷新到主內存,B保存的是變量的副本10,B這時也進行減1,這時值也爲9,導致數據不一致。
具體原理可以參考:java內存模型
(3)什麼是有序性
程序執行的順序按照代碼的先後順序執行。
一般來說處理器爲了提高程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行先後順序同代碼中的順序一致,但是它會保證程序最終執行結果和代碼順序執行的結果是一致的。 如下:
int a = 10; //語句1
int r = 2; //語句2
a = a + 3; //語句3
r = a*a; //語句4
則因爲重排序,他還可能執行順序爲 2-1-3-4,1-3-2-4
但絕不可能 2-1-4-3,因爲這打破了依賴關係。
顯然重排序對單線程運行是不會有任何問題,而多線程就不一定了,所以我們在多線程編程時就得考慮這個問題了。
2.Synchronized關鍵字的兩種用法?
說明:Java提供了一種內置的鎖機制來支持原子性
每一個Java對象都可以用作一個實現同步的鎖,稱爲內置鎖,線程進入同步代碼塊之前自動獲取到鎖,代碼塊執行完成正常退出或代碼塊中拋出異常退出時會釋放掉鎖
內置鎖爲互斥鎖,即線程A獲取到鎖後,線程B阻塞直到線程A釋放鎖,線程B才能獲取到同一個鎖
內置鎖使用synchronized關鍵字實現,synchronized關鍵字有兩種用法:
1.修飾需要進行同步的方法(所有訪問狀態變量的方法都必須進行同步),此時充當鎖的對象爲調用同步方法的對象
2.同步代碼塊和直接使用synchronized修飾需要同步的方法是一樣的,但是鎖的粒度可以更細,並且充當鎖的對象不一定是this,也可以是其它對象,所以使用起來更加靈活
同步代碼塊synchronized
就是將可能會發生線程安全問題的代碼,給包括起來。
synchronized(同一個數據){
可能會發生線程衝突問題
}
就是同步代碼塊
synchronized(對象)//這個對象可以爲任意對象
{
需要被同步的代碼
}
if (flag) {
while (trainCount > 0) {
synchronized (oj) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
if (trainCount > 0) {
System.out
.println(Thread.currentThread().getName() + "," + "出售第" + (100 - trainCount + 1) + "票");
trainCount--;
}
}
}
}
synchronized修飾方法
1.在方法上修飾synchronized 稱爲同步方法,使用的是this鎖
2.方法上加上static關鍵字,使用synchronized 關鍵字修飾 稱爲靜態同步方法,使用鎖是當前類的字節碼文件,可以用 getClass方法獲取,也可以用當前 類名.class 表示。
證明同步方法使用的是this鎖,this指樣例中的SaleTickets對象
/**
* @author Administrator
*/
class SaleTickets implements Runnable {
private int trainTickets = 100;
private Object obj = new Object();
private boolean flag = true;
@Override
public void run() {
if (flag) {
while (trainTickets > 0) {
synchronized (obj) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (trainTickets > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "張票");
trainTickets--;
}
}
}
} else {
while (trainTickets > 0) {
sale();
}
}
}
public synchronized void sale() {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (trainTickets > 0) {
System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - trainTickets + 1) + "張票");
trainTickets--;
}
}
public static class Test {
public static void main(String[] args) throws Exception {
SaleTickets saleTickets = new SaleTickets();
Thread thread1 = new Thread(saleTickets, "1號窗口");
Thread thread2 = new Thread(saleTickets, "2號窗口");
thread1.start();
Thread.sleep(40);
saleTickets.flag = false;
thread2.start();
}
}
}
此時synchronized (obj),鎖爲任意對象;
此時synchronized (this),鎖爲this;
總結: 同步代碼塊爲obj鎖時發生線程不安全,爲this鎖時線程安全,證明同步方法爲this鎖
Volatile關鍵字用法?
可見性也就是說一旦某個線程修改了該被volatile修飾的變量,它會保證修改的值會立即被更新到主存,當有其他線程需要讀取時,可以立即獲取修改之後的值。
在Java中爲了加快程序的運行效率,對一些變量的操作通常是在該線程的寄存器或是CPU緩存上進行的,之後纔會同步到主存中,而加了volatile修飾符的變量則是直接讀寫主存。
Volatile 保證了線程間共享變量的及時可見性,但不能保證原子性