Java多執行緒
- Java多執行緒
- 執行緒的創建
- 執行緒常用方法
- 執行緒的狀態
- 執行緒的優先級
- 守護執行緒
- 執行緒組
- 執行緒安全問題
- volatile關鍵字
- Java中的鎖
- synchronized鎖
- synchronized使用場景
- synchronized注意事項
- Lock類的使用
- Lock鎖使用的注意事項
- 公平鎖、非公平鎖
- synchronzied 和 Lock 的區別
- 死鎖
- 造成死鎖的四個條件
- 死鎖的解決方案
- 執行緒間通信
- wait/notify機制的原理
- notifyAll
- wait()和sleep()的區別
- LockSupport park()/unpark()
- Java執行緒池
- 執行緒池的優點
- 執行緒池的6種創建方式
- 執行緒池的第七種創建方式
- 五種拒絕策略
- ThreadPoolExecutor的執行方式
- ThreadPoolExecutor的執行流程
- 執行緒池的終止
- 執行緒池的狀態
- 異步、同步
- 執行緒工廠
- SimpleDateFormat非執行緒安全問題
- ThreadLocal
- ThreadLocal的原理
- ThreadLocal常用方法
- ThreadLocal的初始化
- InheritableThreadLocal的使用
- 單例模式與多執行緒
- 立即加載/餓漢模式
- 延時加載/懶漢模式
- 餓漢/懶漢對比
- 阻塞佇列的實作
- 常見的鎖策略
- 樂觀鎖
- CAS
- CAS在java中的應用
- CAS 的ABA問題
- ABA 問題的解決
- 悲觀鎖
- 獨占鎖、共享鎖、自旋鎖、可重入鎖
- 詳解synchronized鎖的優化問題
- Semaphore
- CountDownLatch\CyclicBarrier
- hashmap/ConcurrentHashMap
- hashmap在JDK1.7中頭插死回圈問題
- hashmap在JDK1.8中值覆寫問題
- ConcurrentHashMap & HashTable
Java多執行緒
執行緒的創建
1.繼承Thread
2.實作Runnable
3.實作Callable
使用繼承Thread類來開發多執行緒的應用程式在設計上是有局限性的,因為Java是單繼承,
繼承Thread類
public class ThreadDemo1 {
// 繼承Thread類 寫法1
static class MyThread extends Thread{
@Override
public void run() {
//要實作的業務代碼
}
}
// 寫法2
Thread thread = new Thread(){
@Override
public void run() {
//要實作的業務代碼
}
};
}
實作Runnable介面
//實作Runnable介面 寫法1
class MyRunnable implements Runnable{
@Override
public void run() {
//要實作的業務代碼
}
}
//實作Runnable介面 寫法2 匿名內部類
class MyRunnable2 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//要實作的業務代碼
}
});
}
}
實作Callable介面(Callable + FutureTask 創建帶有回傳值的執行緒)
package ThreadDeom;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* user:ypc;
* date:2021-06-11;
* time: 17:34;
*/
//創建有回傳值的執行緒 Callable + Future
public class ThreadDemo2 {
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 0;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//創建Callable子物件
MyCallable myCallable = new MyCallable();
//使用FutureTask 接受 Callable
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
//創建執行緒并設定任務
Thread thread = new Thread(futureTask);
//啟動執行緒
thread.start();
//得到執行緒的執行結果
int num = futureTask.get();
}
}
也可以使用lambda運算式
class ThreadDemo21{
//lambda運算式
Thread thread = new Thread(()-> {
//要實作的業務代碼
});
}
Thread的構造方法

執行緒常用方法
獲取當前執行緒的參考、執行緒的休眠
class Main{
public static void main(String[] args) throws InterruptedException {
Thread.sleep(1000);
//休眠1000毫秒之后列印
System.out.println(Thread.currentThread());
System.out.println(Thread.currentThread().getName());
}
}

package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 18:38;
*/
public class ThreadDemo6 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("執行緒的ID:" + Thread.currentThread().getId());
System.out.println("執行緒的名稱:" + Thread.currentThread().getName());
System.out.println("執行緒的狀態:" + Thread.currentThread().getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"執行緒一");
thread.start();
Thread.sleep(100);
//列印執行緒的狀態
System.out.println("執行緒的狀態:"+thread.getState());
System.out.println("執行緒的優先級:"+thread.getPriority());
System.out.println("執行緒是否存活:"+thread.isAlive());
System.out.println("執行緒是否是守護執行緒:"+thread.isDaemon());
System.out.println("執行緒是否被打斷:"+thread.isInterrupted());
}
}

執行緒的等待
假設有一個坑位,thread1 和 thread2 都要上廁所,一次只能一個人上,thread2只能等待thread1使用完才能使用廁所,就可以使用join()方法,等待執行緒1執行完,thread2在去執行,👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:48;
*/
public class ThreadDemo13 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"🚾");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"出來了");
}
};
Thread t1 = new Thread(runnable,"thread1");
t1.start();
//t1.join();
Thread t2 = new Thread(runnable,"thread2");
t2.start();
}
}

沒有join()顯然是不行的,加上join()之后:

執行緒的終止
1.自定義實作執行緒的終止
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:59;
*/
public class ThreadDemo11 {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!flag){
System.out.println("我是 : " + Thread.currentThread().getName() + ",我還沒有被interrupted呢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
}
},"thread");
thread.start();
Thread.sleep(300);
flag = true;
}
}

2.使用Thread的interrupted來中斷
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:59;
*/
public class ThreadDemo11 {
// private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()){
System.out.println("我是 : " + Thread.currentThread().getName() + ",我還沒有被interrupted呢");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// e.printStackTrace();
break;
}
}
System.out.println("我是 "+Thread.currentThread().getName()+",我被interrupted了");
}
},"thread");
thread.start();
Thread.sleep(300);
thread.interrupt();
// flag = true;
}
}

3.Thraed.interrupted()方法和Threaed.currentThread().interrupt()的區別
Thread.interrupted()方法第一次接收到終止的狀態后,之后會將狀態復位,Thread.interrupted()是靜態的,是全域的,Threaed.currentThread().interrupt()只是普通的方法,
Thraed.interrupted()方法
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:32;
*/
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
});
thread.start();
thread.interrupt();
}
}

Threaed.currentThread().interrupt()
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 10:32;
*/
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
for (int i = 0; i < 10; i++) {
// System.out.println(Thread.interrupted());
System.out.println(Thread.currentThread().isInterrupted());
}
});
thread.start();
thread.interrupt();
}
}

yield()方法
讓出CPU的執行權
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:47;
*/
public class ThreadDemo15 {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
Thread.yield();
System.out.println("thread1");
}
});
thread1.start();
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("thread2");
}
});
thread2.start();
}
}

執行緒的狀態

列印出執行緒的所有的狀態,所有的執行緒的狀態都在列舉中,👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:06;
*/
public class ThreadDemo14 {
public static void main(String[] args) {
for (Thread.State state: Thread.State.values()) {
System.out.println(state);
}
}
}

NEW 創建了執行緒但是還沒有開始作業
RUNNABLE 正在Java虛擬機中執行的執行緒
BLOCKED 受到阻塞并且正在等待某個監視器的鎖的時候所處的狀態
WAITTING 無限期的等待另一個執行緒執行某個特定操作的執行緒處于這個狀態
TIME_WAITTING 有具體等待時間的等待
TERMINATED 已經退出的執行緒處于這種狀態
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 11:06;
*/
class TestThreadDemo{
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(thread.getState());
thread.start();
System.out.println(thread.getState());
Thread.sleep(100);
System.out.println(thread.getState());
thread.join();
System.out.println(thread.getState());
}
}

執行緒的優先級
在Java中執行緒 的優先級分為1 ~ 10 一共十個等級
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 21:22;
*/
public class ThreadDemo9 {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1");
}
});
//最大優先級
t1.setPriority(10);
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2");
}
});
//最小優先級
t2.setPriority(1);
t2.start();
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t3");
}
});
t3.setPriority(1);
t3.start();
}
}
}

執行緒的優先級不是絕對的,只是給程式的建議,
執行緒之間的優先級具有繼承的特性,如果A執行緒啟動了B執行緒,那么B的執行緒的優先級與A是一樣的,👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 20:46;
*/
class ThreadA extends Thread{
@Override
public void run() {
System.out.println("ThreadA優先級是:" + this.getPriority());
ThreadB threadB = new ThreadB();
threadB.start();
}
}
class ThreadB extends ThreadA{
@Override
public void run() {
System.out.println("ThreadB的優先級是:" + this.getPriority());
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
System.out.println("main執行緒開始的優先級是:" + Thread.currentThread().getPriority());
System.out.println("main執行緒結束的優先級是:" + Thread.currentThread().getPriority());
ThreadA threadA = new ThreadA();
threadA.start();
}
}

