总结笔记,课程来自:https://www.bilibili.com/video/BV1B7411L7tE
前言
多线程进阶=》JUC并发编程
一、什么是JUC
JUC指的是:Java里的三个包
java.util.concurrent
java.util.concurrent.atomic:原子性
java.util.concurrent.locks:lock锁
二、线程和进程
1.进程
程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。
2.线程
可以指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。
3.并发
同一时刻,多个线程交替执行。(一个CPU交替执行线程)
4.并行
同一时刻,多个线程同时执行。(多个CPU同时执行多个线程)
# 获取cpu的核数(cpu密集型;io密集型)
System.out.println(Runtime.getRuntime().availableProcessors());
并发编程的本质
并发编程的本质是充分利用cpu资源。
问题:Java真的可以开启线程吗
Java创建Thread类调用start方法,底层是把线程放到一个组里面,然后调用一个本地方法start0;方法底层是C++;Java无法操作硬件。
三、多线程回顾
1.线程的几种状态
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
// 新生
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
// 运行
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
// 阻塞
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
// 等待,死死的等待
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
// 超时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
// 终止
TERMINATED;
}
2.sleep和wait的区别
- sleep是Thread类的本地方法;wait是Object类的方法。
- sleep不释放锁;wait释放锁。
- sleep不需要和synchronized关键字一起使用;wait必须和synchronized代码块一起使用。
- sleep不需要被唤醒(时间到了自动退出阻塞);wait需要被唤醒。
- sleep一般用于当前线程休眠,或者轮循暂停操作;wait则多用于多线程之间的通信。
四、Lock锁
1.传统的synchronized
不加Synchronized
package com.fang.demo0;
/**
* 真正的多线程开发
* 线程就是一个资源类,没有任何附属的操作
*/
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//Runnable接口为函数式接口
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"a").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"b").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}
},"c").start();
}
}
//资源类oop编程
class Ticket {
//属性,方法
private int number = 50;
//买票的方式
public void sale() {
if (number>0) {
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
}
加了锁后正常
public synchronized void sale() {
if (number>0) {
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}
2.公平锁和非公平锁(锁的底层)
公平锁:十分公平,不能插队。
非公平锁:十分不公平,可以插队。(默认非公平锁)
3.Lock锁
Lock锁是一个接口,他有三个实现类:
- ReentrantLock类
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock
4.Lock锁和synchronized的区别
- Synchronized是内置Java关键字;Lock是一个Java类。
- Synchronized无法判断获取锁的状态;Lock可以判断是否获取到了锁。(boolean b = lock.tryLock();)
- Synchronized会自动释放锁;Lock必须要手动释放锁,如果不释放锁,死锁。
- Synchronized线程1获得锁阻塞时,线程2会一直等待下去;Lock锁线程1获得锁阻塞时,线程2等待足够长的时间后中断等待,去做其他的事。
- Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。
lock.lockInterruptibly();方法:当两个线程同时通过该方法想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。 - Synchronized适合锁少量的代码同步问题;Lock适合锁大量的同步代码。
5.生产消费者
-
生产者和消费者问题:synchronized版
public class Demo04 { 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(); } } // 判断等待,业务,通知 class Data { private int i = 0; // +1 public synchronized void increment() throws InterruptedException { if (i != 0) { this.wait(); } i++; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程我+1完成 this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { if (i==0){ this.wait(); } i--; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程,我-1完毕 this.notifyAll(); } }
问题存在:A、B、C、D四个线程!虚假唤醒问题
if改成while解决虚假唤醒
public class Demo04 { 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(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } // 判断等待,业务,通知 class Data { private int i = 0; // +1 public synchronized void increment() throws InterruptedException { while (i != 0) { this.wait(); } i++; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程我+1完成 this.notifyAll(); } // -1 public synchronized void decrement() throws InterruptedException { while (i==0){ this.wait(); } i--; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程,我-1完毕 this.notifyAll(); } }
-
生产者和消费者问题:JUC版
public class Demo04 { 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(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D").start(); } } // 判断等待,业务,通知 class Data { private int i = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // +1 public void increment() throws InterruptedException { lock.lock(); try { while (i != 0) { condition.await(); } i++; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程我+1完成 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } // -1 public void decrement() throws InterruptedException { lock.lock(); try { while (i==0){ condition.await(); } i--; System.out.println(Thread.currentThread().getName() + "=>" + i); // 通知其他线程,我-1完毕 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
3.Condition实现精准通知唤醒
public class Demo05 {
public static void main(String[] args) {
Data01 data01 = new Data01();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data01.A();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data01.B();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data01.C();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C").start();
}
}
// 判断等待,业务,通知
//A执行完调用B,B执行完调用C,C执行完调用A
class Data01 {
private int num = 1;// 1A 2B 3C
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
public void A() throws InterruptedException {
lock.lock();
try {
// 业务代码,判断=>执行=>通知!
while (num!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAA");
num = 2;
// 唤醒指定的线程,B
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void B() throws InterruptedException {
lock.lock();
try {
while (num!=2){
condition2.await();
}
num = 3;
System.out.println(Thread.currentThread().getName()+"=>BBBBB");
// 唤醒指定的线程,C
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void C() throws InterruptedException {
lock.lock();
try {
while (num!=3){
condition3.await();
}
num = 1;
System.out.println(Thread.currentThread().getName()+"=>CCCCC");
// 唤醒指定的线程,A
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
五、8锁现象(就是关于锁的八个问题)
如何判断锁的是谁!永远的知道什么锁,锁到底锁的是谁!
六、集合类不安全
ArryList集合
多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常
public class Test11 {
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
strings.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(strings);
}).start();
}
}
}
解决方案:
- List list = new Vector<>();
- List strings = Collections.synchronizedList(new ArrayList<>());
- List strings = new CopyOnWriteArrayList<>();
概念:CopyOnWrite写入时复制,计算机程序设计语言的一种优化策略。(保证效率和性能问题)
HashSet集合
多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常
public class Test11 {
public static void main(String[] args) {
// Set<String> strings = Collections.synchronizedSet(new HashSet<>());
HashSet<String> strings = new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
strings.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(strings);
}).start();
}
}
}
解决方案:
- Set strings = Collections.synchronizedSet(new HashSet<>());
- Set strings = new CopyOnWriteArraySet<>();
hashset集合的底层是hashmap的key
HashMap集合
多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常
public class Test11 {
public static void main(String[] args) {
// 默认相当于
Map<String, String> map = new HashMap<>(16, 0.75F);
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
}).start();
}
}
}
解决方案:
- 使用Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
七、Callable接口
Callable接口类似于Runnable接口,线程第三种创建方式。
- 可以抛出异常。
- 可以有返回值。
- 方法不同与Runnable接口。Call方法
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask(new MyThread());// 适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();// 打印一个Call,结果会被缓存,提高效率
Integer s = (Integer) futureTask.get();// get方法可能会产生阻塞
System.out.println(s);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call(){
System.out.println("Call");
return 1024;
}
}
原理图:
八、常用辅助类(AbstractQueuedSynchronizer(AQS))
1.CountDownLatch
应用场景:1.多线程任务汇总。2.多线程任务阻塞住,等待发令枪响,一起执行。
每次有线程调用,数量-1,当计数器归零,countDownLatch.await()就会被唤醒向下执行。
// 计数器
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()+"=>Go Out");
countDownLatch.countDown();// 数量-1
}).start();
}
countDownLatch.await();// 等待计数器归零,然后再往下执行
System.out.println("关门");
}
}
打印:
2.CyclicBarrier([ˈsaɪklɪk]、[ˈbæriə®])
应用场景:比如LOL类游戏,满10人一组,开始游戏。
// 相当于加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 集齐七颗龙珠召唤神龙
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {// 如果计数器为7,线程只有6个,则会等待,不进行召唤神龙
System.out.println("召唤神龙");
});
for (int i = 0; i < 7; i++) {
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠!");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
打印:
3.Semaphore([ˈseməfɔː®])
Semaphore:信号量
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位!限流
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
new Thread(()->{
try {
semaphore.acquire();// 得到
System.out.println(Thread.currentThread().getName()+"抢到车位!");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位!");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();// 释放
}
}).start();
}
}
}
打印:
原理:
semaphore.acquire();获得,假设已经满了则等待,等待其他线程释放。
semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程。
九、读写锁
ReadWriteLock接口有一个实现类ReentrantReadWriteLock类。
读可以被多个线程同时读,写的时候只能有一个线程去写
/**
* 独占锁(写锁):一次只能被一个线程占有
* 共享锁(读锁):多个线程可以同时占有
* ReentrantLock:
* 读-读:可以共存
* 读-写:不可以共存
* 写-写:不可以共存
*/
public class ReentrantLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
// 5个线程写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 5个线程读
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
// 读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写,同时只有一个线程写
public void put(String key, Object obj) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入");
map.put(key, obj);
System.out.println(Thread.currentThread().getName() + "写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
// 读,所有线程都可以读
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取");
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
十、阻塞队列BlockingQueue
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞,一直等待 | 阻塞,超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(,,) |
移除 | remove() | pull() | take() | pull(,) |
检测队首元素 | element() | peek() | - | - |
public class Test {
public static void main(String[] args) throws InterruptedException {
test4();
}
// 抛出异常:java.lang.IllegalStateException: Queue full
public static void test1(){
// 队列的大小为3
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// add()方法返回boolean值
boolean flag1 = blockingQueue.add("a");
boolean flag2 = blockingQueue.add("b");
boolean flag3 = blockingQueue.add("c");
boolean flag4 = blockingQueue.add("d");// add添加元素超过队列的长度会抛出异常java.lang.IllegalStateException: Queue full
System.out.println(blockingQueue.element());// 获得队首元素
System.out.println("=========");
// remove()返回本次移除的元素
Object e1 = blockingQueue.remove();
Object e2 = blockingQueue.remove();
Object e3 = blockingQueue.remove();
Object e4 = blockingQueue.remove();// 队列中没有元素仍继续移除元素会抛出异常java.util.NoSuchElementException
}
// 有返回值,不抛出异常
public static void test2(){
// 队列的大小为3
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// offer返回boolean值
boolean flag1 = blockingQueue.offer("a");
boolean flag2 = blockingQueue.offer("b");
boolean flag3 = blockingQueue.offer("c");
//boolean flag4 = blockingQueue.offer("d");// offer添加元素超过队列的长度会返回false
System.out.println(blockingQueue.peek());// 获得队首元素
System.out.println("=========");
// poll()返回本次移除的元素
Object poll1 = blockingQueue.poll();
Object poll2 = blockingQueue.poll();
Object poll3 = blockingQueue.poll();
Object poll4 = blockingQueue.poll();// 队列中没有元素仍继续移除元素会打印出null
}
// 阻塞,一直等待
public static void test3() throws InterruptedException {
// 队列的大小为3
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// put没有返回值
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");// put添加元素超过队列的长度会一直等待
System.out.println("=========");
// take()返回本次移除的元素
Object take1 = blockingQueue.take();
Object take2 = blockingQueue.take();
Object take3 = blockingQueue.take();
Object take4 = blockingQueue.take();// 队列中没有元素仍继续移除元素会一直等待
}
// 阻塞,超时等待
public static void test4() throws InterruptedException {
// 队列的大小为3
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
// offer返回boolean值
boolean flag1 = blockingQueue.offer("a");
boolean flag2 = blockingQueue.offer("b");
boolean flag3 = blockingQueue.offer("c");
// offer添加元素超过队列的长度会返回false;并且等待指定时间后推出,向下执行
boolean flag4 = blockingQueue.offer("d", 2, TimeUnit.SECONDS);
System.out.println("=========");
// poll()返回本次移除的元素
Object poll1 = blockingQueue.poll();
Object poll2 = blockingQueue.poll();
Object poll3 = blockingQueue.poll();
// 队列中没有元素仍继续移除元素会打印出null,等待指定之间后退出。
Object poll4 = blockingQueue.poll(2,TimeUnit.SECONDS);
}
}
SynchronousQueue同步队列
进去一个元素,必须等待取出这个元素后,才能放下一个元素。put()、take()
十一、线程池
3大方法、7大参数、4大拒绝策略
池化技术及线程池的使用
程序的运行,本质:占用系统的资源!优化资源的使用
线程池,连接池,内存池,对象池
池化技术:事先准备好一些资源,有人要用就来拿,用完之后归还
线程池的好处
1.降低资源的消耗
2.提高响应速度
3.方便管理
线程可以复用,可以控制最大并发量,管理线程
线程池:三大方法,7大参数,4种拒绝策略
线程的三大方法
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:
1)newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
2)newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE(约为21亿),可能会创建数量非常多的线程,甚至OOM。
public static void main(String[] args) {
//Executors工具类,三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
for (int i = 0; i < 10; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
//线程池用完,程序结束,关闭线程池
try {
threadPool.shutdown();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
//Executors工具类,三大方法
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定大小得线程池
ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩,线程数可变
7大参数及自定义线程池
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue());
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue());
}
三大方法底层都调用得是ThreadPoolExecutor
三大方法底层都调用得是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;
}
手动创建一个线程池
————————————————
版权声明:本文为CSDN博主「想去22世纪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_48412846/article/details/115681611
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy(); // 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy();// 哪来的去哪(主线程来的,就回去让主线程执行)
new ThreadPoolExecutor.DiscardPolicy();// 丢掉任务,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy();// 尝试和最早的竞争,竞争失败了也丢掉任务,也不抛出异常
最大线程应该如何设置?
-
CPU密集型:最大线程数,CPU几核的就是几,可以保持CPU效率最高。
-
IO密集型:判断程序中十分耗IO的线程数量,大于这个数,一般是这个数的两倍。
程序 15个大型任务 io十分暂用资源
System.out.println(Runtime.getRuntime().availableProcessors());//获得cpu的核心数
十二、四大函数式接口
新时代程序员必须掌握:lambda表达式、链式编程、函数式接口、stream流式计算。
函数式接口:只有一个方法的接口。
Function函数型接口
有一个输入参数,有一个输出(返回值)。
public static void main(String[] args) {
// Function function = new Function<String,String>() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function function = (str)->{return str;};
System.out.println(function.apply("abc"));
}
predicate断定型接口
有一个输入参数,返回值只能是boolean值。
public static void main(String[] args) {
//判断字符串是否为空
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
Predicate<String> predicate = (str)- >{return str.isEmpty();
System.out.println(predicate.test(""));
}
consumer消费型接口
有一个输入参数,没有返回值。
supplier供给型接口
没有输入参数,有一个输出(返回值)。
十三、stream流式计算
存储+计算
存储:mysql,集合
计算都要交给流计算你
public class Test {
public static void main(String[] args) {
User user1 = new User(1,21,"张三");
User user2 = new User(2,23,"李四");
User user3 = new User(3,29,"王五");
User user4 = new User(4,18,"赵六");
//集合存储
List<User> userList = Arrays.asList(user1, user2, user3, user4);
//计算交给流
userList.stream().filter(user -> {return user.getId()%2==0;})
.filter(user -> {return user.getAge()>20;})
.map(user -> {return user.getName().toUpperCase(Locale.ROOT);})
.sorted((u1,u2)->{return u2.compareTo(u1); })
// .limit(1)//分页
.forEach(System.out::println);
}
}
十四、forkjoin详解
什么是ForkJoin
ForkJoin在JDk1.7,并行执行任务!提高效率,数据量大!
大数据:Map Reduce把大任务拆分为小任务.
特点:
特点工作窃取
十五、异步回调
Future的设计初衷:
public static void main(String[] args) throws ExecutionException, InterruptedException {
// //发起一个请求,没有返回值得异步回调
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"run");
// });
// System.out.println("1111");
// //获取阻塞执行结果
// completableFuture.get();
//有返回值的异步回调
//ajax,成功和失败回调
//返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println("completableFuture"+Thread.currentThread().getName());
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println(t);//正常的返回结果
System.out.println(u);//错误信息java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
System.out.println(e.getMessage());//java.lang.ArithmeticException: / by zero
return 233;
}).get());
}
十六、JMM
volatile的JVM提供的轻量级的同步机制。
- 可见性。(可见性和JMM挂钩)
- 不保证原子性。
- 禁止指令重排。
什么是JMM?
Java的内存模型,是一个概念,不存在。
JMM同步约定:
- 线程解锁前,必须把共享变量立即刷回主内存。
- 线程枷锁前,必须读取主内存中最新值到工作内存中。
- 加锁和解锁是同一把锁。
Java内存模型定义了8种操作来完成,虚拟机实现必须保证每一种操作都是原子的、不可再拆分的(double和long类型例外)。
- lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的write操作使用。
- write(写入):作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
8种操作必须满足的规则:
- 不允许read和load、store和write操作之一单独出现。即不允许一个变量从主内存读取了但工作内存不接受;或者从工作内存发起回写了但主内存不接受的情况出现。
- 不允许一个线程丢弃它的最近的assign操作。即变量在工作内存中改变了之后必须把该变化同步回主内存。
- 不允许一个线程无原因地(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存。
- 一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说,就是对一个变量实施use、store操作之前,必须先执行过了assign和load操作。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。
- 如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作初始化变量的值。
- 如果一个变量事先没有被lock操作锁定,那就不允许对它执行unlock操作,也不允许去unlock一个被其他线程锁定住的变量。
- 对一个变量执行unlock操作之前,必须先把此变量同步回主内存中(执行store、write操作)。
指令重排
你写的程序,计算机并不一定你写的去执行。
源代码->编译器优化的重排->指令并行也可能会重排->内存系统也会重排->执行
处理器在进行指令重排时,考虑数据之间的依赖性。
volatile避免指令重排:
内存屏障(CPU的指令)。
- 保证特定操作的执行顺序。
- 可以保证某些变量的内存可见性。
十七、单例模式
https://blog.csdn.net/aijson_update/article/details/126935677
十八、深入理解CAS
什么是CAS
比较并交换:compare and swap !
比较当前内存中的值和主内存中的值,如果主内存中的值是期望的,就执行操作,否则不执行操作。
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望,更新:public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了(2020)就更新,否则不更新。
System.out.println(atomicInteger.compareAndSet(2020, 2021));// true
System.out.println(atomicInteger.get());// 2021
System.out.println(atomicInteger.compareAndSet(2020, 2021));// false
System.out.println(atomicInteger.get());// 2021
}
}
Unsafe类
CAS的缺点
- 循环会耗时。
- 一次性只能保证一个共享变量的原子性。
- 存在ABA问题。
原子引用解决ABA问题
(乐观锁)版本号+1
十九、各种锁的理解
公平锁、非公平锁
可重入锁
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。(加锁的方法可以相互调用)
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() + "发短信");
call();// 这个方法也有锁
}
private synchronized void call() {
System.out.println(Thread.currentThread().getName() + "打电话");
}
}
打印:
自旋锁
不断尝试,直到成功。
死锁
死锁:线程A持有锁A,想要获得锁B;线程B持有锁B,想要获得锁A。
解决方法:查看堆栈信息
- 使用jps -l命令查看进程号。(该命令在JDK的bin目录下)
- 使用jstack+进程号,找到死锁问题。
总结
System.out.println(atomicInteger.get());// 2021
System.out.println(atomicInteger.compareAndSet(2020, 2021));// false
System.out.println(atomicInteger.get());// 2021
}
}
### Unsafe类
[外链图片转存中...(img-c8jMqUYD-1663645289353)]
[外链图片转存中...(img-f3JwbTRR-1663645289354)]
[外链图片转存中...(img-3lDAXWSp-1663645289354)]
### CAS的缺点
1. 循环会耗时。
2. 一次性只能保证一个共享变量的原子性。
3. 存在ABA问题。
### 原子引用解决ABA问题
(乐观锁)版本号+1
## 十九、各种锁的理解
### 公平锁、非公平锁
### 可重入锁
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。(加锁的方法可以相互调用)
```java
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() + "发短信");
call();// 这个方法也有锁
}
private synchronized void call() {
System.out.println(Thread.currentThread().getName() + "打电话");
}
}
打印:
[外链图片转存中…(img-4bUpYaxr-1663645289354)]
自旋锁
不断尝试,直到成功。
[外链图片转存中…(img-toymqQYN-1663645289355)]
死锁
死锁:线程A持有锁A,想要获得锁B;线程B持有锁B,想要获得锁A。
解决方法:查看堆栈信息
- 使用jps -l命令查看进程号。(该命令在JDK的bin目录下)
- 使用jstack+进程号,找到死锁问题。
总结
面试的:单例模式、排序算法、生产者和消费者、死锁