目錄
三、ArrayList多線程異常:java.util.ConcurrentModificationException (同HashSet、HashMap問題)
四、公平鎖、非公平鎖、可重入鎖(遞歸鎖)、自旋鎖、讀/寫鎖(共享鎖/獨佔鎖)的理解
五、CountDownLatch、 CyclicBarrier、Semaphore的使用
一、volatile的理解
1.volatile是Java虛擬機提供的輕量級的同步機制
1.1保證可見性
1.2不保證原子性-反JMM:數據加載過快,返回主內存數據覆蓋,導致數據丟失
1.3禁止指令重排
2.JMM(Java內存模型)理解
2.1可見性:主內存線程--->分配--->工作內存1、2、3線程...--->返回--->主內存線程
2.2原子性
2.3有序性
3.在哪些地方用到volatile
3.1單例模式DCL(Double Check Lock)代碼:DCL雙重檢查加鎖-可能在instance引用對象時,由於指令重排,導致初始化完成前被其他線程引用,造成線程不安全問題。
4.示例:
4.1非volatile 、synchronized解決多線程原子性問題:
JUC:java.util.concurrent.atomic包下提供了原子性方法,如:
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.getAndIncrement();
4.2volatile單例模式DCL,禁止指令重排
private volatile static Instance ins = null;
public static Instance getInstance(){
if (ins == null){
synchronized (Instance.class){
if (ins == null){
ins = new Instance();
}
}
}
return ins;
}
二、CAS的理解
1.什麼是CAS
1.1compareAndSet--->比較並交換,期望相同即交換
2.CAS底層原理
2.1CAS方法
atomicInteger.compareAndSet(expect,update);
2.2調用Unsafe:Unsafe類的方法大部分爲native修飾(Java調用非Java代碼的接口),其直接調用內存偏移地址獲取數據。
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
3.CAS的缺點
3.1由於沒有加鎖,循環時間長,開銷大
3.2只能保證一個共享變量的原子性
3.3會導致ABA問題:
①多線程時間差引起線程1A---(線程2A-B-C-D...-A)---B期間,線程2多次修改數據,導致線程1認爲數據未變化。
②適合只管結果不管過程的業務。
4.示例
4.1時間戳原子引用解決ABA問題:AtomicStamped...
public static void main(String[] args) {
String val = "A";
AtomicStampedReference<String> satf = new AtomicStampedReference<String>(val, 1);
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "初始版本號:" + satf.getStamp() + " val:" + satf.getReference());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
satf.compareAndSet("A", "B", satf.getStamp(), satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "修改後版本號:" + satf.getStamp() + " val:" + satf.getReference());
satf.compareAndSet("B", "A", satf.getStamp(), satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "修改後版本號:" + satf.getStamp() + " val:" + satf.getReference());
}, "t1").start();
new Thread(() -> {
int init = satf.getStamp();
System.out.println(Thread.currentThread().getName() + "初始版本號:" + satf.getStamp());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flg = satf.compareAndSet("A", "B",init, satf.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "等待t1後版本號:" + satf.getStamp() + " cas狀態:" + flg + " val值:" + satf.getReference());
}, "t2").start();
}
三、ArrayList多線程異常:java.util.ConcurrentModificationException (同HashSet、HashMap問題)
1.1異常復現(犧牲Vector安全性,提高性能的結果)
List<String> list = new ArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }, String.valueOf(i)).start(); }
1.2解決方法
①簡單解決:Collections.synchronized...
List<String> list = Collections.synchronizedList(new ArrayList<>());
②提升解決:JUC工具類--->CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentHashMap
如add方法,使用ReentrantLock加鎖實現
List<String> list = new CopyOnWriteArrayList<>();
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
四、公平鎖、非公平鎖、可重入鎖(遞歸鎖)、自旋鎖、讀/寫鎖(共享鎖/獨佔鎖)的理解
1.公平鎖和非公平鎖
1.1公平鎖:排隊佔鎖。
1.2非公平鎖:先搶佔鎖,搶不到排隊佔鎖。
Synchronized非公平鎖,ReentrantLock默認非公平鎖,可選擇鎖方式:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.可重入鎖(遞歸鎖)
2.1同一線程進入外層鎖後,即不受限制進入內層鎖,以此避免死鎖的發生。
Synchronized、ReentrantLock爲可重入鎖。
public synchronized void syc1() {
syc2();
}
public synchronized void syc2() {
}
3.自旋鎖
3.1採用循環的方式去嘗試獲取鎖,以此防止出現阻塞,但是耗費CPU資源。
如:atomicInteger.getAndIncrement()--->unsafe.getAndAddInt(this, valueOffset, 1)就是自旋鎖;
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
4.讀/寫鎖(共享鎖/獨佔鎖)
4.1獨佔鎖:只被一個線程佔有;
4.2共享鎖:可被多個線程持有;
4.3解決Synchronized、ReentrantLock獨佔鎖的問題:
ReentrantReadWriteLock提供了讀寫分離的鎖機制,多線程操作時,寫操作就獨佔鎖,讀操作就共享鎖。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
try {
rwLock.writeLock().lock();
//寫操作
}finally {
rwLock.writeLock().unlock();
}
try {
rwLock.readLock().lock();
} finally {
//寫操作
rwLock.readLock().unlock();
}
五、CountDownLatch、 CyclicBarrier、Semaphore的使用
1.CountDownLatch
1.1定義計數器,讓調用await()的線程阻塞,直到計數減爲0,再喚醒。
1.2示例
注:如果計數不夠,會一直處於等待,耗費資源。
System.out.println("定義countDownLatch計數器");
int cdNum = 6;
CountDownLatch countDownLatch = new CountDownLatch(cdNum);
for (int i = 0; i < cdNum; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "分線程結束,並計數");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
System.out.println(Thread.currentThread().getName() + "等待分線程全部結束後繼續主線程");
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "主線程結束");
2.CyclicBarrier
2.1定義計數器,直到計數加爲num,纔會執行await()後的方法。
2.2示例
System.out.println("定義cyclicBarrier計數器");
int num = 6;
CyclicBarrier cyclicBarrier = new CyclicBarrier(num, () -> {
System.out.println("分線程執行完畢,交回主線程");
});
for (int i = 0; i < num; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "分線程執行中");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
3.Semaphore
3.1Semaphore用於多線程搶佔多個資源,及併發資源數的控制,Synchronized、ReentrantLock是多線程搶佔一個資源,也就是併發數爲1。
3.2示例
System.out.println("定義semaphore信號量,進行多線程搶佔資源");
int num = 3;
Semaphore semaphore = new Semaphore(num);
for (int i = 0; i < 9; i++) {
new Thread(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 線程佔用資源中");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 3秒後釋放資源");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
六、阻塞隊列的理解
隊列:先到先得。
棧:先進後出。
1.1阻塞隊列,即隊列滿時能出不能進,隊列空時能進不能出。
1.2常用方法:
方法類型 | 拋出異常 | 特殊值 | 阻塞 | 超時 |
出入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | tkae() | poll(time,unit) |
檢查 | element() | peek() | 不可用 | 不可用 |
1.3常用實現類:
SynchronousQueue:不存儲元素的阻塞隊列,只能put一個,take一個。
ArrayBlockingQueue |
LinkedBlockingQueue |
PriorityBlockingQueue |
SynchronousQueue |
1.4擴展示例:
public static void main(String[] args) throws Exception {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(5));
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t生產線程啓動");
try {
myResource.myProd();
} catch (Exception e) {
e.printStackTrace();
}
}, "Prod").start();
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "\t消費線程啓動");
try {
myResource.myConsumer();
} catch (Exception e) {
e.printStackTrace();
}
}, "Cons").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("時間到,停止活動");
myResource.stop();
}
static class MyResource {
/**
* 默認開啓 進行生產消費的交互
*/
private volatile boolean flag = true;
/**
* 默認值是0
*/
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue = null;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
System.out.println("接口實現方法\t" + blockingQueue.getClass().getName());
}
public void myProd() throws Exception {
String data = null;
boolean returnValue;
while (flag) {
data = atomicInteger.incrementAndGet() + "";
returnValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (returnValue) {
System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據 " + data + " 成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t 插入隊列數據 " + data + " 失敗");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName() + "\t 停止 表示 flag" + flag);
}
public void myConsumer() throws Exception {
String result = null;
while (flag) {
result = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (StringUtils.isBlank(result)) {
flag = false;
System.out.println(Thread.currentThread().getName() + "\t 超過2m沒有取到 消費退出");
return;
}
System.out.println(Thread.currentThread().getName() + "\t 消費隊列 " + result + " 成功");
}
}
public void stop() throws Exception {
flag = false;
}
}
七、Synchronized和Lock的區別
1.Synchronized是關鍵字,屬於JVM層面;Lock是JUC的具體類,是API層面的鎖。
2.Synchronized不需要手動釋放資源;Lock需要手動釋放,用lock和unlock配合使用,否則導致死鎖。
3.Synchronized是不能被中斷的;Lock是可以中斷的。
4.Synchronized是非公平鎖;Lock默認非公平鎖,設爲true即爲公平鎖。
5.Synchronized隨機喚醒鎖;Lock可以精準喚醒鎖。
Lock lock = new ReentrantLock(); Condition c1 = lock.newCondition(); c1.await(); c1.signal();
八、Callable接口的理解
1.創建線程的四種方式:
①直接繼承Thread。
②實現Runnable接口:無返回結果,不拋異常。
③實現Callalbe接口:有返回結果,拋異常。
④線程池Executor。
2.Callable接口可以獲取執行結果,並利用阻塞一直等待結果返回。
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> oft = new FutureTask<>(new MyThread());
new Thread(oft, "1").start();
int num1 = 100;
System.out.println(Thread.currentThread().getName() + "\t 獲取Callable線程結果,未完成則堵塞等待");
int num2 = oft.get();
System.out.println(Thread.currentThread().getName() + "\t 處理結果: \t" + (num1 + num2));
}
static class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t Callable進行處理中!");
TimeUnit.SECONDS.sleep(3);
return 100;
}
}
九、線程池Executor的理解
1.線程池優勢:通過線程的複用,管理線程併發數,最大化利用系統資源,提供響應速度。
2.常見實現方法:
Executors.newFixedThreadPool | 創建定長線程池,使用LinkedBlockingQueue阻塞隊列 |
Executors.newSingleThreadPool | 創建一個線程的線程池,使用LinkedBlockingQueue阻塞隊列 |
Executors.newCachedThreadPool | 創建可調節的線程池,使用SynchronousQueue阻塞隊列 |
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "\t 進入業務處理");
});
}
threadPool.shutdown();
}
3.線程池參數說明
--->public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
--->public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue ) { this(corePoolSize,------------------------>核心線程數 maximumPoolSize,--------------------->最大線程數 keepAliveTime, ---------------------->空閒線程存活時間,最大線程數減爲核心線程數 unit,-------------------------------->空閒線程存活時間d單位 workQueue,--------------------------->任務隊列,被提交但未被執行的任務 Executors.defaultThreadFactory(), --->線程工廠 defaultHandler);}-------------------->拒絕策略,最大線程數和任務隊列都滿的時候執行拒絕策略
4.拒絕策略說明
AbortPolicy | 默認策略,直接拋出RejectedExecution異常 |
CallerRunsPolicy | 將任務回退到調用者處理,從而降低新任務的流量 |
DiscardOldestPolicy | 將隊列中等待最久的任務拋棄,並提交當前任務 |
DiscardPolicy | 直接丟棄當前任務 |
5.生產實現策略
5.1線程池使用ThreadPoolExecutor的方式創建。
5.2最大線程5+阻塞隊列5<小於任務15,執行決絕策略。
5.3簡單配置:
①CPU密集型任務線程數=CPU核數+1;
② IO密集型任務線程數=CPU核數*2 或 CPU核數/(1-阻塞系統)
阻塞係數≈0.8 - 0.9
public static void main(String[] args) {
int cpuNum = Runtime.getRuntime().availableProcessors();
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
1L,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(5),
Executors.defaultThreadFactory()
// new ThreadPoolExecutor.AbortPolicy()
// new ThreadPoolExecutor.CallerRunsPolicy()
// new ThreadPoolExecutor.DiscardOldestPolicy()
// new ThreadPoolExecutor.DiscardPolicy()
);
for (int i = 0; i < 15; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 進入業務處理");
});
}
threadPool.shutdown();
十、死鎖的理解
1.死鎖即多個線程相互等待的現象,由系統資源不足,進程運行推進的順序不合適,資源分配不當導致。
2.示例:
public static void main(String[] args) {
String lock1 = "lock1";
String lock2 = "lock2";
new Thread(new DeadLock(lock1, lock2), "A").start();
new Thread(new DeadLock(lock2, lock1), "B").start();
}
static class DeadLock implements Runnable {
private String val1;
private String val2;
public DeadLock(String val1, String val2) {
this.val1 = val1;
this.val2 = val2;
}
@Override
public void run() {
synchronized (val1) {
System.out.println(Thread.currentThread().getName() + "\t 當前持有" + val1 + "\t 準備獲取" + val2);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (val2) {
System.out.println(Thread.currentThread().getName() + "\t 已獲取" + val2);
}
}
}
}
3.如何排查是否發生死鎖
① 查詢進程號
jps -l
② 得到Java stack information for the threads listed above,即死鎖發生的具體信息
stack 進程號