再看👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 20:46;
*/
class ThreadA extends Thread{
@Override
public void run() {
System.out.println("ThreadA優先級是:" + this.getPriority());
ThreadB threadB = new ThreadB();
threadB.start();
}
}
class ThreadB extends ThreadA{
@Override
public void run() {
System.out.println("ThreadB的優先級是:" + this.getPriority());
}
}
public class ThreadDemo7 {
public static void main(String[] args) {
System.out.println("main執行緒開始的優先級是:" + Thread.currentThread().getPriority());
Thread.currentThread().setPriority(9);
System.out.println("main執行緒結束的優先級是:" + Thread.currentThread().getPriority());
ThreadA threadA = new ThreadA();
threadA.start();
}
}
結果為👇

守護執行緒
Java中有兩種執行緒:一種是用戶執行緒,一種就是守護執行緒,
什么是守護執行緒?守護執行緒是一種特殊的執行緒,當行程中不存在用戶執行緒的時候,守護執行緒就會自動銷毀,典型的守護執行緒就是垃圾回收執行緒,當行程中沒有了非守護執行緒,則垃圾回收執行緒也就沒有存在的必要了,
Daemon執行緒的作用就是為其他執行緒的運行提供便利的,👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 21:06;
*/
public class ThreadDemo8 {
static private int i = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
i++;
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//設定守護執行緒
thread.setDaemon(true);
thread.start();
Thread.sleep(5000);
System.out.println("我是守護執行緒thread 當用戶執行緒執行完成后 我也就銷毀了😭哭了");
}
}

注意:守護執行緒的設定必須放在start()之前,否則就會報錯,

在守護執行緒中創建的執行緒默認也是守護執行緒,
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:35;
*/
public class ThreadDemo10 {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
Thread thread2 = new Thread(() -> {
},"thread2");
System.out.println("thread2是守護執行緒嗎?:" + thread2.isDaemon());
},"thread1");
System.out.println("thread1是守護執行緒嗎?:" + thread1.isDaemon());
//thread1.setDaemon(true);
thread1.start();
// System.out.println("thread1是守護執行緒嗎?:" + thread1.isDaemon());
}
}

再看👇
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 9:35;
*/
public class ThreadDemo10 {
public static void main(String[] args) {
Thread thread1 = new Thread(()->{
Thread thread2 = new Thread(() -> {
},"thread2");
System.out.println("thread2是守護執行緒嗎?:" + thread2.isDaemon());
},"thread1");
System.out.println("thread1是守護執行緒嗎?:" + thread1.isDaemon());
thread1.setDaemon(true);
thread1.start();
System.out.println("thread1是守護執行緒嗎?:" + thread1.isDaemon());
}
}

執行緒組
為了便于對某些具有相同功能的執行緒進行管理,可以把這些執行緒歸屬到同一個執行緒組中,執行緒組中既可以有執行緒物件,也可以有執行緒組,組中也可以有執行緒,
使用執行緒模擬賽跑
public class ThreadDemo5 {
//執行緒模擬賽跑(未使用執行緒分組)
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到達了終點");
}
}, "選手一");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到達了終點");
}
}, "選手二");
t1.start();
t2.start();
System.out.println("所有選手到達了終點");
}
}
運行結果:

不符合預期效果,就可以使用執行緒組來實作
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-11;
* time: 18:24;
*/
class ThreadGroup1 {
//執行緒分組模擬賽跑
public static void main(String[] args) {
ThreadGroup threadGroup = new ThreadGroup("Group");
Thread t1 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("選手一到達了終點");
}
});
Thread t2 = new Thread(threadGroup, new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("選手二到達了終點");
}
});
t2.start();
t1.start();
while (threadGroup.activeCount() != 0) {
}
System.out.println("所有選手到達了終點");
}
}

執行緒組常用的方法

執行緒安全問題
來看單執行緒情況下讓count分別自增和自減10000次
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 12:03;
*/
class Counter {
private static int count = 0;
public void increase(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease(){
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount(){
return count;
}
}
public class ThreadDemo16 {
public static void main(String[] args) {
//單執行緒
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
}
}
結果符合預期

如果想使程式的執行速度快,就可以使用多執行緒的方式來執行,在來看多執行緒情況下的問題
public class ThreadDemo16 {
public static void main(String[] args) throws InterruptedException {
//多執行緒情況下
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
counter.decrease();
});
Thread thread2 = new Thread(()->{
counter.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.getCount());
/*
//單執行緒
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
*/
}
}
執行結果:



每次的執行結果是不一樣的,這就是多執行緒的不安全問題

預期的結果是0,但結果卻不是,
執行緒不安全問題的原因:
1.CPU的搶占式執行
2.多個執行緒共同操作一個變數
3.記憶體可見性
4.原子性問題
5.編譯器優化(指令重排)
多個執行緒操作同一個變數
如果多個執行緒操作的不是一個變數,就不會發生執行緒的不安全問題,可以將上面的代碼修改如下:👇
public class ThreadDemo16 {
static int res1 = 0;
static int res2 = 0;
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
res1 = counter.getCount();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
res2 = counter.getCount();
}
});
System.out.println(res1 + res2);
/*
//多執行緒情況下
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
counter.decrease();
});
Thread thread2 = new Thread(()->{
counter.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.getCount());
*/
/*
//單執行緒
Counter counter = new Counter();
counter.increase();
counter.decrease();
System.out.println(counter.getCount());
*/
}
}
這樣就可以了:

記憶體不可見問題:看下面的代碼,是不是到thread2執行的時候,就會改變num的值,從而終止了thread1呢?
package ThreadDeom;
import java.util.Scanner;
/**
* user:ypc;
* date:2021-06-12;
* time: 13:03;
*/
public class ThreadDemo17 {
private static int num = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
while (num == 0){}
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Scanner scanner = new Scanner(System.in);
System.out.println("輸入一個數字來終止執行緒thread1");
num = scanner.nextInt();
}
});
thread2.start();
}
}
結果是不能的:

輸入一個數字后回車,并沒有讓thread1的回圈結束,這就是記憶體不可見的問題,
原子性的問題
上面的++和–操作其實是分三步來執行的

假設在第二部的時候,有另外一個執行緒也來修改值,那么就會出現臟資料的問題了,
所以就會發生執行緒的不安全問題
編譯器優化
編譯器的優化會打亂原本程式的執行順序,就有可能導致執行緒的不安全問題發生,
在單執行緒不會發生執行緒的不安全問題,在多執行緒就可能會不安全,
volatile關鍵字
可以使用volatile關鍵字,這個關鍵字可以解決指令重排和記憶體不可見的問題,

加上volatile關鍵字之后的運行結果

但是volatile關鍵字不能解決原子性的問題👇:
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:02;
*/
class Counter1 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Counter1 counter1 = new Counter1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
counter1.decrease();
}
});
Thread thread2 = new Thread(() -> {
counter1.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter1.getCount());
}
}


那么Java中如何解決原子性的問題呢👇
Java中的鎖
Java中的加鎖操作有兩種:
1.synchronized鎖(jvm層的解決方案,也叫監視器鎖)
在作業系統的層面使用的是互斥鎖(mutex lock)
在Java中放在了物件頭中,
2.手動鎖Lock
操作鎖的流程
1.嘗試獲取鎖
2.使用鎖
3.釋放鎖
synchronized鎖
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:12;
*/
class Counter2 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo19 {
public static void main(String[] args) throws InterruptedException {
//宣告鎖物件,任何的物件都可以作為鎖
Object lock = new Object();
Counter2 counter2 = new Counter2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//使用鎖
synchronized (lock) {
counter2.decrease();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
counter2.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter2.getCount());
}
}
結果是:

synchronized使用場景
1.使用synchronized來修飾代碼塊(可以給任意的物件進行加鎖操作)
public class ThreadDemo19 {
public static void main(String[] args) throws InterruptedException {
//宣告鎖物件,任何的物件都可以作為鎖
Object lock = new Object();
Counter2 counter2 = new Counter2();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//使用鎖
synchronized (lock) {
counter2.decrease();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
counter2.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter2.getCount());
}
}

