水滴石穿--多執行緒原子操作、threadlocal、volatile、多執行緒下的單例模式
接著上一篇文章,下面看看幾個比較好理解的知識點!!
volatile
java關鍵字volatile修飾的變數從字面意義上理解易變的,不穩定的,事實上時告訴編譯器該變數是易變的不要對該變數使用快取等級的優化,每次都從記憶體地址中讀取值。
不過並沒有說明在對volatile修飾的變數進行修改後立即寫會記憶體地址,也就是說volatile只提供記憶體的可見性,而沒有提供原子性,所以在高併發下是不安全的。
volatile使用的場景:最好是那種只有一個執行緒修改變數,多個執行緒讀取變數的地方,對記憶體可見性高原子性低的地方。
volatile與枷鎖機制的區別:枷鎖機制確保了可見性和原子性,而volatile只有可見性。
舉一個例子看看:
/** * @classDesc: volatile特性的演示 * @author: hj * @date:2018年12月12日 下午1:54:12 */ public class RunThread extends Thread { private volatile boolean isrunning = true; // private boolean isrunning = true; public void setRunning(boolean running) { this.isrunning = running; } @Override public void run() { System.out.println("進入run方法.."); int i = 0; while (isrunning == true) { // .. } System.out.println("執行緒停止"); } public static void main(String[] args) { RunThread rt = new RunThread(); rt.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } rt.setRunning(false); System.out.println("isRunning的值已經被設定了false"); } }
volatile關鍵字不具備synchronized關鍵字的原子性(同步)
/** * volatile關鍵字不具備synchronized關鍵字的原子性(同步) * * @author hj * */ public class VolatileNoAtomic extends Thread { // private static volatile int count; private static AtomicInteger count = new AtomicInteger(0); private static void addCount() { for (int i = 0; i < 1000; i++) { // count++ ; count.incrementAndGet(); } System.out.println(count); } public void run() { addCount(); } public static void main(String[] args) { VolatileNoAtomic[] arr = new VolatileNoAtomic[100]; for (int i = 0; i < 10; i++) { arr[i] = new VolatileNoAtomic(); } for (int i = 0; i < 10; i++) { arr[i].start(); } } }
原子操作
atomic是不會阻塞執行緒的,基本型別有atomicinteger atomiclong atomicboolean.。
只要記住一點:多個addAndGet在一個方法內是非原子性的,需要加synchronized進行修飾,保證4個addAndGet整體原子性
/**
* @classDesc: Atomic原子操作的演示
* @author: hj
* @date:2018年12月12日 下午2:20:30
*/
public class AtomicUse {
private static AtomicInteger count = new AtomicInteger(0);
// 多個addAndGet在一個方法內是非原子性的,需要加synchronized進行修飾,保證4個addAndGet整體原子性
/** synchronized */
public synchronized int multiAdd() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count.addAndGet(1);
count.addAndGet(2);
count.addAndGet(3);
count.addAndGet(4); // +10
return count.get();
}
public static void main(String[] args) {
final AtomicUse au = new AtomicUse();
List<Thread> ts = new ArrayList<Thread>();
for (int i = 0; i < 100; i++) {
ts.add(new Thread(new Runnable() {
@Override
public void run() {
System.out.println(au.multiAdd());
}
}));
}
for (Thread t : ts) {
t.start();
}
}
}
threadlocal
threadlocal當前執行緒的副本,在使用Threadlocal維護變數時Threadlocal為該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立的改變自己的副本,而不改變其他執行緒的副本。在資源的連線池中使用的比較廣泛。
/**
* @classDesc: 資料與執行緒繫結ThreadLocal使用
* @author: hj
* @date:2018年12月12日 下午2:29:13
*/
public class ConnThreadLocal {
public static ThreadLocal<String> th = new ThreadLocal<String>();
public void setTh(String value) {
th.set(value);
}
public void getTh() {
System.out.println(Thread.currentThread().getName() + ":" + this.th.get());
}
public static void main(String[] args) throws InterruptedException {
final ConnThreadLocal ct = new ConnThreadLocal();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
ct.setTh("張三");
ct.getTh();
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
ct.getTh();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2");
t1.start();
t2.start();
}
}
ps:jdk底層實現threadlocal是通過map實現的,所以使用時注意執行緒的數量,不要超過了限制引起記憶體溢位。
多執行緒下的單例模式
單例模式本應該是在設計模式中講的,也是設計模式中最簡單的一種模式。在高併發下傳統櫃的設計模式就不行了,在這提供2中高併發設計下的單例模式。雙檢測和類中類(double check ;class in class)
/**
* @classDesc: 高併發下的單例模式,類中類
* @author: hj
* @date:2018年12月12日 下午2:41:41
*/
public class Singletion {
private static class InnerSingletion {
private static Singletion single = new Singletion();
}
public static Singletion getInstance() {
return InnerSingletion.single;
}
}
/**
* @classDesc: 高併發下的單例模式,雙檢測
* @author: hj
* @date:2018年12月12日 下午2:41:41
*/
public class DubbleSingleton {
private static DubbleSingleton ds;
public static DubbleSingleton getDs() {
if (ds == null) {
try {
// 模擬初始化物件的準備時間...
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DubbleSingleton.class) {
if (ds == null) {
ds = new DubbleSingleton();
}
}
}
return ds;
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(DubbleSingleton.getDs().hashCode());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(DubbleSingleton.getDs().hashCode());
}
}, "t2");
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(DubbleSingleton.getDs().hashCode());
}
}, "t3");
t1.start();
t2.start();
t3.start();
}
}
ps:在鈕釦軟體的筆試中考了,結果忘了這次就記住,如果在筆試中考到單例模式記得把這個寫上,能增色不少。。
面試題
1.原子操作的一定是執行緒安全的嗎??舉舉例子說明
2.threadlocal使用場景有哪些??使用時注意哪些??
3.volatile是執行緒安全的嗎?使用場景有哪些??對比加鎖機制回答。
4.寫出單例模式程式碼,說明在高併發下是安全的嗎?如何改進?