JUC:java util concurrent
普通的線程代碼 Thread
Runnable:沒有返回值 效率相比Callable較低
線程和進程
線程、進程
進程:一個程序,QQ.exe Music.exe 程序的集合
一個進程可以包含多個線程,至少包含一個
java默認有幾個線程? 2個 main線程+GC線程
線程:Thread Runnable Callable
Java真的可以開啓線程麼 開不了,只能調用native本地方法,Java無法操作硬件,因爲Java運行在虛擬機上
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
併發、並行
併發:多線程同時操作同一資源
並行:多個人一起行走 CPU多核,多個線程同時執行
併發編程的本質:充分利用CPU的資源4
線程有幾個狀態
public enum State {
//新生
NEW,
//運行
RUNNABLE,
//阻塞
BLOCKED,
//等待
WAITING,
//超時等待
TIMED_WAITING,
//終止
TERMINATED;
}
wati/sleep的區別
1.來自不同的類
wait=>Object
sleep=>Thread
2.關於鎖的釋放
wait釋放鎖,sleep不會釋放鎖
3.使用的範圍不同
wait:必須在同步代碼塊中
sleep:任何地方
4.是否需要捕獲異常
wait:不需要捕獲異常
sleep:需要捕獲異常
Lock鎖
傳統Synchronized
/**
* @Description: 基本的賣票例子
* @Date: 2020-05-23 17:00
**/
/**
* 線程就是一個單獨的資源類,沒有任何附屬的操作
* 1、 屬性 方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//併發 多線程操作同一個資源類
Ticket ticket = new Ticket();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 60; i++) {
ticket.sale();
}
}, "C").start();
}
}
//資源OOP
class Ticket {
//屬性 方法
private int number = 50;
//賣票的方式
public synchronized void sale() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "賣出了第" + (number--) + "票");
}
}
}
Lock接口
public ReentrantLock() {
sync = new NonfairSync(); //非公平鎖
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平鎖:先來後到
非公平鎖:可以插隊(根據CPU),默認非公平鎖
(公平鎖不公平,非公平鎖公平,因爲如果有兩個線程,一個執行3s 一個執行3h,3h的先到,那麼公平鎖就會讓3s的線程等待)
public class SaleTicketDemo02 {
public static void main(String[] args) {
//併發 多線程操作同一個資源類
Ticket02 ticket = new Ticket02();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "A").start();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "B").start();
new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "C").start();
}
}
//Lock三部曲
//new ReentrantLock()
//lock.lock()
//try catch finally => lock.unlock
//資源OOP
class Ticket02 {
//屬性 方法
private int number = 50;
Lock lock = new ReentrantLock();
//賣票的方式
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "賣出了第" + (number--) + "票");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Synchronized 和 Lock的區別
1、Synchronized是關鍵字 Lock是類
2、Synchronized無法判斷獲取鎖的狀態,Lock可以判斷是否獲取到了鎖
3、Synchronized會自動釋放鎖,Lock鎖必須手動釋放鎖,如果不釋放鎖會死鎖
4、Synchronized 線程1獲得鎖,線程2等待,當線程1阻塞是,線程2一直等;Lock鎖不一定會等待下去
5、Synchronized可重入鎖,不可以中斷,非公平;Lock可重入鎖,可以判斷鎖,可以自己設置公平鎖和非公平鎖,默認非公平
6、Synchronized適合鎖少量的代碼同步問題,Lock適合鎖大量的同步代碼
鎖是什麼?如何判斷鎖的是什麼
生產者和消費者問題
Synchronized版本
/**
* @Description: 線程之間的通信問題 生產者和消費者問題
* 線程交替執行 A B操作同一個變量 num=0
* A num+1
* B num-1
* @Author: wangyinghao_sx
* @Date: 2020-05-23 17:46
**/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
// 判斷等待 業務 通知
class Data {
private int number = 0;
public synchronized void increment() throws InterruptedException {
//if (number != 0) {
//等待
//wait();
//}
// 如果用if 多個生產者或者消費者的時候會有問題
while (number != 0) {
//等待
wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他線程 我+1完畢
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
//if (number == 0) {
//等待
//wait();
//}
while (number == 0) {
//等待
wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他線程,我-1完畢
this.notifyAll();
}
}
問題存在:多個消費者 生產者時使用if導致的虛假喚醒的問題 所以將if改成while
可是爲什麼加了synchronized還會有兩個生產者都進入生產呢?原因是 當A生產之後,即當前number爲1,此時notifyAll,這個時候ABC都有機會被喚醒,假設C先被喚醒,之後if(number!=0) wait等待被喚醒,此時A再次醒來,A也if(number!=0) wait等待被喚醒,當B消費之後,number=0,但此時AC均已經判斷過number的值,所以如果接下來先喚醒A,那麼number+1,然後notifyAll喚醒了C,C再次number+1,所以會出現以下錯誤情況
A=>1
B=>0
A=>1
C=>2
A=>3
B=>2
B=>1
B=>0
改成while之後的正確結果
A=>1
B=>0
A=>1
B=>0
C=>1
B=>0
A=>1
B=>0
C=>1
JUC版生產者消費者
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
// 判斷等待 業務 通知
class Data {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
try {
lock.lock();
// 如果用if 多個生產者的時候就會有問題
while (number != 0) {
//等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他線程 我+1完畢
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
try {
lock.lock();
while (number == 0) {
//等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition 可以精準通知想喚醒的進程
public class C {
public static void main(String[] args) {
Data03 data = new Data03();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
data.printC();
}
}, "C").start();
}
}
// 判斷等待 業務 通知
class Data03 {
private int number = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
public void printA() {
try {
lock.lock();
while (number != 1) {
condition1.await();
}
System.out.println(Thread.currentThread().getName());
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
try {
lock.lock();
while (number != 2) {
condition2.await();
}
System.out.println(Thread.currentThread().getName());
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
try {
lock.lock();
while (number != 3) {
condition3.await();
}
System.out.println(Thread.currentThread().getName());
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
A
B
C
A
B
C
A
B
C
8鎖現象
如何判斷鎖的是誰
/**
* @Description: 8鎖 就是關於鎖的八個問題
* 1. 標準情況下 先發短信 後打電話
* 2. sendSms延遲四秒 先發短信 後打電話
* @Author: wangyinghao_sx
* @Date: 2020-05-23 20:48
**/
public class Test01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
//synchronized鎖的對象是方法的調用者
//兩個方法用的是同個鎖 誰先拿到誰先執行
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("發短信");
}
public synchronized void call() {
System.out.println("打電話");
}
}
/**
* @Description: 8鎖 就是關於鎖的八個問題
* 3. 增加一個普通方法 先hello 再發短信
* 4. 兩個對象 兩個同步方法 先打電話 再發短信 (發短信的sleep了)
* @Author: wangyinghao_sx
* @Date: 2020-05-23 20:48
**/
public class Test02 {
public static void main(String[] args) {
Phone02 phone1 = new Phone02();
Phone02 phone2 = new Phone02();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone02 {
//synchronized鎖的對象是方法的調用者
//兩個方法用的是同個鎖 誰先拿到誰先執行
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("發短信");
}
public synchronized void call() {
System.out.println("打電話");
}
public void hello(){
System.out.println("hello");
}
}
/**
* @Description: 8鎖 就是關於鎖的八個問題
* 5. 增加兩個靜態的同步方法 只有一個對象 先打印發短信 後打電話
* 6. 兩個對象,增加兩個靜態的同步方法 先發短信 後打電話
* @Author: wangyinghao_sx
* @Date: 2020-05-23 20:48
**/
public class Test03 {
public static void main(String[] args) {
Phone03 phone1 = new Phone03();
Phone03 phone2 = new Phone03();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone03 {
//synchronized鎖的對象是方法的調用者
//static 靜態方法
//類一加載就有了 鎖的是Class模版
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("發短信");
}
public static synchronized void call() {
System.out.println("打電話");
}
}
/**
* @Description: 8鎖 就是關於鎖的八個問題
* 5. 一個普通同步方法 一個靜態同步方法 只有一個對象 先打印打電話 後發短信
* 6. 兩個對象,一個普通同步方法 一個靜態同步方法 先打印打電話 後發短信
* @Author: wangyinghao_sx
* @Date: 2020-05-23 20:48
**/
public class Test04 {
public static void main(String[] args) {
Phone04 phone1 = new Phone04();
Phone04 phone2 = new Phone04();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone04 {
//synchronized鎖的對象是方法的調用者
//static 靜態方法
//類一加載就有了 鎖的是Class模版
public static synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("發短信");
}
//普通同步方法 鎖的是調用者
public synchronized void call() {
System.out.println("打電話");
}
}
List不安全
/**
* @Description: java.util.ConcurrentModificationException
* @Author: wangyinghao_sx
* @Date: 2020-05-24 10:17
**/
public class ListTest {
public static void main(String[] args) {
/**
* 併發下ArrayList不安全
* 解決方案:
* 1.List<String> list = new Vector<>();
* 2.List<String> list = Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list = new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 寫入時複製 COW 計算機程序設計領域的一種優化策略
// List<String> list = new CopyOnWriteArrayList<>();
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
Set 不安全
public class SetTest {
public static void main(String[] args) {
//1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
//2.Set<String> set = new CopyOnWriteArraySet<>();
Set<String> set = new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
hashSet底層是什麼?
public HashSet() {
map = new HashMap<>();
}
// add set本質就是map的key是無法重複的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Map不安全
public class MapTest {
public static void main(String[] args) {
// Map<String, String> map = new ConcurrentHashMap<>();
Map<String, String> map = new HashMap<>();
// 加載因子 static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 初始化容量 static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
for (int i = 0; i < 10; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
Callable
1、可以有返回值
2、可以拋出異常
3、和Runnable方法不同 run->call
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// new Thread(new MyThread()).start();
MyCallable callable = new MyCallable();
FutureTask futureTask = new FutureTask(callable);
new Thread(futureTask, "A").start();
new Thread(futureTask, "B").start(); //兩個Thread只返回了一個123 結果有緩存 效率高
String o = (String) futureTask.get(); //獲取Callable的返回結果 get可能產生阻塞
//把get放到最後 或者異步通信
System.out.println(o);
}
}
class MyThread implements Runnable {
@Override
public void run() {
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "123";
}
}
常用的輔助類
CountDownLatch 減法計數器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//總數是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
countDownLatch.await(); //等待計數器歸零 再向下繼續執行
System.out.println("OK");
}
}
CyclicBarrier 加法計數器
public class CyclicBarrierTest {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
System.out.println("OK");
});
for (int i = 0; i < 7; i++) {
// lambda能操作到for循環的i麼 不能 lambda本質是一個類 除非i設置爲final或jdk8的effecitvely final
int finalI = i; //jdk8的優化 默認應該是final int finalI = i
new Thread(() -> {
System.out.println(finalI);
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
Semaphore 信號量
public class SemaphoreDemo {
public static void main(String[] args) {
// 允許運行的線程數量
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(() -> {
// acquire 得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "搶到車位");
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + "離開車位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// release釋放
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
原理:
semaphore.acquire(); 獲得,假設如果已經滿了,等待
semaphore.release(); 釋放當前信號量,喚醒等待線程
讀寫鎖 ReadWriteLock
public class ReadWriteLockDemo {
public static void main(String[] args) {
// MyCache myCache = new MyCache();
// for (int i = 0; i < 10; i++) {
// int finalI = i;
// new Thread(() -> {
// myCache.put(finalI + "", finalI + "");
// }, String.valueOf(i)).start();
// }
//
// for (int i = 0; i < 10; i++) {
// int finalI = i;
// new Thread(() -> {
// myCache.get(finalI + "");
// }, String.valueOf(i)).start();
// }
MyCacheLock myCacheLock = new MyCacheLock();
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
myCacheLock.put(finalI + "", finalI + "");
}, String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
myCacheLock.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫入" + key + "完畢");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "讀取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取" + key + "完畢");
}
}
//加鎖的緩存
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//寫入的時候只希望同時一個線程寫 獨佔鎖
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫入" + key + "完畢");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
//讀取的時候所有人都可以讀 但是不能寫 共享鎖
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "讀取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取" + key + "完畢");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
阻塞隊列 BlockingQueue
寫入的時候:如果隊列滿了,必須阻塞等待
讀取的時候:如果隊列是空的,必須阻塞等待‘
隊列有四組API:添加 刪除 查看隊首元素
1、拋出異常 add() remove() element()
2、不拋出異常,有返回值 offer() poll() peek()
3、阻塞等待,一直等待 put() take()
4、超時等待 offer(timeout,TimeUtil) pool(time,TimeUtil)
public class Test {
public static void main(String[] args) {
}
// 拋出異常
public static void test1() {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.add("d"));
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
// 不拋出異常
public static void test2() {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
// 等待阻塞 一直等待
public static void test3() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
// 超時等待
public static void test4() throws InterruptedException {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d", 2, TimeUnit.SECONDS);
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2, TimeUnit.SECONDS));
}
}
SynchronousQueue 同步隊列
沒有容量,進去一個元素,必須等待取出來之後才能再放元素 put() take()
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
T1put 1
T2take 1
T1put 2
T2take 2
T1put 3
T2take 3
線程池
線程池好處:
- 降低資源的消耗
- 提高響應速度
- 方便管理
即 線程複用,可以控制最大併發數,管理線程
線程池:三大方法 七大參數 四種拒絕策略
三大方法
public class Demo01 {
public static void main(String[] args) {
ExecutorService pool = Executors.newSingleThreadExecutor();//單個線程
// ExecutorService pool = Executors.newFixedThreadPool(5); //固定線程池大小
// ExecutorService pool = Executors.newCachedThreadPool(); //可伸縮
try {
for (int i = 0; i < 10; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
七大參數
源碼分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 本質 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, 核心線程池大小
int maximumPoolSize, 最大線程池大小
long keepAliveTime, 存活時間,超時沒人調用會釋放
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory, 線程工廠 創建線程 一般不用動
RejectedExecutionHandler handler 拒絕策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
手動創建線程池
public class Demo01 {
public static void main(String[] args) {
//手動創建線程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(2,
5,
3, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//銀行滿了 還有人進來 不處理這個人並拋出異常
try {
//最大承載等於 maxPoolSize+linkedBlockingDeque.size
for (int i = 0; i <10; i++) {
pool.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
pool.shutdown();
}
}
}
四種拒絕策略
- AbortPolicy 銀行滿了還進來人 不處理這個 並拋出異常
- CallerRunsPolicy 拿來的回哪去 main會處理這個
- DiscardOldestPolicy 隊列滿了嘗試和最早的任務競爭 不會拋出異常
- DiscardPolicy 隊列滿了丟掉任務 不拋出異常
最大線程數應該怎麼定義:
- CPU密集型 幾核就定義幾 Runtime.getRuntime().availableProcessors()
- IO密集型 判斷程序中耗IO的線程個數 設置爲2倍
四大函數式接口
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
函數式接口簡化編程模型,在新版本的底層框架中大量應用
foreach(消費者類的函數式接口)
/**
* @Description: 函數式接口 有一個輸入參數 有一個輸出
* 函數式接口可以用lambda簡化
* @Date: 2020-05-25 13:04
**/
public class Demo01 {
public static void main(String[] args) {
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String str) {
return str;
}
};
function = (str) -> {
return str;
};
System.out.println(function.apply("test"));
}
}
/**
* @Description: 斷定型接口 有一個輸入參數 返回值只能是布爾值
* @Author: wangyinghao_sx
* @Date: 2020-05-25 13:13
**/
public class Demo02 {
public static void main(String[] args) {
Predicate<String> predicate = new Predicate<String>() {
//判斷字符串是否爲空
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
predicate = (s)->{
return s.isEmpty();
};
System.out.println(predicate.test(""));
}
}
/**
* @Description: 消費型接口 只有輸入 沒有返回值
* @Author: wangyinghao_sx
* @Date: 2020-05-25 13:37
**/
public class Demo03 {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer = (s)->{
System.out.println(s);
};
consumer.accept("test");
}
}
/**
* @Description: 供給型接口 沒有參數輸入 只有返回值
* @Date: 2020-05-25 13:41
**/
public class Demo04 {
public static void main(String[] args) {
Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
return 1024;
}
};
supplier = () -> {
return 1024;
};
System.out.println(supplier.get());
}
}
stream流式計算
什麼是流式計算
大數據:存儲+計算
集合:MySQL本質就是存儲
計算都應該交給流來操作
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(6, "e", 25);
//集合存儲
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//stream計算
list.stream().filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
分枝合併 ForkJoin
並行執行任務,提高效率,大數據量
ForkJoin特點:
工作竊取 若存在AB兩個線程同時處理多個任務,B線程先處理完所有任務,則B會幫A處理
原因:AB維護的是雙端隊列
/**
* @Description: 求和計算
* ForkJoin
* stream並行計算
* ForkJoin如何使用
* 1、forkjoinPool 通過它來執行
* 2、計算任務 forkjoinPool.execute(ForkJoinTask task)
* 3、計算類繼承ForkJoinTask
* @Author: wangyinghao_sx
* @Date: 2020-05-25 15:04
**/
public class Demo extends RecursiveTask<Long> {
public Long start;
public Long end;
public Long temp = 10000L;
public Demo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if (start - end < temp) {
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
//forkjoin
long mid = (start + end) / 2;
Demo task1 = new Demo(start, mid);
task1.fork(); //拆分任務 把任務壓入線程隊列
Demo task2 = new Demo(mid, end);
task2.fork();
return task1.join() + task2.join();
}
}
}
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
//7084
//5668
//202
}
//普通
public static void test1() {
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
//forkjoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new Demo(1L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
//stream並行流
public static void test3() {
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
異步回調
/**
* @Description: 異步調用
* @Author: wangyinghao_sx
* @Date: 2020-05-25 15:45
**/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//無返回值
CompletableFuture<Void> completableFuture1 = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName());
});
completableFuture1.get();
//有返回值
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName());
return 1024;
});
System.out.println(completableFuture2.whenComplete((t,u)->{
System.out.println(t); //正常的返回結果
System.out.println(u); //錯誤信息
}).exceptionally((e)->{
System.out.println(e.getMessage());
return 233;
}).get());
}
}
JMM
請你談談volatile的理解
volatile是java虛擬機提供的輕量級同步機制
1.保證可見性
2.不保證原子性
3.禁止指令重排
什麼是JMM java內存模型(不存在 是一個概念)
- 線程解鎖前,必須把共享變量立刻刷回主存
- 線程加鎖前,必須讀取主存中的最新值到工作內存中
- 加鎖和解鎖是同一把鎖
線程:工作內存 主內存
內存交互操作有8種,虛擬機實現必須保證每一個操作都是原子的,不可在分的(對於double和long類型的變量來說,load、store、read和write操作在某些平臺上允許例外)
lock (鎖定):作用於主內存的變量,把一個變量標識爲線程獨佔狀態
unlock (解鎖):作用於主內存的變量,它把一個處於鎖定狀態的變量釋放出來,釋放後的變量纔可以被其他線程鎖定
read (讀取):作用於主內存變量,它把一個變量的值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用
load (載入):作用於工作內存的變量,它把read操作從主存中變量放入工作內存中
use (使用):作用於工作內存中的變量,它把工作內存中的變量傳輸給執行引擎,每當虛擬機遇到一個需要使用到變量的值,就會使用到這個指令
assign (賦值):作用於工作內存中的變量,它把一個從執行引擎中接受到的值放入工作內存的變量副本中
store (存儲):作用於主內存中的變量,它把一個從工作內存中一個變量的值傳送到主內存中,以便後續的write使用
write (寫入):作用於主內存中的變量,它把store操作從工作內存中得到的變量的值放入主內存的變量中
JMM對這八種指令的使用,制定瞭如下規則:
不允許read和load、store和write操作之一單獨出現。即使用了read必須load,使用了store必須write
不允許線程丟棄他最近的assign操作,即工作變量的數據改變了之後,必須告知主存
不允許一個線程將沒有assign的數據從工作內存同步回主內存
一個新的變量必須在主內存中誕生,不允許工作內存直接使用一個未被初始化的變量。就是懟變量實施use、store操作之前,必須經過assign和load操作
一個變量同一時間只有一個線程能對其進行lock。多次lock後,必須執行相同次數的unlock才能解鎖
如果對一個變量進行lock操作,會清空所有工作內存中此變量的值,在執行引擎使用這個變量前,必須重新load或assign操作初始化變量的值
如果一個變量沒有被lock,就不能對其進行unlock操作。也不能unlock一個被其他線程鎖住的變量
對一個變量進行unlock操作之前,必須把此變量同步回主內存
volatile保證可見性
public class JMMDemo {
//線程A 死循環 不加volatile感知不到num被修改
private static int num = 0;
// 加volatile之後A能感知到main對num的修改 結束線程A
// private static volatile int num = 0;
public static void main(String[] args) {
new Thread(() -> {
while (num == 0) {
}
},"A).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
}
}
volatile不保證原子性
原子性:不可分割,要麼同時成功,要麼同時失敗
如果不加lock或synchronized怎麼保證原子性
使用原子類解決原子性問題
public class VDemo02 {
//AtomicInteger 原子類
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("OK" + num);
}
}
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
原子類的底層和操作系統掛鉤,在內存中修改值,Unsafe類是一個特殊的存在
指令重排
源代碼->編譯器優化重排->指令並行重排->內存系統重排->執行
volatile避免指令重排
內存屏障、CPU指令作用:
- 保證特定的操作的執行順序
- 可以保證某些變量的內存可見性
單例模式
餓漢式 DCL懶漢式
/**
* @Description: 餓漢式單例 耗內存 原因:啓動時就加載了 如果類中有耗費內存的對象就會浪費空間
* @Author: wangyinghao_sx
* @Date: 2020-05-26 13:39
**/
public class Hungry {
private Hungry() {
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance() {
return HUNGRY;
}
}
/**
* @Description: 餓漢式單例 單線程有效 多線程存在問題
* @Author: wangyinghao_sx
* @Date: 2020-05-26 13:44
**/
public class LazyMan {
private LazyMan(){
}
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if(lazyMan==null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
/**
* @Description: 餓漢式單例 多線程安全 但是不管怎麼處理 只要沒用枚舉 反射都能破壞單例
* @Author: wangyinghao_sx
* @Date: 2020-05-26 13:44
**/
public class SafeLazyMan {
private SafeLazyMan(){
//防止在反射的作用下不單例
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要使用反射試圖破壞單例");
}
}
}
// private static SafeLazyMan lazyMan;
// 因爲new SafeLazyMan()可能指令重排 所以爲了避免指令重排導致return null 加volatile
private volatile static SafeLazyMan lazyMan;
//雙重檢測鎖模式 DCL懶漢式
public static SafeLazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new SafeLazyMan(); // 有可能指令重排
/**
* 分配內存空間
* 執行構造方法 初始化對象
* 把這個對象指向這個空間
*/
}
}
}
return lazyMan;
}
//原本的單例模式在反射的作用下不再單例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// LazyMan lazyMan1 = LazyMan.getInstance();
// LazyMan lazyMan2 = LazyMan.getInstance();
// System.out.println(lazyMan1.hashCode()); //116211441
// System.out.println(lazyMan2.hashCode()); //116211441
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
LazyMan lazyMan3 = declaredConstructor.newInstance();
System.out.println(lazyMan3.hashCode()); //1625635731
LazyMan lazyMan4 = declaredConstructor.newInstance();
}
}
/**
* @Description: 靜態內部類
* @Author: wangyinghao_sx
* @Date: 2020-05-26 13:58
**/
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return InnerClass.HOLDER;
}
public static class InnerClass {
private static final Holder HOLDER = new Holder();
}
}
/**
* @Description: enum是什麼 本身也是一個Class 枚舉本身就是單例模式
* @Author: wangyinghao_sx
* @Date: 2020-05-26 15:03
**/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
}
}
深入瞭解CAS
什麼是CAS
比較當前工作內存中的值和主內存的值,如果這個值是期望的,那麼執行操作,如果不是就一直循環
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2000);
//public final boolean compareAndSet(int expect, int update)
//期望 更新
//如果期望的值達到了 就更新 否則不更新 CAS是CPU的併發原語
atomicInteger.compareAndSet(2000,2021);
System.out.println(atomicInteger.get());
}
}
自旋鎖
缺點:
- 循環會耗時(do while)
- 一次性只能保證一個共享變量的原子性
- ABA問題
Unsafe類—>自旋鎖
ABA問題:
引入原子引用
new AtomicStampedReference(100,1);
各種鎖的理解
1.公平鎖 非公平鎖
公平鎖:非常公平 先來後到
非公平鎖:可以插隊 默認非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
2.可重入鎖
可重入鎖(遞歸鎖)
/**
* @Description: A執行完sms發完短信之後 正常來說在進入call的時候應該釋放並重新獲取鎖 但由於可重入鎖 並沒有重新獲取鎖 所以B永遠都是在A執行完sms和call之後執行
* @Author: wangyinghao_sx
* @Date: 2020-05-26 17:12
**/
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
/**
* @Description:
* @Author: wangyinghao_sx
* @Date: 2020-05-26 17:12
**/
public class Demo02 {
public static void main(String[] args) {
Phone1 phone = new Phone1();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone1{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock(); //細節問題:sms的兩個鎖配對 lock1->lock2->lock2.unlock->lock1.unlock
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"sms");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
lock.unlock();//鎖數目需要配對
}
call();
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.自旋鎖
spinlock
public class SpinlockDemo {
AtomicReference atomicReference = new AtomicReference();
// 加鎖
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName());
while (!atomicReference.compareAndSet(null, thread)) {
}
}
// 解鎖
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName());
atomicReference.compareAndSet(thread, null);
}
}
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
//底層使用自旋鎖CAS
SpinlockDemo spinlockDemo = new SpinlockDemo();
new Thread(()->{
spinlockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}finally {
spinlockDemo.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
//T2必須等待T1解鎖之後才能解鎖
new Thread(()->{
spinlockDemo.myLock();
try {
}catch (Exception e){
e.printStackTrace();
}finally {
spinlockDemo.myUnLock();
}
},"T2").start();
}
}
死鎖解決
- 使用jps -l定位進程號
- 使用jstack +進程號 查看進程信息
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.wang.lock.MyThread.run(Test.java:39)
- waiting to lock <0x000000076ac27580> (a java.lang.String)
- locked <0x000000076ac275b0> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
"Thread-0":
at com.wang.lock.MyThread.run(Test.java:39)
- waiting to lock <0x000000076ac275b0> (a java.lang.String)
- locked <0x000000076ac27580> (a java.lang.String)
at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.
```