2.使用synchronized來修飾靜態方法(對當前的類進行加鎖的操作)
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:02;
*/
class Counter1 {
private static volatile int count = 0;
public void increase() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
public void decrease() {
for (int i = 0; i < 10000; i++) {
count--;
}
}
public int getCount() {
return count;
}
}
public class ThreadDemo18 {
public static void main(String[] args) throws InterruptedException {
Counter1 counter1 = new Counter1();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
counter1.decrease();
}
});
Thread thread2 = new Thread(() -> {
counter1.increase();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter1.getCount());
}
}

3.使用synchronized來修飾普通的方法(對當前類的實體來進行加鎖)
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 14:12;
*/
public class ThreadDemo20 {
private static int num = 0;
private static final int maxSize = 100000;
public static void main(String[] args) throws InterruptedException {
ThreadDemo20 threadDemo20 = new ThreadDemo20();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo20.increase();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadDemo20. decrease();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(num);
}
//給靜態的方法進行加鎖,被加的鎖是當前的物件,
// public synchronized static void increase(){
//給普通的方法進行加鎖的操作
public synchronized void increase() {
for (int i = 0; i < maxSize; i++) {
num++;
}
}
// public synchronized static void decrease(){
public synchronized void decrease() {
for (int i = 0; i < maxSize; i++) {
num--;
}
}
}

synchronized注意事項
1.加鎖的時候一定要使用同一把鎖物件
Lock類的使用
也叫手動鎖
package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 18:32;
*/
public class ThreadDemo22 {
private static int number = 0;
private static final int maxSize = 100000;
public static void main(String[] args) {
//創建lock鎖物件,lock是介面,不能實列化
Lock lock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try {
number++;
} finally {
lock.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < maxSize; i++) {
lock.lock();
try {
number--;
} finally {
lock.unlock();
}
}
});
System.out.println(number);
}
}

Lock鎖使用的注意事項
1.lock()操作一定要放在try外面
如果放在try的里面:
1.try中拋出了例外,還沒有加鎖就釋放了finally中的鎖的操作了
2.如果放在了try,沒加鎖就釋放了鎖,就會拋出例外,就會將業務代碼中的例外吞噬掉👇
如果一定要放的話,將lock()放在try的第一行,
package ThreadDeom;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 18:49;
*/
public class ThreadDemo23 {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
try{
System.out.println(1/0);
lock.lock();
} finally {
lock.unlock();
}
}
}

公平鎖、非公平鎖
公平鎖的調度:
一個執行緒釋放鎖,
主動喚醒“需要得到鎖”的佇列來得到鎖,
非公平鎖
當一個執行緒釋放鎖之后,另一個執行緒剛好執行到獲取鎖的代碼就可以直接獲取鎖,
Java中的所有鎖默認都是非公平鎖,
非公平鎖的性能更高,
ReentrantLock可以設定非公平鎖,
公平鎖
package ThreadDeom;
import java.util.concurrent.locks.ReentrantLock;
/**
* user:ypc;
* date:2021-06-12;
* time: 19:22;
*/
public class ThreadDemo24 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock reentrantLock = new ReentrantLock();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
reentrantLock.lock();
try {
System.out.println("thread1");
} finally {
reentrantLock.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
reentrantLock.lock();
try {
System.out.println("thread2");
} finally {
reentrantLock.unlock();
}
}
});
Thread.sleep(100);
thread1.start();
thread2.start();
}
}
列印的結果是無序的

如果設定為公平鎖:👇


thread1和thread2 交替輸出
synchronzied 和 Lock 的區別
1.synchronzied可以自動的進行加鎖和釋放鎖,而Lock需要手動的加鎖、釋放鎖,
2.Lock是Java層面的鎖實作,而synchronzied 是JVM層面鎖的實作
3.synchronzed 即可以修飾代碼塊,又可以修飾普通方法和靜態的方法,而Lock 只能修飾代碼塊
4. synchronized 實作的是 非公平的鎖,而Lock 可以實作公平鎖,
5.lock的靈活性更高
死鎖
在兩個或兩個以上的執行緒運行中,因為資源的搶占而造成執行緒一直等待的問題,
看👇:
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 19:48;
*/
public class ThreadDemo25 {
public static void main(String[] args) throws InterruptedException {
Object lockA = new Object();
Object lockB = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
//讓執行緒2獲取lockB
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
//執行緒2獲取資源B
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
//讓執行緒1先獲取到鎖lockA
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
}
}
}
});
thread1.start();
thread2.start();
}
}
這就造成了死鎖

造成死鎖的四個條件
1.互斥條件:
當資源被一個執行緒擁有之后,就不能被其它的執行緒擁有了
2.擁有請求條件:
當一個執行緒擁有了一個資源之后,又試圖請求另一個資源,
3.不可剝奪條件:
當一個執行緒擁有了一個資源之后,如果不是這個執行緒主動的釋放資源,其他執行緒就不能擁有這個執行緒,
4.環路等待條件:
兩個或兩個以上的執行緒擁有了資源之后,試圖獲取對方的資源的時候形成了一個環路,
死鎖的解決方案
解決請求擁有和環路等待,
最有效的解決方案就是控制加鎖的順序,
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 20:25;
*/
public class ThreadDemo26 {
public static void main(String[] args) throws InterruptedException {
Object lockA = new Object();
Object lockB = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
//讓執行緒2獲取lockB
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
}
}
}
});
thread1.start();
thread2.start();
}
}

執行緒間通信
執行緒之間的通訊是指在一個執行緒中的操作可以影響另一個執行緒,
wait/notify機制的原理
擁有相同鎖的執行緒之間才能使用wait/notify機制,
wait()是Object()的方法,它的作用是是當前執行wait()方法的執行緒等待,在wati()所在的代碼出停止執行,并釋放鎖,直到接到通知或者被中斷為止,即在呼叫wait()的方法之前,執行緒必需先獲取到物件級別的鎖,也就是只能在同步方法或者同步塊中使用wait()方法,
如果在使用wait()方法之前執行緒沒有獲得相應的鎖,那么程式在執行時就會拋出例外,
notify()方法要在同步方法或者同步塊中執行,即在呼叫notify()方法之前,執行緒必需要先獲取到鎖物件,如果執行緒沒有持有鎖物件的話,那么也會拋出例外,該方法用來通知可能在等待該鎖的其它執行緒,如果有多個執行緒,那么則按照執行wait()方法的順序來對處于wait()方法的執行緒發出通知,并使該執行緒重新獲取鎖,執行notify()方法之后,當前執行緒不會馬上釋放鎖,處于wait()狀態的執行緒也不會立馬得到這個物件鎖,而是要等notify的synchronized同步區域執行完成之后才會釋放鎖,處于wait()狀態的執行緒才會得到鎖物件,
總結:wait()方法用于讓執行緒停止運行,而notify()方法用于通知暫停的執行緒繼續運行,
在使用wait()或者notify()方法之前沒有物件鎖,就會報例外👇:
lock.notify();

正確的使用之后
package ThreadDeom;
/**
* user:ypc;
* date:2021-06-12;
* time: 21:11;
*/
public class ThreadDemo27 {
//設定鎖物件
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("被notify()喚醒之后");
}
}
});
thread.start();
Thread.sleep(1000);
synchronized (lock) {
lock.notify();
}
}
}

注意:使用wait()方法的時候一定要和執行緒的鎖物件是一個鎖,
notifyAll
在多執行緒的情況下使用notify()方法只可以喚醒一個執行緒👇

package ThreadDeom;
/**
* user:ypc;
* date:2021-06-13;
* time: 8:06;
*/
public class ThreadDemo28 {
//設定鎖物件
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("thread1在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1被notify()喚醒之后");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("thread2在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2被notify()喚醒之后");
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("thread3在wait()");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread3被notify()喚醒之后");
}
}
});
thread1.start();
thread2.start();
thread3.start();
Thread.sleep(1000);
synchronized (lock) {
System.out.println("主執行緒呼叫notify()之后");
lock.notify();
}
}
}
那么如果使用notifyAll()方法呢?

可以看到所有的執行緒都被喚醒了

那么使用notify()喚醒的執行緒有沒有什么順序呢?
使用notify()喚醒執行緒的順序是正序、倒序、還是隨機的,這取決與JVM的具體實作,并不是所有的JVM在執行notify()時都是按照wait()的執行順序進行喚醒的,也不是所有的notidyAll()都是按照wait()方法的倒序進行喚醒的,這取決于JVM的具體實作,
wait()和notify()不能喚醒指定的執行緒,
wait()和sleep()的區別
也可以讓wait()等待指定的時間,如果超過給定的時間,wait()不會無限期的等待下去.

