synchronized 和 volatile 使用
線程拋出異常,鎖會(huì)被釋放
如下demo
public class Demo1 {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + ": start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
int i = 1 / 0;
}
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
Runnable r = new Runnable() {
@Override
public void run() {
demo1.m();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
如果不想釋放那把線程鎖,則在1 / 0 地方加上try/catch。則本段程序不會(huì)執(zhí)行線程t2代碼,因?yàn)?code>t1一直沒(méi)有釋放。
線程之間的可見(jiàn)性
主線程將值改變,子線程由于cpu的緩沖區(qū)占滿,無(wú)法被讀取主內(nèi)存的變量。
public class Demo2 {
boolean running = true;
synchronized void m() {
System.out.println(": start");
while (running) {
}
System.out.println(": end");
}
public static void main(String[] args) {
Demo2 demo1 = new Demo2();
new Thread(demo1::m, "t1").start();
try{
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo1.running = false;
}
}
通過(guò)定義volatile關(guān)鍵字(線程之間通信的方式)來(lái),讓緩沖區(qū)讀取主內(nèi)存數(shù)據(jù)
可見(jiàn)性
一旦某個(gè)線程修改了volatile關(guān)鍵字修飾的變量,則改變量會(huì)立即保存修改后的值到物理內(nèi)存。其他線程讀取該值時(shí),也可以立即獲取修改后的值。
在java運(yùn)行時(shí),為了提高程序運(yùn)行效率,對(duì)于一些變量的操作通常在寄存器或者CPU緩存上進(jìn)行,之后才會(huì)保存到物理內(nèi)存中,而使用volatile關(guān)鍵字修飾的變量則是直接讀取物理內(nèi)存。
volatile最佳實(shí)踐
volatile由于不能保證原子性,用戶不需要依賴于上一個(gè)變量的場(chǎng)景
Atomic使用
AtomicInteger等類,是用來(lái)解決簡(jiǎn)單的類似 count++ 原子計(jì)算的問(wèn)題。他的效率比synchronized高的多。
public class Demo2 {
AtomicInteger count = new AtomicInteger(0);
void m() {
for (int i = 0; i < 100000; i++) {
count.incrementAndGet();
}
}
public static void main(String[] args) {
Demo2 demo2 = new Demo2();
List<Thread> threads = new ArrayList<>();
for (int j = 0; j < 10; j++) {
threads.add(new Thread(demo2::m, "theads" + j));
}
threads.forEach(o -> o.start());
threads.forEach(o -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(demo2.count.get());
}
}
但是如果AtomicXXX多個(gè)方法不構(gòu)成原子性比如
if(count.get() < 1000) {
count.incrementAndGet();
}
這時(shí)候在兩段代碼執(zhí)行中,可能被其他線程執(zhí)行
synchronized
- 不要以字符串常量作為鎖定對(duì)象
String s1="hello";
String s2="world";
synchronized(s1) {
}
鎖定的是字符串的對(duì)象,這時(shí)候鎖定S1以及S2都是鎖定的同一個(gè)對(duì)象,造成死鎖的情況。
wait/notify
- wait 會(huì)讓出鎖對(duì)象,notify不會(huì)釋放鎖的對(duì)象。
- wait 是鎖定當(dāng)前的對(duì)象,notify會(huì)隨機(jī)喚醒一個(gè)線程
CountDownLatch
每個(gè)線程執(zhí)行完一個(gè)任務(wù)“倒數(shù)”一次??偨Y(jié)來(lái)說(shuō),CountDownLatch的作用就是等待其他的線程都執(zhí)行完任務(wù),必要時(shí)可以對(duì)各個(gè)任務(wù)的執(zhí)行結(jié)果進(jìn)行匯總