首先先看看下面的代碼:
public class checkSynchronized extends Thread{
static volatile int i = 0;
public static void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
increase();
}
}
public static void main(String args[]) throws InterruptedException {
Thread t1 = new checkSynchronized();
Thread t2 = new checkSynchronized();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("i : " + i);
}
}
執(zhí)行上述代碼估灿,你會發(fā)現(xiàn)大部分情況下i的最終值都會小于2000000的
為什么加了volatile關(guān)鍵詞的變量依舊會出現(xiàn)線程安全問題呢崇呵?
這是因?yàn)関olatile只保證可見性,不保證原子性
圖為兩條線程同時對i進(jìn)行寫入時馅袁,一個線程的結(jié)果會覆蓋另一線程的結(jié)果域慷,造成線程安全問題。
解決此問題就應(yīng)該在線程甲進(jìn)行寫入值時,線程乙不僅不能寫入犹褒、而且還不能讀取值兄纺,如果讀取值的話就會讀取到一個舊值化漆,依舊會造成線程安全問題疙赠。那該如何實(shí)現(xiàn)呢?
在這里就要引出今天的主角了:"Synchronized"
關(guān)鍵字synchronized的作用就是實(shí)現(xiàn)線程間的同步問題,它能將同步區(qū)的代碼進(jìn)行加鎖,一次只能允許一條線程進(jìn)入同步區(qū)银萍,以保證同步區(qū)中的線程安全問題。
下面就來測試下該關(guān)鍵字的作用:
public class checkSynchronized extends Thread{
Object lock;
checkSynchronized(Object lock) {
this.lock = lock;
}
static volatile int i = 0;
public static void increase() {
i++;
}
@Override
public void run() {
synchronized (lock) {
for (int j = 0; j < 1000000; j++) {
increase();
}
}
}
public static void main(String args[]) throws InterruptedException {
Object lock = new Object();
Thread t1 = new checkSynchronized(lock);
Thread t2 = new checkSynchronized(lock);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("i : " + i);
}
}
大家可以看到在run()方法里加了synchronized巧鸭,并且指定了鎖對象。
運(yùn)行結(jié)果:i : 2000000
在這里簡單地整理下synchronized的多種用法:
- 指定鎖對象崖咨,進(jìn)入前須先獲得給定對象的鎖(如上述代碼所述)
- 加在實(shí)例方法署拟,進(jìn)入前須獲得當(dāng)前實(shí)例的鎖
- 加在靜態(tài)方法,進(jìn)入前須獲得當(dāng)前類的鎖
在這里給出一段錯誤代碼蟹腾,大家需要明白:
public static void main(String args[]) throws InterruptedException {
Object lock1 = new Object(); //生成第一個鎖
Object lock2 = new Object(); //生成第二個鎖
Thread t1 = new checkSynchronized(lock1);
Thread t2 = new checkSynchronized(lock2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("i : " + i);
}
這段代碼表示出各自線程最終使用了各自的鎖,線程安全是無法保證的。
對于作用在實(shí)例方法上的也要注意,因?yàn)槠淇赡軙l(fā)生和上述相同原因的線程安全錯誤艺晴。
public class checkSynchronized implements Runnable{
static volatile int i = 0;
public synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
increase();
}
}
public static void main(String args[]) throws InterruptedException {
checkSynchronized onlyOne = new checkSynchronized();
Thread t1 = new Thread(onlyOne);
Thread t2 = new Thread(onlyOne);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("i : " + i);
}
}
上述代碼的synchronized作用在實(shí)例方法上,鎖為當(dāng)前實(shí)例掸屡。所以在main方法中只實(shí)例了一個checkSynchronized實(shí)例封寞,因?yàn)殒i只需要一個、多了就會出現(xiàn)安全問題仅财。
如果實(shí)例了兩個對象,則會出現(xiàn)安全問題(鎖多了和不加鎖就沒啥區(qū)別了)狈究,如下述代碼:
public static void main(String args[]) throws InterruptedException {
checkSynchronized onlyOne = new checkSynchronized();
checkSynchronized wrong = new checkSynchronized();
Thread t1 = new Thread(onlyOne);
Thread t2 = new Thread(wrong);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("i : " + i);
}
解決的辦法非常容易,只需在加了鎖的實(shí)例方法加上"static"關(guān)鍵字就行盏求。
public static synchronized void increase()
這樣鎖就作用在類方法上了抖锥。當(dāng)線程要執(zhí)行該同步方法時是請求當(dāng)前類的鎖并非實(shí)例的鎖,所以再多的實(shí)例線程之間依舊能正確同步碎罚。
synchronized不僅用于線程同步磅废、確保線程安全問題外,還能保證線程之間的可見性和有序性問題荆烈。相當(dāng)于是volatile的升級版拯勉。但被synchronized限制的多線程之間是串行執(zhí)行的竟趾,所帶來的性能消耗是很大的。