沒有被notify()喚醒,過了1000毫秒之后會自動停止,

wait()在不傳入任何引數的時候,執行緒會進入waiting 的狀態,而在wait()中加入一個大于0的引數的時候,執行緒會進入time_wating的狀態,
sleep()和wait()的區別 : 執行緒在sleep()的時候是不會釋放鎖的,而執行wait()的時候它就會釋放鎖,👇:
package ThreadDeom;
import jdk.nashorn.internal.ir.Block;
/**
* user:ypc;
* date:2021-06-13;
* time: 8:45;
*/
public class ThreadDemo29 {
private static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("thread獲取到了鎖");
//如果sleep釋放鎖的話,會在thread獲取到了鎖和thread釋放了鎖之間列印
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread釋放了鎖");
}
});
thread.start();
//讓thread 先獲取到鎖
Thread.sleep(1000);
synchronized (lock) {
System.out.println("主執行緒獲取到了鎖");
}
}
}

可以看到執行緒在sleep()的時候,執行緒是不會釋放鎖的,再來看看wait()方法👇:


執行緒使用wait()的時候它就會釋放掉鎖,
1.wait()和sleep()都是讓執行緒進行休眠的
2.wait()和sleep()方法都有可能在執行的程序接收到執行緒終止的通知
3.wait()必須和synchronzied一起使用,而sleep()不用,
4.wait()會釋放鎖,而sleep()不會釋放鎖,
5.wait()時Object的方法,而sleep()時Thread的方法,
6.默認情況下,wait()不傳任何的引數的情況下,wait()會進入waiting的狀態,如果傳遞了引數,wait()會進入time_waiting的狀態,而sleep()進入的是time_waiting的狀態,
sleep(0) 和wait(0)的區別:
1.sleep(0)表示0毫秒之后繼續執行,而wait(0)表示執行緒會一直休眠下去wait(0)和wait()是一樣的,wait()的原始碼就是呼叫了wait(0)方法,
2.sleep(0)表示重新出發一次CPU的競爭,
為什么wait()會釋放鎖,而sleep()不會釋放鎖?
sleep()需要傳遞一個最大的等待時間,也就是說sleep()是可控的,而wait()是不可以傳遞引數的,從設計的層面來說,如果讓wait()一直持有所得話,那么執行緒就可能一直阻塞,
為什么wait()是Object的方法,而sleep()是執行緒的方法?
wait()需要操作鎖,而鎖是屬于物件級別的,所有的鎖都是放在物件頭中的,它不是執行緒級別的,一個執行緒可以有多把的鎖,為了靈活,就將wait()放在Object中了,
LockSupport park()/unpark()
使用LockSupport可以解決wait()/notify()隨機喚醒的問題,
package ThreadDeom;
import java.util.concurrent.locks.LockSupport;
/**
* user:ypc;
* date:2021-06-13;
* time: 9:36;
*/
public class ThreadDemo30 {
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//讓執行緒休眠
LockSupport.park();
System.out.println("unPark()了thread1");
}
});
Thread thread2 = new Thread(() -> {
LockSupport.park();
System.out.println("unPark()了thread2");
});
Thread thread3 = new Thread() {
@Override
public void run() {
LockSupport.park();
System.out.println("unPark()了thread3");
}
};
thread1.start();
thread2.start();
thread3.start();
LockSupport.unpark(thread1);
LockSupport.unpark(thread2);
}
}

Java執行緒池
執行緒的缺點:
1.執行緒的創建它會開辟本地方法堆疊、JVM堆疊、程式計數器私有的記憶體,同時消耗的時候需要銷毀以上三個區域,因此頻繁的創建和銷毀執行緒比較消耗系統的資源,
2.在任務量遠遠大于執行緒可以處理的任務量的時候,不能很好的拒絕任務,
所以就有了執行緒池:
使用池化的而技術來管理和使用執行緒,
執行緒池的優點
1.可以避免頻繁的創建和銷毀執行緒
2.可以更好的管理執行緒的個數和資源的個數,
3.執行緒池擁有更多的功能,比如執行緒池可以進行定時任務的執行,
4.執行緒池可以更友好的拒絕不能處理的任務,
執行緒池的6種創建方式
一共有7種創建方式
創建方式一:
創建固定個數的執行緒池:
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 10:24;
*/
public class ThreadPoolDemo1 {
public static void main(String[] args) {
//創建一個固定個數的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//執行任務
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒名" + Thread.currentThread().getName());
}
});
}
}
}

那么如果執行次數大于10次呢?
執行緒池不會創建新的執行緒,它會復用之前的執行緒,


那么如果只執行兩個任務呢?它創建了是10個執行緒還是兩個執行緒呢?
我們可以使用Jconsole來看一看:


結果是只有2個執行緒被創建,
創建方式二:
創建帶有快取的執行緒池:
適用于短期有大量的任務的時候使用
public class ThreadPoolDemo2 {
public static void main(String[] args) {
//創建帶快取的執行緒池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 100; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}

方式三:
創建執行定時任務的執行緒池
package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 11:32;
*/
public class ThreadPoolDemo3 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
System.out.println("執行定時任務前的時間:" + new Date());
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("執行任務的時間:" + new Date());
}
},1,2, TimeUnit.SECONDS);
}
}

執行任務的四個引數的意義:
引數1:延遲執行的任務
引數2:延遲一段時間后執行
引數3:定時任務執行的頻率
引數4:配合前兩個引數使用,是2、3引數的時間單位
還有兩種執行的方法:
只會執行一次的方法:


第三種的執行方式:


那么這種的執行方式和第一種的執行方式有什么區別呢?
當在兩種執行的方式中分別加上sleep()之后:

方式一:

方式三:

結論很明顯了:
第一種方式是以上一個任務的開始時間+定時的時間作為當前任務的開始時間
第三種方式是以上一個任務的結束時間來作為當前任務的開始時間,
創建方式四:
package ThreadPoolDemo;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 12:38;
*/
public class ThreadPoolDemo4 {
public static void main(String[] args) {
//創建單個執行任務的執行緒池
ScheduledExecutorService scheduledExecutorService
= Executors.newSingleThreadScheduledExecutor();
System.out.println("執行任務之前" + new Date());
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println("我是SingleThreadSchedule"+ new Date());
}
},3,1, TimeUnit.SECONDS);
}
}


創建方式五:
創建單個執行緒的執行緒池
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 12:55;
*/
public class ThreadPoolDemo5 {
public static void main(String[] args) {
//創建單個執行緒的執行緒池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 20; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒名 " + Thread.currentThread().getName());
}
});
}
}
}

創建單個執行緒池的作用是什么?
1.可以避免頻繁創建和銷毀執行緒所帶來的性能的開銷
2.它有任務佇列,可以存盤多余的任務
3.可以更好的管理任務
4.當有大量的任務不能處理的時候,可以友好的執行拒絕策略
創建方式六:
創建異步執行緒池根據當前CPU來創建對應個數的執行緒池
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* user:ypc;
* date:2021-06-13;
* time: 13:12;
*/
public class ThreadPoolDemo6 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newWorkStealingPool();
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("執行緒名" + Thread.currentThread().getName());
}
});
}
}
}

運行結果為什么什么都沒有呢?
看下面的異步與同步的區別就知道了,
加上這個

就可以輸出結果了

執行緒池的第七種創建方式
前六種的創建方式有什么問題呢?
1.執行緒的數量不可控(比如帶快取的執行緒池)
2.作業任務量不可控(默認的任務佇列的大小時Integer.MAX_VALUE),任務比較大肯會導致記憶體的溢位,
所以就可以使用下面的創建執行緒池的方式了:
package ThreadPoolDemo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 15:05;
*/
public class ThreadPoolDemo7 {
private static int threadId = 0;
public static void main(String[] args) {
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setName("我是threadPool-" + ++threadId);
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 3, 100,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(12),
threadFactory, new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 15; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
}
}

引數說明:

引數一:核心執行緒數|執行緒池正常情況下的執行緒 數量
引數二:最大執行緒數|當有大量的任務的時候可以創建的最多的執行緒數
引數三:最大執行緒的存活時間
引數四:配合引數三一起使用的表示引數三的時間單位
引數五:任務佇列
引數六:執行緒工廠
引數七:決絕策略
注意事項:最大的執行緒數要大于等于核心的執行緒數


五種拒絕策略


為什么拒絕策略可以舍棄最新的任務或者最舊的任務呢?
因為LinkedBlockingDeque時FIFO的,
第五種:自定義的拒絕策略


ThreadPoolExecutor的執行方式

