概述
面試中,多線程和併發編程已經是必不可少的了,我經常看到此類問題,當時也簡單瞭解過,什麼繼承Thread類,實現Runnable接口,這些都被說爛了,知道這些當然是遠遠不夠的,於是這幾天搜索相關資料惡補了一下,爲了方便後期複習,在此做個總結。
繼承Thread類
這個可以說是很原始的方式,就是繼承Thread類,重寫run方法。
package com.hzy;
public class Main {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
由於Java是單繼承的,所以這種方法用的比較少,一般都是用Runnable接口
實現Runnable接口
這裏給出幾個常用的構造方法
先給出一個傳統的方法實現
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(new MyThread()).start();
new Thread(new MyThread(),"賀志營").start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
我們也可以通過匿名內部類實現
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();
}
}
還可以通過Lamata表達式實現
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}).start();
}
}
實現Callable接口
Callable接口可以接收返回值,可以拋出異常,重寫的是call方法
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 創建執行服務
ExecutorService service = Executors.newFixedThreadPool(3);
// 提交執行
Future<Boolean> future1 = service.submit(new MyThread());
Future<Boolean> future2 = service.submit(new MyThread());
Future<Boolean> future3 = service.submit(new MyThread());
// 獲取返回值
Boolean b1 = future1.get();
Boolean b2 = future2.get();
Boolean b3 = future3.get();
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
// 關閉服務
service.shutdown();
}
}
class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
}
}
另外還可以通過FutureTask適配器創建
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 創建一個適配器
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask,"A").start();
Boolean o = (Boolean) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
}
}
線程的五大狀態
創建狀態
所謂的創建狀態,也就是我們的new Thread();
就緒狀態
所謂的就緒狀態,就是我們myThread.start();
運行狀態
運行狀態就是我們的代碼執行。
阻塞狀態
當線程等待(wait)、同步(synchronized)、sleep和join的時候
死亡狀態
run()結束、main()結束
線程常用方法
setPriority(int new Priority)
更改線程的優先級sleep(long millis)
讓當前線程進入休眠join()
相當於插隊,插入的線程執行結束後,被插隊線程繼續執行。yield()
線程禮讓,暫停當前的線程,並把該線程狀態轉化爲就緒狀態interrupt()
中斷線程(不建議用)isAlive()
判斷線程是否處於存活狀態
多線程買票案例
當多個線程操作同一個資源的時候,就會出現線程安全問題。假設我們有100張票,在三個窗口同時賣,也就是我們有三個線程去買票。
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
new Thread(myThread,"窗口4").start();
new Thread(myThread,"窗口5").start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() {
if (ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模擬買票延遲100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
}
}
可以看出,出現了線程安全問題,原因是在對ticket變爲0之前,有多個線程同時進來。
解決辦法,可以通過synchronized線程同步,可以使用同步方法和同步代碼塊進行解決。同步方法,也就是在方法上加一個synchronized關鍵字。
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public synchronized void buy() {
if (ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模擬買票延遲100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
}
}
同步代碼塊鎖的是一個對象,即是需要變化的量
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
}
}
class MyTicket {
int ticket;
public MyTicket(int ticket) {
this.ticket = ticket;
}
}
class MyThread implements Runnable {
MyTicket myTicket = new MyTicket(100);
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() {
synchronized (myTicket) {
if (myTicket.ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模擬買票延遲100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ myTicket.ticket--);
}
}
}
死鎖
所謂死鎖,也就是A擁有A資源的同時想要B的資源,B擁有B資源的同時想要A的資源。
package com.hzy;
public class Main {
public static void main(String[] args){
A a = new A();
B b = new B();
new Thread(new MyThread(a,b,0)).start();
new Thread(new MyThread(a,b,1)).start();
}
}
class A {
}
class B {
}
class MyThread implements Runnable {
private A a;
private B b;
private int choice;
public MyThread(A a,B b,int choice) {
this.a = a;
this.b = b;
this.choice = choice;
}
@Override
public void run() {
if (choice == 0) {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "獲得" + a);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "獲得" + b);
}
}
}else {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "獲得" + b);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "獲得" + a);
}
}
}
}
}
可以看出,A需要B資源,B需要A資源,出現了死鎖的狀態。
Lock鎖
Lock是一個接口,而不是一個關鍵字,他是一個顯示鎖,只能鎖同步代碼塊,不能鎖方法,可以顯示加鎖,釋放鎖,可以指定喚醒某一個線程,Lock鎖有一個實現類是ReentrantLock,可重入鎖(遞歸鎖),在這裏說一下,所有的鎖都是可重入鎖,也就是如果我們獲得了外面的鎖之後,會自動獲取裏面的鎖。
正常的,如果我們不加鎖,會出現線程安全問題
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket --);
}
}
}
可以通過RenntrantLock進行加鎖,顯示鎖,記得解鎖。
package com.hzy;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
// 定義lock鎖
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket --);
}else {
break;
}
lock.unlock();
}
}
}
比ReentrantLock更細的鎖是,ReentrantReadWriteLock,這裏有一個讀鎖,一個寫鎖,
package com.hzy;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.put(temp + "",temp + "");
},temp + "").start();
}
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.get(temp + "");
},temp + "").start();
}
}
}
class MyReadWriteLock {
private volatile Map<String,String> map = new HashMap<>();
public void put(String key,String value) {
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "寫入完成");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "讀取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取完成");
}
}
如果不加鎖的話,在寫入完成之前會被其他線程插入
而我們想要的是,在寫入的時候,只能是一個線程,而讀取的時候,可以是多個線程。
package com.hzy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Main {
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.put(temp + "",temp + "");
},temp + "").start();
}
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.get(temp + "");
},temp + "").start();
}
}
}
class MyReadWriteLock {
private volatile Map<String,String> map = new HashMap<>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,String value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "寫入完成");
readWriteLock.writeLock().unlock();
}
public void get(String key) {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "讀取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取完成");
readWriteLock.readLock().unlock();
}
}
可以看出,在寫入的時候,是一個寫入,一個寫入完成,在讀取的時候,可能會有多個讀取。
synchronized和Lock的區別
- synchronized是關鍵字,Lock是類
- synchronized無法獲取鎖的狀態,Lock可以
- synchronized會自動釋放鎖,Lock需要手動
- synchronized沒有Lock鎖靈活(Lock鎖可以自己定製)
生產者消費者問題
這是一個經典的線程通信問題,也就是不同線程之間有聯繫,生產者生產的東西放到緩衝區,如果緩衝區滿了,生產者進入堵塞,緩衝區空了,消費者堵塞。
常用的方法有wait(),notify(),notifyAll()
package com.hzy;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
Buffer buffer = new Buffer();
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("生產者生產了" + i);
buffer.put();
}
},"生產者").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("消費者消費了" + i);
buffer.get();
}
},"消費者").start();
}
}
// 緩衝區
class Buffer {
private int len = 0;
public synchronized void put() {
if (len < 10) {
len ++;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notifyAll();
}
public synchronized void get() {
if (len > 0) {
len --;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notifyAll();
}
}
學習了Lock鎖,這裏的生產者消費者問題,可以通過Lock鎖實現,可以指定喚醒某個線程,常用的方法是await,signal。
這裏給出緩衝區
// 緩衝區
class Buffer {
private int len = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();// 該對象可以設置一些條件
public void put() {
lock.lock();
if (len < 10) {
len ++;
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
condition.signalAll();
lock.unlock();
}
public void get() {
lock.lock();
if (len > 0) {
len --;
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
condition.signalAll();
lock.unlock();
}
}
其中這裏引入了Condition,他可以指定喚醒某個線程,這裏我們演示三個線程ABC。
package com.hzy;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
Buffer buffer = new Buffer();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.a();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.b();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.c();
}
},"C").start();
}
}
// 緩衝區
class Buffer {
private int flag = 1;// 1執行A,2執行B,3執行C
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();// 該對象可以設置一些條件
Condition condition2 = lock.newCondition();// 該對象可以設置一些條件
Condition condition3 = lock.newCondition();// 該對象可以設置一些條件
public void a() {
lock.lock();
try {
while (flag != 1) {
condition1.await();
}
System.out.println("A");
flag = 2;
condition2.signal();// 指定喚醒B
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void b() {
lock.lock();
try {
while (flag != 2) {
condition2.await();
}
System.out.println("B");
flag = 3;
condition3.signal();// 指定喚醒C
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void c() {
lock.lock();
try {
while (flag != 3) {
condition3.await();
}
System.out.println("C");
flag = 1;
condition1.signal();// 指定喚醒A
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
結果都是ABC、ABC…
八鎖問題
一
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
先輸出sendMsg,不要理解爲是先調用了sendMsg,而是因爲A線程先獲得了鎖。
二
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
這個例子,再次解釋了是因爲sendMsg先獲得的鎖,這裏的synchronized鎖的對象是調用者,這兩個方法用的是同一把鎖,誰先拿到,誰先執行
三
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
// public synchronized void call() {
// System.out.println("call");
// }
public void call() {
System.out.println("call");
}
}
這裏把call方法的synchronized去掉了,會先輸出哪個呢,答案是先輸出call,因爲他不去獲取鎖資源,所以直接輸出了
四
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
這裏用兩個phone對象,分別調用sendMsg和call方法,會先執行哪個,答案是先執行call,因爲這裏的鎖鎖的是對象,而他們不是同一個對象,所以資源不受影響。
五
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call() {
System.out.println("call");
}
}
這裏是通過一個對象,去調用靜態的同步方法,看看是先sendMsg還是call,答案是sentMsg,因爲這裏鎖的是Class對象(只有一個)
六
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call() {
System.out.println("call");
}
}
這裏是通過兩個對象去調用sentMsg和call,結果是什麼呢,答案還是先執行sentMsg,因爲鎖的是Class對象,所以不管是phone1還是phone2,都是屬於Phone的Class對象。
七
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
這裏鎖的一個是靜態同步方法,一個是普通同步方法,用一個對象對調用,結果是什麼呢,答案是先call,因爲靜態同步方法鎖的是Class對象,而普通同步方法鎖的是調用者,鎖的不是同一個東西。
八
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
這裏用兩個對象去調用靜態同步方法sendMsg和普通同步方法call,會先輸出哪個,答案是call,因爲sendMsg鎖的是Class,而call鎖的是調用者,不是同一個一個東西
線程池
線程的創建跟Callable差不多,也是用ExecutorService
package com.hzy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(10);// 創建固定個數線程
// ExecutorService service2 = Executors.newCachedThreadPool();// 創建動態線程(自適應大小)
// ExecutorService service1 = Executors.newSingleThreadExecutor();// 創建單個線程
// 執行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 關閉連接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
可以看出,在線程池中拿了三個線程
在阿里巴巴開發手冊中有規定,創建線程池要用ThreadPoolExecutor
對於ThreadPoolExecutor的學習,就從七大參數,四種拒絕策略
int corePoolSize// 核心線程池大小
int maximumPoolSize// 最大核心線程池大小
long keepAliveTime// 超時存活時間
TimeUnit unit// 超時單位
BlockingQueue<Runnable> workQueue// 阻塞隊列
ThreadFactory threadFactory// 線程工廠,用於創建線程
RejectedExecutionHandler handler// 拒絕策略
AbortPolicy());// 銀行滿了還有人進來,不處理,拋出異常(默認)
CallerRunsPolicy();// 銀行滿了,不處理,哪裏來的去哪裏,一般拋給main線程
DiscardPolicy();// 銀行滿了,把該線程丟掉,不拋異常
DiscardOldestPolicy();// 銀行滿了,會和先來的線程競爭,不拋異常
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args){
/**
* 用一個銀行的例子進行講解這七大參數
*/
ExecutorService threadPool = new ThreadPoolExecutor(
2,// 兩個常開營業窗口
5,// 五個窗口,其中三個應急用
3,// 超時存貨時間
TimeUnit.SECONDS,// 超時時間單位
new LinkedBlockingDeque<>(3),// 銀行候客區大小
Executors.defaultThreadFactory(),// 默認線程池工廠
new ThreadPoolExecutor.AbortPolicy());// 銀行滿了還有人進來,不處理,拋出異常(默認)
// new ThreadPoolExecutor.CallerRunsPolicy();// 銀行滿了,不處理,哪裏來的去哪裏,一般拋給main線程
// new ThreadPoolExecutor.DiscardPolicy();// 銀行滿了,把該線程丟掉,不拋異常
// new ThreadPoolExecutor.DiscardOldestPolicy();// 銀行滿了,會和先來的線程競爭,不拋異常
for (int i = 0; i < 8; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
// 關閉連接
threadPool.shutdown();
}
}
在定義線程池最大大小的時候,一般有兩種策略CPU密集型和IO密集型,所謂CPU密集型,也就是,幾核的CPU就定義爲幾,我的是八核,所以定義爲8,Runtime.getRuntime().availableProcessors();// 獲取CPU的核數
,IO密集型,就是判斷程序中有多少個非常耗IO線程的程序,最大線程池的大小要大於這個值即可。
volatile
說起volatile,不難想出三大特性保證可見性,不保證原子性,禁止指令重排。
在講解volatile之前,需要講解一個東西,Java內存模型(Java Memory Model,JMM),當線程讀取內存中的一個變量時,會先把這個變量拷貝到CPU的高速緩存區,然後對其進行操作,操作完成後,會把該變量寫入到內存中。在單線程中是不會出現任何問題的,但是在多線程中就會有問題,當線程1讀取了該變量a=1到緩存區進行了加1操作,還沒寫到內存中,線程2讀取了內存中的變量a=1也進行加1操作,然後線程1寫入內存a=2,線程2也寫入a=2到內存,那麼最後,該變量的值是2,而不是3(出現了線程安全問題)。我們想要當線程1進行了加1操作之後,讓線程2知道,這就是volatile的作用了,可以保證可見性,也就是,當線程1對a變量進行了加1操作,會直接寫入到內存中(立即馬上),並且通知線程2,變量被修改了,要求線程2緩衝區的值去內存中重新讀取。但是,加1操作不是原子性的(三步,首先讀取a變量的值,然後對其進行加1操作,然後賦值給a),也就是說,當線程1讀取a變量到緩衝區後,還沒有修改a的值,此時線程2進來了,讀取了a的值,並且對其進行了加1操作,由於可見性,會把線程1緩衝區的值進行修改,但是,線程1中的CPU已經讀取了緩衝區的值,而且是更新前的值,所以出現了線程安全問題,也是volatile不保證原子性的問題。於是就需要加1操作是原子性操作,於是就有了一個automic包,通過該包下的方法,可以實現加1的原子性操作(還有其他原子性操作)。
在原子操作裏的自增,其實用的是自旋鎖,也就是一直進行比較並交換。
說起原子性操作,其實得聊聊CAS(Compare And Swap,比較並交換),如果我們想要修改某個值num,那麼我們可以通過一個方法compareAndSet(5,6)
意思是,我們期望num的值是5,如果是,就修改爲6。但是這就會有一個問題,我們期望的num是5,如果有其他線程把5修改爲了8,然後又修改爲了5,最終是5,但是已經被修改過一次了,這就是ABA問題。我們可以通過AtomicStampedReference,也就是原子引用,在創建的時候,有一個印記,相當於版本號,每被修改一次,版本號都被更新,所以,當出現ABA問題的時候,我們就可以清楚的知道,被修改了多少次。
還有一個特性就是禁止指令重排,既然要禁止他,那麼就得知道什麼是指令重排,所謂指令重排,也就是,爲了提高程序執行效率,寄存器對代碼的優化,如果我們有一段代碼如下
a = 1;// 語句1
b = 2;// 語句2
a = a + 1;// 語句3
b = a + 1;// 語句4
所謂指令重排,也就是在不影響單線程程序程序結果的情況下進行最優執行排序,可以看出,語句1和語句2的執行順序並不會影響程序的結果,最終a的值爲2,b的值爲3。下面看這個代碼Instance instance = new Instance();
從微觀的角度看,這條語句可以分解爲三步,一是分配內存空間,二是初始化對象三是指向該內存空間,其中二和三會發生指令重排,如果在多線程的情況下,線程A分配了內存空間,並且執行了該內存空間(沒有初始化對象),線程B進行了訪問該內存空間,這就會出錯了。而volatile就是禁止指令重排的,也並非絕對的禁止,假設我們有1、2、3、4、5條語句
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
如果我們對c操作進行了volatile修飾,那麼a和b依然可以指令重排,d和e也可以指令重排,ab和de不能指令重排了,c把他們分開了(專業術語是給c加了個內存屏障)。