package ThreadPoolDemo;
import java.util.concurrent.*;
/**
* user:ypc;
* date:2021-06-13;
* time: 16:58;
*/
public class ThreadPoolDemo9 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3, 4, 100,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10), new ThreadPoolExecutor.DiscardOldestPolicy());
//執行緒池的執行方式一
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("使用了execute()執行了執行緒池");
}
});
//執行緒池的執行方式二
Future<String> futureTask =
threadPoolExecutor.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "使用submit(new Callable<>())執行了執行緒池";
}
});
System.out.println(futureTask.get());
}
}
無回傳值的執行方式

有回傳值的執行方式

ThreadPoolExecutor的執行流程
當任務量小于核心執行緒數的時候,ThreadPoolExecutor會創建執行緒來執行任務
當任務量大于核心的執行緒數的時候,并且沒有空閑的執行緒時候,且當執行緒池的執行緒數小于最大執行緒數的時候,此時會將任務存放到任務佇列中
如果任務佇列也被存滿了,且最大執行緒數大于執行緒池的執行緒數的時候,會創建新的執行緒來執行任務,
如果執行緒池的執行緒數等于最大的執行緒數,并且任務佇列也已經滿了,就會執行拒絕策略,👇

執行緒池的終止
shutdown()
執行緒池的任務會執行完
shutdownNow()
立即終止執行緒池,執行緒池的任務不會執行完
執行緒池的狀態

異步、同步
- Java 執行緒 同步與異步
多執行緒并發時,多個執行緒同時請求同一個資源,必然導致此資源的資料不安全,A執行緒修改了B執行緒的處理的資料,而B執行緒又修改了A執行緒處理的數理,顯然這是由于全域資源造成的,有時為了解決此問題,優先考慮使用區域變數,退而求其次使用同步代碼塊,出于這樣的安全考慮就必須犧牲系統處理性能,加在多執行緒并發時資源掙奪最激烈的地方,這就實作了執行緒的同步機制
同步
A執行緒要請求某個資源,但是此資源正在被B執行緒使用中,因為同步機制存在,A執行緒請求不到,怎么辦,A執行緒只能等待下去
異步
A執行緒要請求某個資源,但是此資源正在被B執行緒使用中,因為沒有同步機制存在,A執行緒仍然請求的到,A執行緒無需等待
同步的方式:
1.發送請求
2.等待執行完成
3.有結果的回傳
異步的方式
1.發請求
2.執行完成
3.另一個執行緒異步處理
4.處理完成之后回傳回呼結果
顯然,同步最最安全,最保險的,而異步不安全,容易導致死鎖,這樣一個執行緒死掉就會導致整個行程崩潰,使用異步的機制,性能會有所提升
執行緒工廠
設想這樣一種場景,我們需要一個執行緒池,并且對于執行緒池中的執行緒物件,賦予統一的執行緒優先級、統一的名稱、甚至進行統一的業務處理或和業務方面的初始化作業,這時工廠方法就是最好用的方法了
package ThreadPoolDemo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* user:ypc;
* date:2021-06-13;
* time: 11:12;
*/
public class ThreadFactoryDemo {
public static void main(String[] args) {
MyThreadFactory myThreadFactory = new MyThreadFactory();
ExecutorService executorService = Executors.newFixedThreadPool(10,myThreadFactory);
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("使用執行緒工廠設定的執行緒名:"+ Thread.currentThread().getName() +
" 使用執行緒工廠設定的執行緒的優先級" + Thread.currentThread().getPriority());
}
});
}
}
private static int count = 0;
static class MyThreadFactory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setPriority(8);
thread.setName("thread--" + count++);
return thread;
}
}
}

SimpleDateFormat非執行緒安全問題
實作1000個執行緒的時間格式化
package SimpleDateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-13;
* time: 17:30;
*/
public class SimpleDateFormat1 {
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10,10,100,
TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(1000),new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 1001; i++) {
int finalI = i;
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
Date date = new Date(finalI * 1000);
myFormatTime(date);
}
});
}
threadPoolExecutor.shutdown();
}
private static void myFormatTime(Date date){
System.out.println(simpleDateFormat.format(date));
}
}
產生了執行緒不安全的問題👇:

這是因為:

多執行緒的情況下:

執行緒1在時間片用完之后,執行緒2來setTime()那么執行緒1的得到了執行緒2的時間,
所以可以使用加鎖的操作:

就不會有重復的時間了

但是雖然可以解決執行緒不安全的問題,但是排隊等待鎖,性能就會變得低
所以可以使用區域變數:

也解決了執行緒不安全的問題:

但是每次也都會創建新的私有變數
那么有沒有一種方案既可以避免加鎖排隊執行,又不會每次創建任務的時候不會創建私有的變數呢?
那就是ThreadLocal👇:
ThreadLocal
ThreadLocal的作用就是讓每一個執行緒都擁有自己的變數,
那么選擇鎖還是ThreadLocal?
看創建實列物件的復用率,如果復用率比較高的話,就使用ThreadLocal,
ThreadLocal的原理
類ThreadLocal的主要作用就是將資料放到當前物件的Map中,這個Map時thread類的實列變數,類ThreadLocal自己不管理、不存盤任何的資料,它只是資料和Map之間的橋梁,
執行的流程:資料—>ThreadLocal—>currentThread()—>Map,
執行后每個Map存有自己的資料,Map中的key中存盤的就是ThreadLocal物件,value就是存盤的值,每個Thread的Map值只對當前的執行緒可見,其它的執行緒不可以訪問當前執行緒物件中Map的值,當前的執行緒被銷毀,Map也隨之被銷毀,Map中的資料如果沒有被參考、沒有被使用,則隨時GC回收,
ThreadLocal常用方法

set(T):將內容存盤到ThreadLocal
get():從執行緒去私有的變數
remove():從執行緒中移除私有變數
package ThreadLocalDemo;
import java.text.SimpleDateFormat;
/**
* user:ypc;
* date:2021-06-13;
* time: 18:37;
*/
public class ThreadLocalDemo1 {
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//設定私有變數
threadLocal.set(new SimpleDateFormat("mm:ss"));
//得到ThreadLocal
SimpleDateFormat simpleDateFormat = threadLocal.get();
//移除
threadLocal.remove();
}
}
ThreadLocal的初始化
ThreadLocal提供了兩種初始化的方法
initialValue()和
initialValue()初始化:
package ThreadLocalDemo;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* user:ypc;
* date:2021-06-13;
* time: 19:07;
*/
public class ThreadLocalDemo2 {
//創建并初始化ThreadLocal
private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal() {
@Override
protected SimpleDateFormat initialValue() {
System.out.println(Thread.currentThread().getName() + "執行了自己的threadLocal中的初始化方法initialValue()");
return new SimpleDateFormat("mm:ss");
}
};
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
Date date = new Date(5000);
System.out.println("thread0格式化時間之后得結果時:" + threadLocal.get().format(date));
});
thread1.setName("thread0");
thread1.start();
Thread thread2 = new Thread(() -> {
Date date = new Date(6000);
System.out.println("thread1格式化時間之后得結果時:" + threadLocal.get().format(date));
});
thread2.setName("thread1");
thread2.start();
}
}

withInitial方法初始化:
package ThreadLocalDemo;
import java.util.function.Supplier;
/**
* user:ypc;
* date:2021-06-14;
* time: 17:23;
*/
public class ThreadLocalDemo3 {
private static ThreadLocal<String> stringThreadLocal =
ThreadLocal.withInitial(new Supplier<String>() {
@Override
public String get() {
System.out.println("執行了withInitial()方法");
return "我是" + Thread.currentThread().getName() + "的ThreadLocal";
}
});
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println(stringThreadLocal.get());
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(stringThreadLocal.get());
}
});
thread2.start();
}
}

注意:
ThreadLocal如果使用了set()方法的話,那么它的初始化方法就不會起作用了,
來看:👇
package ThreadLocalDemo;
/**
* user:ypc;
* date:2021-06-14;
* time: 18:43;
*/
class Tools {
public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA中取值:" + Tools.t1.get());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadLocalDemo4 {
public static void main(String[] args) throws InterruptedException {
//main是ThreadA 的 父執行緒 讓main執行緒set,ThreadA,是get不到的
if (Tools.t1.get() == null) {
Tools.t1.set("main父執行緒的set");
}
System.out.println("main get 到了: " + Tools.t1.get());
Thread.sleep(1000);
ThreadA a = new ThreadA();
a.start();
}
}

類ThreadLocal不能實作值的繼承,那么就可以使用InheritableThreadLocal了👇
InheritableThreadLocal的使用
使用InheritableThreadLocal可以使子執行緒繼承父執行緒的值

在來看運行的結果:

子執行緒有最新的值,父執行緒依舊是舊的值
package ThreadLocalDemo;
/**
* user:ypc;
* date:2021-06-14;
* time: 19:07;
*/
class ThreadB extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadB中取值:" + Tools.t1.get());
if (i == 5){
Tools.t1.set("我是ThreadB中新set()");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadLocalDemo5 {
public static void main(String[] args) throws InterruptedException {
if (Tools.t1.get() == null) {
Tools.t1.set("main父執行緒的set");
}
System.out.println("main get 到了: " + Tools.t1.get());
Thread.sleep(1000);
ThreadA a = new ThreadA();
a.start();
Thread.sleep(5000);
for (int i = 0; i < 10; i++) {
System.out.println("main的get是:" + Tools.t1.get());
Thread.sleep(100);
}
}
}

ThreadLocal的臟讀問題
來看👇
package ThreadLocalDemo;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-14;
* time: 19:49;
*/
public class ThreadLocalDemo6 {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static class MyThread extends Thread {
private static boolean flag = false;
@Override
public void run() {
String name = this.getName();
if (!flag) {
threadLocal.set(name);
System.out.println(name + "設定了" + name);
flag = true;
}
System.out.println(name + "得到了" + threadLocal.get());
}
}
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 0,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10));
for (int i = 0; i < 2; i++) {
threadPoolExecutor.execute(new MyThread());
}
threadPoolExecutor.shutdown();
}
}

發生了臟讀:
執行緒池復用了執行緒,也復用了這個執行緒相關的靜態屬性,就導致了臟讀
那么如何避免臟讀呢?
去掉static 之后:


單例模式與多執行緒
單例模式就是全域唯一但是所有程式都可以使用的物件
寫單例模式步驟:
1.將建構式設定為私有的
2.創建一個靜態的類變數
3.提供獲取單例的方法
立即加載/餓漢模式
/**
* user:ypc;
* date:2021-06-13;
* time: 21:02;
*/
//餓漢方式實作單例模式
public class Singleton {
//1.將建構式設定為私有的,不然外部可以創建
private Singleton(){
}
//2.創建靜態的類變數(讓第三步的方法進行回傳)
private static Singleton singleton = new Singleton();
//給外部介面提供的獲取單例的方法
public static Singleton getInstance(){
return singleton;
}
}
測驗餓漢的單例模式
//測驗餓漢方式實作的單例模式,創建兩個執行緒,看是不是得到了一個實列物件,如果為true就說明餓漢的單例模式沒有問題
static Singleton singleton1 = null;
static Singleton singleton2 = null;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
singleton1 = Singleton.getInstance();
});
Thread thread2 = new Thread(() -> {
singleton2 = Singleton.getInstance();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(singleton1 == singleton2);
}

延時加載/懶漢模式
不會隨著程式的啟動而啟動,而是等到有人呼叫它的時候,它才會初始化
/**
* user:ypc;
* date:2021-06-13;
* time: 21:22;
*/
//懶漢方式實作單例模式
public class Singleton2 {
static class Singleton {
//1.設定私有的建構式
private Singleton() {
}
//2.提供一個私有的靜態變數
private static Singleton singleton = null;
//3.提供給外部呼叫,回傳一個單例物件給外部
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
}
那么這樣寫有什么問題呢?
我們來看看多執行緒情況下的懶漢方式實作單例模式:
/**
* user:ypc;
* date:2021-06-13;
* time: 21:22;
*/
//懶漢方式實作單例模式
public class Singleton2 {
static class Singleton {
//1.設定私有的建構式
private Singleton() {
}
//2.提供一個私有的靜態變數
private static Singleton singleton = null;
//3.提供給外部呼叫,回傳一個單例物件給外部
public static Singleton getInstance() throws InterruptedException {
if (singleton == null) {
Thread.sleep(100);
singleton = new Singleton();
}
return singleton;
}
}
static Singleton singleton1 = null;
static Singleton singleton2 = null;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
singleton1 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
singleton2 = Singleton.getInstance();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(singleton1 == singleton2);
}
}
結果:

所以發生了執行緒不安全的問題
那么要如何更改呢?
加鎖:👇

結果就是true了:

給方法加鎖可以實作執行緒安全,但是所鎖的粒度太大,
使用雙重校驗鎖優化后:
static class Singleton {
//1.設定私有的建構式
private Singleton() {
}
//2.提供一個私有的靜態變數
private static Singleton singleton = null;
//3.提供給外部呼叫,回傳一個單例物件給外部
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}

那么這樣寫就沒有問題了嗎?
不是的:有可能還會發生指令重排的問題
當有執行緒在進行第一次初始化的時候,就有可能發生問題👇
先來看初始化的程序
1,先分配記憶體空間
2.初始化
3.將singleton指向記憶體
有可能指令重排序之后:
執行緒1執行的順序變成了 1 --> 3 --> 2
在執行緒1執行完1、3之后時間片使用完了
執行緒2再來執行,執行緒2得到了未初始化的singleton,也就是的到了一個空的物件
也就發生了執行緒不安全的問題
那么要如何解決指令重排序的問題呢?
那就是使用volatile關鍵字👇:
/**
* user:ypc;
* date:2021-06-13;
* time: 21:22;
*/
//懶漢方式實作單例模式
public class Singleton2 {
static class Singleton {
//1.設定私有的建構式
private Singleton() {
}
//2.提供一個私有的靜態變數
private static volatile Singleton singleton = null;
//3.提供給外部呼叫,回傳一個單例物件給外部
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
這樣就沒有問題了
餓漢/懶漢對比
餓漢方式:
優點:實作簡單,不存在執行緒安全的問題,因為餓漢的方式是隨著程式的啟動而初始化的,因為類加載是執行緒安全的,所以它是執行緒安全的,
缺點:隨著程式的啟動而啟動,有可能在整個程式的運行周期都沒有用到,這樣就帶來了不必要的開銷,
阻塞佇列的實作
import java.util.Random;
/**
* user:ypc;
* date:2021-06-14;
* time: 8:57;
*/
public class MyBlockingQueue {
private int[] values;
private int first;
private int last;
private int size;
MyBlockingQueue(int maxSize) {
this.values = new int[maxSize];
this.first = 0;
this.last = 0;
this.size = 0;
}
public void offer(int val) throws InterruptedException {
synchronized (this) {
if (this.size == values.length) {
this.wait();
}
this.values[last++] = val;
size++;
//變為回圈佇列
if (this.last == values.length) {
this.last = 0;
}
//喚醒消費者
this.notify();
}
}
public int poll() throws InterruptedException {
int result = 0;
synchronized (this) {
if (size == 0) {
this.wait();
}
result = this.values[first++];
this.size--;
if (first == this.values.length) {
this.first = 0;
}
//喚醒生產者開生產資料
this.notify();
}
return result;
}
public static void main(String[] args) {
MyBlockingQueue myBlockingQueue = new MyBlockingQueue(100);
//生產者
Thread thread1 = new Thread(() -> {
while (true) {
try {
int num = new Random().nextInt(100);
myBlockingQueue.offer(num);
System.out.println("生產者生產資料:" + num);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//消費者
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
while (true) {
int res = myBlockingQueue.poll();
System.out.println("消費者消費資料:" + res);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
}
可以看到生產者每生產一個資料都會被取走:

常見的鎖策略
樂觀鎖
它認為程式在一般的情況下不會發生問題,所以他在使用的時候不會加鎖,只有在資料修改的時候才會判斷有沒有鎖競爭,如果沒有就會直接修改資料,如果有就會回傳失敗資訊給用戶自行處理,
CAS
樂觀鎖的經典實作
Compare and Swap
CAS 實作的三個重要的屬性:
(V,A,B)
V:記憶體中的值
A:預期的舊值
B:新值
V == A? V -> B : 修改失敗
修改失之后:
自旋對比和替換
CAS 的底層實作:
CAS在Java中是通過unsafe來實作的,unsafe時本地類和本地方法,它是c/c++實作的原生方法,通過呼叫作業系統Atomic:: cmpxchg原子指令來實作的
CAS在java中的應用
i++、i–問題
可以使用加鎖、ThreadLocal 解決問題
也可以使用atomic.AtomicInteger來解決問題,底層也使用了樂觀鎖,
import java.util.concurrent.atomic.AtomicInteger;
/**
* user:ypc;
* date:2021-06-14;
* time: 10:12;
*/
public class ThreadDemo1 {
private static AtomicInteger count = new AtomicInteger(0);
private static final int MaxSize = 100000;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < MaxSize; i++) {
count.getAndIncrement();//i++
}
}
});
thread1.start();
Thread thread2 = new Thread(()->{
for (int i = 0; i < MaxSize; i++) {
count.getAndDecrement();//i--
}
});
thread2.start();
thread1.join();
thread2.join();
System.out.println(count);
}
}

CAS 的ABA問題
當有多個執行緒對一個原子類進行操作的時候,某個執行緒在短時間內將原子類的值A修改為B,又馬上將其修改為A,此時其他執行緒不感知,還是會修改成功,
來看:
import java.util.concurrent.atomic.AtomicInteger;
/**
* user:ypc;
* date:2021-06-14;
* time: 10:43;
*/
public class ThreadDemo2 {
//執行緒操作資源,原子類ai的初始值為4
static AtomicInteger ai = new AtomicInteger(4);
public static void main(String[] args) {
new Thread(() -> {
//利用CAS將ai的值改成5
boolean b = ai.compareAndSet(4, 5);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為5:"+b);
//休眠一秒
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
//利用CAS將ai的值改回4
b = ai.compareAndSet(5,4);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為4:"+b);
},"A").start();
new Thread(() -> {
//模擬此執行緒執行較慢的情況
try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
//利用CAS將ai的值從4改為10
boolean b = ai.compareAndSet(4, 10);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為10:"+b);
},"B").start();
//等待其他執行緒完成,為什么是2,因為一個是main執行緒,一個是后臺的GC執行緒
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("ai最終的值為:"+ai.get());
}
}
上面例子模擬的是A、B兩個執行緒操作一個資源ai,A的執行速度比B的快,在B執行前,A就已經將ai的值改為5之后馬上又把ai的值改回為4,但是B不感知,所以最后B就修改成功了,
那么會造成會有什么問題呢?
假設A現在有100元,要給B轉賬100元,點擊了兩次轉賬按鈕,第一次B只會得到100元,A現在剩余0元,第二次A是0元,預期的舊值是100,不相等,就不會執行轉賬操作,
如果點擊第二次按鈕之前,A又得到了100元,B不能感知的到,此時A得到了轉賬100元,預期的舊值就是100,又會轉給B100元,
那么如何解決這個問題呢?👇
ABA 問題的解決
我們可以給操作加上版本號,每次修改的時候判斷版本號和預期的舊值,如果不一樣就不會執行操作了,
即是預期的舊值和V值相等,但是版本號不一樣,也不會執行操作,
在Java中的實作:
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* user:ypc;
* date:2021-06-14;
* time: 11:05;
*/
public class ThreadDemo3 {
static AtomicStampedReference<Integer> ai = new AtomicStampedReference<>(4,0);
public static void main(String[] args) {
new Thread(() -> {
//四個引數分別是預估記憶體值,更新值,預估版本號,初始版本號
//只有當預估記憶體值==實際記憶體值相等并且預估版本號==實際版本號,才會進行修改
boolean b = ai.compareAndSet(4, 5,0,1);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為5:"+b);
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
b = ai.compareAndSet(5,4,1,2);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為4:"+b);
},"A").start();
new Thread(() -> {
try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}
boolean b = ai.compareAndSet(4, 10,0,1);
System.out.println(Thread.currentThread().getName()+"是否成功將ai的值修改為10:"+b);
},"B").start();
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("ai最終的值為:"+ai.getReference());
}
}

注意:里面的舊值對比的是參考,
如果范圍在-128 - 127 里,會使用快取的值,如果超過了這個范圍,就會重新來new物件
可以將Integer 的高速快取的值的邊界調整
悲觀鎖
悲觀鎖認為只要執行多執行緒的任務,就會發生執行緒不安全的問題,所以正在進入方法之后會直接加鎖,
直接使用synchronzied關鍵字給方法加鎖就可以了
獨占鎖、共享鎖、自旋鎖、可重入鎖
獨占鎖:指的是這一把鎖只能被一個執行緒所擁有
比如:synchronzied、Lock
共享鎖: 指的是一把鎖可以被多個執行緒同時擁有
ReadWriterLock讀寫鎖就是共享鎖
讀鎖就是共享的,將鎖的粒度更加的細化
import java.util.Date;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* user:ypc;
* date:2021-06-14;
* time: 11:42;
*/
public class ThreadDemo4 {
//創建讀寫鎖
public static void main(String[] args) {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//讀鎖
ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
//寫鎖
ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 1000,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
//任務一:讀鎖演示
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "進入了讀鎖,時間:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
});
//任務二:讀鎖演示
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "進入了讀鎖,時間:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readLock.unlock();
}
}
});
//任務三:寫鎖
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "進入了寫鎖,時間:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
//任務四:寫鎖
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "進入了寫鎖,時間:" + new Date());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock.unlock();
}
}
});
}
}

可重入鎖:
當一個執行緒擁有了鎖之后,可以重復的進入,就叫可重入鎖,
synchronzied就是典型的可重入鎖的代表
讀鎖的時間在一秒內,所以兩個執行緒讀到的鎖是一把鎖,即讀鎖是共享鎖
而寫鎖的時間剛好是一秒,所以寫鎖是獨占鎖,


自旋鎖:相當于死回圈,一直嘗試獲取鎖
詳解synchronized鎖的優化問題
synchroized加鎖的整個程序,都是依賴于Monitor(監視器鎖)實作的,監視器鎖在虛擬機中又是根據作業系統的Metux Lock(互斥量)來實作的,這就導致在加鎖的程序中需要頻繁的在作業系統的內核態和和JVM級別的用戶態進行切換,并且涉及到執行緒背景關系的切換,是比較消耗性能的,所以后來有一位大佬Doug Lea基于java實作了一個AQS的框架,提供了Lock鎖,性能遠遠高于synchroized,這就導致Oracle公司很沒有面子,因此他們在JDK1.6對synchroized做了優化,引入了偏向鎖和輕量級鎖,存在一個從無鎖-》偏向鎖–》輕量級鎖–》重量級鎖的升級程序,優化后性能就可以和Lock鎖的方式持平了,
物件頭
HotSpot虛擬機中,物件在記憶體中分為三塊區域:物件頭、實體資料和對齊填充,

物件頭包括兩部分:Mark Word 和 型別指標,型別指標是指向該物件所屬類物件的指標,我們不關注,mark word用于存盤物件的HashCode、GC分代年齡、鎖狀態等資訊,在32位系統上mark word長度為32bit,64位系統上長度為64bit,他不是一個固定的資料結構,是和物件的狀態緊密相關,有一個對應關系的,具體如下表所示:

當某一執行緒第一次獲得鎖的時候,虛擬機會把物件頭中的鎖標志位設定為“01”,把偏向模式設定為“1”,表示進入偏向鎖模式,同時使用CAS操作將獲取到這個鎖的執行緒的ID記錄在物件的Mark Word中,如果CAS操作成功,持有偏向鎖的執行緒每次進入這個鎖的相關的同步塊的時候,虛擬機都可以不在進行任何的同步操作,
當其他執行緒進入同步塊時,發現已經有偏向的執行緒了,偏向模式馬上結束,根據鎖物件目前是否處于被鎖定的狀態決定是否撤銷偏向,也就是將偏向模式設定為“0”,撤銷后標志位恢復到“01”,也就是未鎖定的狀態或者輕量級鎖定,標志位為“00”的狀態,后續的同步操作就按照下面的輕量級鎖那樣去執行
1、在執行緒進入同步塊的時候,如果同步物件狀態為無鎖狀態(鎖標志為 01),虛擬機首先將在當前執行緒的堆疊幀中建立一個名為鎖記錄的空間,用來存盤鎖物件目前的 Mark Word 的拷貝,拷貝成功后,虛擬機將使用 CAS 操作嘗試將物件的 Mark Word 更新為指向 Lock Record 的指標,并將 Lock Record 里的 owner 指標指向鎖物件的 Mark Word,如果更新成功,則執行 2,否則執行 3,

2、如果這個更新動作成功了,那么這個執行緒就擁有了該物件的鎖,并且鎖物件的 Mark Word 中的鎖標志位設定為 “00”,即表示此物件處于輕量級鎖定狀態,這時候虛擬機執行緒堆疊與堆中鎖物件的物件頭的狀態如圖所示,

3、如果這個更新操作失敗了,虛擬機首先會檢查鎖物件的 Mark Word 是否指向當前執行緒的堆疊幀,如果是就說明當前執行緒已經擁有了這個物件的鎖,那就可以直接進入同步塊繼續執行,否則說明多個執行緒競爭鎖,輕量級鎖就要膨脹為重要量級鎖,鎖標志的狀態值變為 “10”,Mark Word 中存盤的就是指向重量級鎖的指標,后面等待鎖的執行緒也要進入阻塞狀態,而當前執行緒便嘗試使用自旋來獲取鎖,自旋失敗后膨脹為重量級鎖,被阻塞,
Semaphore
Semaphore的作用:
在java中,使用了synchronized關鍵字和Lock鎖實作了資源的并發訪問控制,在同一時間只允許唯一了執行緒進入臨界區訪問資源(讀鎖除外),這樣子控制的主要目的是為了解決多個執行緒并發同一資源造成的資料不一致的問題,也就是做限流的作用
Semaphore實作原理:
Semaphore是用來保護一個或者多個共享資源的訪問,Semaphore內部維護了一個計數器,其值為可以訪問的共享資源的個數,一個執行緒要訪問共享資源,先獲得信號量,如果信號量的計數器值大于1,意味著有共享資源可以訪問,則使其計數器值減去1,再訪問共享資源,
如果計數器值為0,執行緒進入休眠,當某個執行緒使用完共享資源后,釋放信號量,并將信號量內部的計數器加1,之前進入休眠的執行緒將被喚醒并再次試圖獲得信號量,
就好比一個廁所管理員,站在門口,只有廁所有空位,就開門允許與空側數量等量的人進入廁所,多個人進入廁所后,相當于N個人來分配使用N個空位,為避免多個人來同時競爭同一個側衛,在內部仍然使用鎖來控制資源的同步訪問,
Semaphore的使用:
Semaphore使用時需要先構建一個引數來指定共享資源的數量,Semaphore構造完成后即是獲取Semaphore、共享資源使用完畢后釋放Semaphore,
使用Semaphore 來模擬有四輛車同時到達了停車場的門口,但是停車位只有兩個,也就是只能停兩輛車,這就可以使用信號量來實作,👇:
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* user:ypc;
* date:2021-06-14;
* time: 14:00;
*/
public class ThreadDemo6 {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100), new ThreadPoolExecutor.DiscardPolicy());
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "到達了停車場");
try {
Thread.sleep(1000);
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "進入了停車場");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出了了停車場");
semaphore.release();
}
});
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "到達了停車場");
try {
Thread.sleep(1000);
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "進入了停車場");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出了了停車場");
semaphore.release();
}
});
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "到達了停車場");
try {
Thread.sleep(1000);
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "進入了停車場");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出了了停車場");
semaphore.release();
}
});
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "到達了停車場");
try {
Thread.sleep(1000);
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "進入了停車場");
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "出了了停車場");
semaphore.release();
}
});
threadPoolExecutor.shutdown();
}
}

CountDownLatch\CyclicBarrier
CountDownLatch
一個可以用來協調多個執行緒之間的同步,或者說起到執行緒之間的通信作用的工具類,
它能夠使一個執行緒在等待另外一些執行緒完成各自作業之后,再繼續執行,使用一個計數器進行實作,計數器初始值為執行緒的數量,當每一個執行緒完成自己任務后,計數器的值就會減一,當計數器的值為0時,表示所有的執行緒都已經完成了任務,然后在CountDownLatch上等待的執行緒就可以恢復執行任務,
CountDownLatch的用法
某一執行緒在開始運行前等待n個執行緒執行完畢,
將CountDownLatch的計數器初始化為n:new CountDownLatch(n) ,每當一個任務執行緒執行完畢,就將計數器減1, countdownlatch.countDown(),當計數器的值變為0時,在CountDownLatch上 await() 的執行緒就會被喚醒,一個典型應用場景就是啟動一個服務時,主執行緒需要等待多個組件加載完畢,之后再繼續執行,
實作多個執行緒開始執行任務的最大并行性,注意是并行性,不是并發,強調的是多個執行緒在某一時刻同時開始執行,做法是初始化一個共享的CountDownLatch(1),將其計數器初始化為1,多個執行緒在開始執行任務前首先 coundownlatch.await(),當主執行緒呼叫 countDown() 時,計數器變為0,多個執行緒同時被喚醒,
CountDownLatch的不足
CountDownLatch是一次性的,計數器的值只能在構造方法中初始化一次,之后沒有任何機制再次對其設定值,當CountDownLatch使用完畢后,它不能再次被使用,

模擬賽跑:當三個運動員都到達終點的時候宣布比賽結束
import java.util.Random;
import java.util.concurrent.*;
/**
* user:ypc;
* date:2021-06-14;
* time: 14:27;
*/
public class ThreadDemo7 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開跑");
int num = new Random().nextInt(4);
num += 1;
try {
Thread.sleep(1000*num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到達了終點");
countDownLatch.countDown();
}
});
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開跑");
int num = new Random().nextInt(4);
num += 1;
try {
Thread.sleep(1000*num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到達了終點");
countDownLatch.countDown();
}
});
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開跑");
int num = new Random().nextInt(4);
num += 1;
try {
Thread.sleep(1000*num);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "到達了終點");
countDownLatch.countDown();
}
});
countDownLatch.await();
System.out.println("所有的選手都到達了終點");
threadPoolExecutor.shutdown();
}
}

CyclicBarrier
CyclicBarrier 的字面意思是可回圈(Cyclic)使用的屏障(Barrier),它要做的事情是,讓一組執行緒到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個執行緒到達屏障時,屏障才會開門,所有被屏障攔截的執行緒才會繼續干活,執行緒進入屏障通過CyclicBarrier的await()方法,
CyclicBarrier默認的構造方法是CyclicBarrier(int parties),其引數表示屏障攔截的執行緒數量,每個執行緒呼叫await方法告訴CyclicBarrier我已經到達了屏障,然后當前執行緒被阻塞,
import java.util.concurrent.*;
/**
* user:ypc;
* date:2021-06-14;
* time: 15:03;
*/
public class ThreadDemo8 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
@Override
public void run() {
System.out.println("到達了回圈屏障");
}
});
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 200,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
for (int i = 0; i < 10; i++) {
int finalI = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(finalI * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "進入了任務");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "退出了任務");
}
});
}
threadPoolExecutor.shutdown();
}
}

CyclicBarrier原理
每當執行緒執行await,內部變數count減1,如果count!= 0,說明有執行緒還未到屏障處,則在鎖條件變數trip上等待,
當count == 0時,說明所有執行緒都已經到屏障處,執行條件變數的signalAll方法喚醒等待的執行緒,
其中 nextGeneration方法可以實作屏障的回圈使用:
重新生成Generation物件
恢復count值
CyclicBarrier可以回圈的使用,
hashmap/ConcurrentHashMap
hashmap在JDK1.7中頭插死回圈問題
來看👇JDK1.7 hashMap transfer的原始碼
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
來看多執行緒情況下的問題:

這樣就會造成死回圈,
hashmap在JDK1.8中值覆寫問題
在JDK1.8的時候使用的是尾插法
來看👇:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) // 如果沒有hash碰撞則直接插入元素
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
在多執行緒的情況下:

其中第六行代碼是判斷是否出現hash碰撞,假設兩個執行緒1、2都在進行put操作,并且hash函式計算出的插入下標是相同的,當執行緒1執行完第六行代碼后由于時間片耗盡導致被掛起,而執行緒2得到時間片后在該下標處插入了元素,完成了正常的插入,然后執行緒A獲得時間片,由于之前已經進行了hash碰撞的判斷,所有此時不會再進行判斷,而是直接進行插入,這就導致了執行緒2插入的資料被執行緒1覆寫了,從而執行緒不安全,
除此之前,還有就是代碼的第38行處有個++size,我們這樣想,還是執行緒1、2,這兩個執行緒同時進行put操作時,假設當前HashMap的zise大小為10,當執行緒1執行到第38行代碼時,從主記憶體中獲得size的值為10后準備進行+1操作,但是由于時間片耗盡只好讓出CPU,執行緒2快樂的拿到CPU還是從主記憶體中拿到size的值10進行+1操作,完成了put操作并將size=11寫回主記憶體,然后執行緒1再次拿到CPU并繼續執行(此時size的值仍為10),當執行完put操作后,還是將size=11寫回記憶體,此時,執行緒1、2都執行了一次put操作,但是size的值只增加了1,所有說還是由于資料覆寫又導致了執行緒不安全,
ConcurrentHashMap & HashTable
來看這個🤣
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/287876.html
標籤:其他
