JUC 并发编程
JUC = java.until.concurrent
普通的线程代码:Thread
Runable 没有返回值、效率相对 Callable 较低
基本概念
进程:一个程序,QQ.exe、Music.exe 等程序的集合
一个进程可以包含多个线程,至少包含一个
线程:
一个进程中的各个功能是由线程负责实现的。如:自动保存,写字等
java 默认有两个线程,main 和 GC
对于java 而言:Thread、Runable、Callable
// Thread.State;
// 线程状态种类:6种
public enum State {
NEW, // 新生
RUNNABLE, // 运行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 超时等待
TERMINATED; // 终止
}
Java 真的可以开启线程吗?
开不了!
// 通过 new Thread().start() ctrl 进入 start() 方法源码
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(); // 通过 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(); // start0 为本地方法(native)底层为 c++
并发:多线程操作同一个资源
- cpu 一核,模拟出多条线程,快速交替执行
充分利用 CPU 资源
并行:多个线程同时执行
- cpu 多核,多个线程可以同时执行,线程池
public static void main(String[] args) {
// 获取 CPU 核数
System.out.println(Runtime.getRuntime().availableProcessors());
}
线程和进程
wait 和 sleep 的区别:
-
类型区别
wait => Object
sleep => Thread
-
释放区别
wait 会释放锁
sleep 不会释放
-
使用范围
wait 只能在同步代码块中
sleep 可以在任何地方使用
java 线程实现方式:
import sun.awt.windows.ThemeReader;
/**
* 真正的线程开发
* 线程就是一个单独的资源类,没有任何附属操作
*/
public class SaleTickDemo1 {
public static void main(String[] args) {
// 并发多线程操作同一个类, 把资源丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 匿名内部类的方式实现,函数式接口
new Thread(new Runnable() {
@Override
public void run() {
for (int i=1;i<10;i++){
ticket.sale();
}
}
},"A").start();
// lambda 表达式简化代码
new Thread(()->{
for (int i=1;i<10;i++){
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i=1;i<10;i++){
ticket.sale();
}
},"C").start();
}
}
class Ticket{
private int number = 50;
public void sale(){
if (number > 0){
number--;
System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
}
}
}
// 输出可能会乱序
Lock 锁
synchronized
class Ticket{
private int number = 50;
// synchronized 本质:队列,锁
public synchronized void sale(){
if (number > 0){
number--;
System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
}
}
}
Lock
接口,所有已知实现类:
- ReentrantLock 可重入锁(常用)
- ReentrantReadWriteLock.ReadLock() 可重入读写锁,读锁
- ReentrantReadWriteLock.WriteLock() 可重入读写锁,写锁
// 创建锁
Lock lock = new ReentrantLock();
================ReentrantLock 构造方法=======================
public ReentrantLock() {
// 默认为非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 公平锁和非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
=============================================================
class Ticket2{
private int number = 50;
Lock lock = new ReentrantLock();
public void sale(){
==========================官方文档加锁固定代码===========================
lock.lock(); // 枷锁
try {
// 业务代码
if (number > 0){
number--;
System.out.println(Thread.currentThread().getName()+"卖出了"+1+"票,剩余:"+number);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); // 解锁
}
=========================================================================
}
}
公平锁:先来后到,排队进行
非公平锁:可以插队
synchronized 和 Lock 区别:
Lock 需要三步:新建锁,加锁,解锁
synchronized 自动完成
- synchronized 是java 内置关键字,Lock 是一个java 类
- synchronized 无法获取锁的状态,Lock 可以判断是否获取到了锁
- synchronized 或自动释放锁,Lock 必须手动释放,如果不释放,会造成死锁
- synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等);Lock 锁可以通过 tryLock() 方法尝试获取锁
- synchronized 默认为可重入锁,不可以中断,非公平锁;Lock 可重入锁,可以判断锁,非公平/公平(可以自己选择)
- synchronized 适合锁少量的代码同步问题,Lock 灵活度高,适合锁大量代码同步问题
锁是什么,如何判断锁的是谁?
生产者和消费者
package PC;
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒和通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Test1 {
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 number = 0;
public synchronized void inCrement() throws InterruptedException {
if (number!=0){ // 只有为零时工作
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
// 通知其他线程,我+1完了
this.notifyAll();
}
public synchronized void deCrement() throws InterruptedException {
if (number == 0){ // 只有不为零时工作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
// 通知其他线程,我-1完了
this.notifyAll();
}
}
**问题:**大于两个线程出现虚假唤醒问题(多个线程跳过等待过程直接执行),解决方法:if 改成 while 循环
package PC;
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒和通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Test1 {
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 number = 0;
public synchronized void inCrement() throws InterruptedException {
while (number!=0){ // 只有为零时工作
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"->"+number);
// 通知其他线程,我+1完了
this.notifyAll();
}
public synchronized void deCrement() throws InterruptedException {
while (number == 0){ // 只有不为零时工作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
// 通知其他线程,我-1完了
this.notifyAll();
}
}
JUC 中生产者和消费者
在 synchronized 中使用 wait、notify
在 Lock 中使用 condition:await、signal
Lock 取代了 synchronized 方法和语句的使用,Conditon 取代了对象监视器方法的使用
package PC;
import javax.swing.*;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒和通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Test2 {
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 Data2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void inCrement() throws InterruptedException {
lock.lock();
try {
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 {
lock.lock();
try {
while (number == 0){ // 只有不为零时工作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"->"+number);
// 通知其他线程,我-1完了
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition 精准通知
线程 A 完成工作通知 B,B 完成工作通知 C,C 完成工作通知 A
package PC;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题:生产者和消费者问题!等待唤醒和通知唤醒
* A 执行完通知 B 执行完通知 C
*/
public class Test3 {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();
}
}
// 资源类
class Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
lock.lock();
try {
while (number != 1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"->B");
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number != 2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"->C");
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number != 3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"->A");
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8 锁现象
为什么总是先执行 sendMsg() 方法:
package Lock8;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}).start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendMsg(){
// 加上延时仍然先执行 sendMsg
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (Exception e) {
// e.printStackTrace();
// }
System.out.println("sendMsg");
}
public synchronized void call(){
System.out.println("call");
}
}
错误回答:顺序,先写的代码先执行
synchronized 锁的对象时方法的调用者(这里是phone)!!两个方法用的是同一个锁,谁先拿到谁先执行。
// B 线程调用普通方法,先执行 hello,因为没有锁
package Lock8;
import java.util.concurrent.TimeUnit;
/**
* 增加一个普通方法 hello
* 先执行 hello 还是 sendMsg ?
*/
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendMsg();
}).start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
}).start();
}
}
class Phone2{
public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call(){
System.out.println("call");
}
public void hello(){
System.out.println("hello");
}
}
添加 static 关键字:
package Lock8;
import java.util.concurrent.TimeUnit;
/**
* 增加两个静态同步方法
*/
public class Test3 {
public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
// 由于 static 方法,即使是两个对象,锁的对象也只有一个(class)。所以先输出 sendMsg
new Thread(()->{
phone.sendMsg();
}).start();
// 捕获
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}).start();
}
}
class Phone3{
// static 静态方法,类一加载就有了!锁的对象是 Class,Class 只有一个
public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call(){
System.out.println("call");
}
public void hello(){
System.out.println("hello");
}
}
集合类不安全
List 不安全
public class ListTest1 {
public static void main(String[] args) {
// 并发下 ArrayList 不安全
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,7));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出报错:
Exception in thread “4” java.util.ConcurrentModificationException
并发修改异常
解决方案:
List list = new ArrayList<>(); => List list = new Vector<>();
Vector 是线程安全的,Vector 的方法由 synchronized 修饰,但 Vector 的出现时间不 ArrayList 要早,说明 ArrayList 有一定优势!
List list = Collections.synchronizedList(new ArrayList<>());
Collections 是一个集合类,里面包含很多线程安全的方法(List,Set,Map)
List list = new CopyOnWriteArrayList<>();
CopyOnWrite 写入时复制:简称 COW,计算机程序设计领域的一种优化方案
多个线程调用的时候 ,读取的时候 list 固定的,写入的时候复制一份,在完成写入后插入数据。避免脏数据产生造成的问题。
CopyOnWrite 对比 Vector :
Vector 读写操作都用了 synchronized 修饰,效率较低。
CopyOnWrite 只在写入时加锁 lock,效率较高。
Set 不安全
List 和 Set 没有本质区别,都是 Collection 的子类
public class SetTest {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,7));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
输出报错:
Exception in thread “7” java.util.ConcurrentModificationException
并发修改异常
解决方案:
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set set = new CopyOnWriteArraySet<>();
**HashSet 底层:**HashMap
public HashSet() {
map = new HashMap<>();
}
// add 方法
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();
使用 map 的 key 不能重复,对应的 value 一个不变常量
HashMap 不安全
源码:HashMap 初始化常量
static final int MAXIMUM_CAPACITY = 1 << 30; // 最大存储容量,位运算
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 默认加载因子,表示容量超过 75% 时自动扩容
==============================================================
public class MapTest {
public static void main(String[] args) {
// hashmap 是这样用的吗,等价于什么
// Map<String, String> map = new HashMap<>(16, 0.75);
Map<String, String> map = new HashMap<>();
// 加载因子,初始化容量
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();
}
}
}
输出报错:
Exception in thread “1” java.util.ConcurrentModificationException
解决方案:
Map<String, String> map = new ConcurrentHashMap<>();
ConcurrentHashMap 是 HashTable 的替代
Callable
- 可以有返回值
- 可以抛出异常
- 方法不同,Runable 是 run 方法,Callable 是 call 方法
public interface Callable<V> {
V call() throws Exception; // 泛型为返回值类型
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start(); // 怎么启动 Callable?
// new Thread(new Runnable() {}).start();
// new Thread(new FutureTask<>( Callable)).start();
// 所以可以通过 FutureTask 启动 Callable
MyThread myThread = new MyThread();
// 适配类
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "A").start();
// 获取返回值:
String res = (String) futureTask.get(); // 可能会产生阻塞,可以放在代码最后,或者异步操作
System.out.println(res); // 只打印一次
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call");
return "123";
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPerK8cN-1613985012029)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1613984866374-13c18640-dfc2-42fb-8cf3-da50929c3ada.jpeg)]
常用辅助类
CountDownLatch
线程减法计数器,当线程达到一定数量时,唤醒程序
保证指定线程任务执行完成
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数为 6
CountDownLatch countDownLatch = new CountDownLatch(6); // 输入线程数
/*
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
*/
for (int i = 0; i < 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go out");
countDownLatch.countDown(); // 数量减一
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后向下执行
// 如果不用 await() 方法,close door 可能会先打印
System.out.println("close door");
}
}
每次有线程调用 countDownLatch.countDown()
计数器减一,当计数器为 0 时,countDownLatch.await()
被唤醒,继续向下执行
CyclicBarrier
线程加法计数器,当线程达到一定数量时,触发指定线程工作
public class CyclicBarrireDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("集齐 7 颗龙珠,正在召唤神龙");
});
for (int i = 1; i <= 7; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"搜集"+temp+"颗龙珠");
try {
cyclicBarrier.await(); // 等待 7个线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
信号量,指定资源数量(线程数量),只有当有资源时才能执行
public class SemaphoreTest {
public static void main(String[] args) {
// 资源(信号量)数量
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire(); // 获取资源
Random random = new Random();
System.out.println(Thread.currentThread().getName()+"获得资源");
TimeUnit.SECONDS.sleep(random.nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release(); // 释放资源
System.out.println(Thread.currentThread().getName()+"释放资源");
}
}, String.valueOf(i)).start();
}
}
}
// 输出:
1获得资源
2获得资源
3获得资源
1释放资源
4获得资源
2释放资源
6获得资源
3释放资源
5获得资源
5释放资源
4释放资源
6释放资源
读写锁
public class ReadWriteDemo {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
// 写入操作
for (int i = 0; i < 5; i++) {
// lambada 表达式里无法访问外部变量
final int temp = i;
new Thread(()->{
myCache.put(temp+"", temp+"");
}, String.valueOf(i)).start();
}
// 读取操作
for (int i = 5; i < 10; i++) {
// lambada 表达式里无法访问外部变量
final int temp = i;
new Thread(()->{
myCache.get(temp+"");
}, 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()+"写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取");
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取完成");
}
}
/**
* 自定义缓存,加锁
*/
class MyCache2{
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()+"写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写入完成");
} 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()+"读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
写锁(独占锁)只能一个线程独自占用
读锁(共享锁)多个线程可以同时占用
读写共存关系:
- 读-读:可以共存
- 读-写:不可以共存
- 写-写:不可以共存
阻塞队列
队列:先进先出
- 写:如果队列满了,会阻塞
- 读:如果队列空了,会阻塞
线程池和多线程并发情况下常用,BlockingQueue
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V1h6RgHY-1613985012031)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1613984865833-2d9f0a90-73f4-49d5-9985-9715191abae6.jpeg)]
四组 API
方式 | 抛出异常 | 不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(…) |
移除 | remove | poll | take | poll(…) |
判断队列首 | element | peek |
public class Test {
public static void main(String[] args) {
test1();
}
/**
* 抛出异常
*/
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.element()); // 返回首元素
// 队列满时抛出异常,Queue full
// System.out.println(blockingQueue.add("d"));
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.peek()); // 返回首元素
// 不抛异常,返回 false
// System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
// 不抛异常,返回 null
// 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());
}
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);
blockingQueue.poll();
blockingQueue.poll();
blockingQueue.poll();
// 等待两秒 超时退出
blockingQueue.poll(2, TimeUnit.SECONDS);
}
}
同步队列
SynchronousQueue
/**
* 同步队列
* 和其他 BlockingQueue 不同,SynchronousQueue 不存储元素
* put 了一个元素,必须从队列里先 take 出来,否者无法再 put 进去
* 用 offer 无法取值
*/
public class SyncQueueTest {
public static void main(String[] args) {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
线程池
池化技术:程序的运行,本质为占用系统资源,为了优化资源的使用,有了池化技术
线程池,连接池,内存池,对象池
池化技术:事先准备好资源,有人要用,就来我这里那,用完后还
默认大小 2
优点:
- 降低资源消耗
- 提高响应速度(不用销毁和创建)
- 方便管理
- 线程复用,可以控制最大并发数
线程:
三大方法
public class Demo1 {
public static void main(String[] args) {
// 单一线程池
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 指定线程数量
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 可伸缩线程池(如果循环十次,则最多可以有十个线程)
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.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;
}
阿里云文档:线程池不允许使用 Excutors 去创建,而是通过 ThreadPoolExecutor 的方式,这样吃力的方式让写的同学更加明确线程池运行规则,避免耗尽资源的风险。
手动创建线程池
拒绝策略(最大线程数量和阻塞队列都满了时触发)
- AbortPolicy (默认)
不处理,直接抛出异常 java.util.concurrent.RejectedExecutionException
- CallerRunsPolicy
哪来的去哪里,如果是main 线程中出现的,就有 main 线程执行
- DiscardPolicy
丢弃任务,不抛出异常
- DiscardOldestPolicy
尝试与第一个队列竞争,不会抛出异常
public class Demo1 {
public static void main(String[] args) {
// 自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
// 最大承载 LinkedBlockingDeque + MaxPoolSize
for (int i = 0; i < 9; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); // 关闭线程池
}
}
}
最大线程如何定义?
- CPU 密集型 如果 CPU 为 4 核 8 线程,则最高线程数为 8 线程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EyUZJpX-1613985012033)(https://cdn.nlark.com/yuque/0/2021/png/12539997/1613982697889-af99784c-2d43-4445-952f-bc8ea76b0a6b.png)]
Runtime.getRuntime().availableProcessors()
可获取最大线程数,不应该写死,防止运维时服务器性能不够时出错。
- IO 密集型 判断程序中十分耗 IO 的线程数量,留出相应空间
四大函数式接口
lambda 表达式,链式编程,函数式接口,Stream 流计算
函数式接口:只有一个方法的接口(java 中非常多)
可以简化编程模型,可以用 lambda 简化代码
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Function
public class Demo01 {
public static void main(String[] args) {
// Function<String, String> f = new Function<String, String>() {
// @Override
// public String apply(String s) {
// return s;
// }
// };
// 简化后
Function<String, String> f = (str)->{return str;};
f.apply("123");
/* 源码
public interface Function<T, R> {
T 为传入类型,R 为返回类型
R apply(T t);
}*/
}
}
Predicate
/**
* 断定式函数:有一个输入参数,返回值只能是 布尔值
*/
public class Demo02 {
public static void main(String[] args) {
// Predicate<String> predicate = new Predicate<String>(){
// @Override
// public boolean test(String s) {
// return false;
// }
// };
// 简化
Predicate<String> predicate = (str)->{return false;};
predicate.test("123");
}
}
Consumer
/**
* 消费式函数接口:只有输入没有输出返回
*/
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<String> consumer = (str)->{
System.out.println("s");
};
consumer.accept("123");
}
}
Supplier
/**
* 供给型接口:没有参数输入,只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier<String> supplier = new Supplier<String>() {
// @Override
// public String get() {
// return "1231";
// }
// };
Supplier<String> supplier = ()->{return "123";};
supplier.get();
}
}
Stream 流式计算
大数据:存储 + 计算
计算都应该交给流来操作!
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现
* 现在有 5 个用户,筛选
* 1. ID 必须是偶数
* 2. 年龄大于 23 岁
* 3. 用户名转为大写字母
* 4. 用户名字母倒着排序
* 5. 只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User user1 = new User(1, "a", 21);
User user2 = new User(2, "b", 22);
User user3 = new User(3, "c", 23);
User user4 = new User(4, "d", 24);
User user5 = new User(6, "e", 25);
// 集合存储数据
List<User> list = Arrays.asList(user1, user2, user3, user4, user5);
// 流计算,链式编程
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((u1,u2)->{return u2.compareTo(u1);})
.limit(1)
.forEach(System.out::println);
}
}
ForkJoin
并行执行任务!提高效率,大数据量
将大任务拆分成小任务,最后将小任务结果和并
特点:工作窃取
里面是双端队列,当一个线程执行完全部任务时,会执行别的线程还未执行的任务
举例:大数计算:
public class Forkjoin extends RecursiveTask<Long> {
private Long start;
private Long end;
// 临界值
private Long temp = 10000L;
public Forkjoin(Long start, Long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if ((end-start)<temp){
long sum = 0L;
for(long i = start;i<=end;i++){
sum += i;
}
return sum;
}else {
// 分支合并计算
long mid = (start + end)/2; // 取中间值
Forkjoin forkjoin = new Forkjoin(start, mid);
forkjoin.fork(); // 将任务压入线程队列
Forkjoin forkjoin1 = new Forkjoin(mid + 1, end);
forkjoin1.fork();
return forkjoin.join() + forkjoin1.join();
}
}
}
public class TestFork {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();
test2();
test3();
}
// 普通计算
public static void test1(){
long sum = 0L;
long start = System.currentTimeMillis();
for (long i = 1L; i <=100_0000_0000 ; i++) {
sum += i;
}
long end = System.currentTimeMillis();
System.out.println("sum="+sum+",时间:"+(end-start));
}
// Forkjoin 计算
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new Forkjoin(0L, 100_0000_0000L);
// submit 有返回值,execute 没有返回值
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("sum="+sum+",时间:"+(end-start));
}
// 并行流计算
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.range(0L, 100_0000_0001L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+sum+",时间:"+(end-start));
}
}
输出:
sum=-5340232216128654848,时间:5069
sum=-5340232216128654848,时间:1598
sum=-5340232216128654848,时间:1255
异步回调
Future 设计初衷:对将来的某个操作的结果进行建模
一般用 CompletableFuture
/**
* 异步执行请求:
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 没有返回值的调用
// CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
// System.out.println("11111");
// });
// completableFuture.get();
// 有返回值的调用
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync->Integer");
int i = 10/0;
return 1024;
});
completableFuture.whenComplete((t, u)->{ // 成功返回
System.out.println("t->"+t); // 正常的返回结果
System.out.println("u->"+u); // 错误的返回信息,没有错误为 null
}).exceptionally((e)->{ // 错误返回
System.out.println(e.getMessage());
return 0;
});
}
}
JMM
JMM 是 Java 内存模型,不存在的东西,是一个概念,约定
关于 JMM 的一些约定:
- 线程解锁前,必须把共享变量立刻更新到主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXqKz1Sk-1614071229152)(https://cdn.nlark.com/yuque/0/2021/jpeg/12539997/1614060189370-e4153ec0-6c3b-4b29-94af-af01dec69831.jpeg)]
8 种操作
public class Demo01 {
private static int num = 0;
public static void main(String[] args) {
new Thread(()->{
while (num == 0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
System.out.println(num);
// 修改后线程并没有停止, 线程并不知道主线程的值已经修改!!!
}
}
解决方案:
private static ``volatile`` int num = 0;
Volatile
Volatile 是 java 虚拟机提供的 轻量级同步机制
- 保证可见性
如上述代码所示,volatile 可以保证变量在线程中的可见性,修改后立马让各线程知道
- 不保证原子性
线程 A 在执行时不能被打扰,也不能被分割,要么同时成功,要么同时失败
public class Demo2 {
// private volatile static int num = 0;
private volatile static AtomicInteger num = new AtomicInteger();
private static void add(){
//num++; // 不是原子性操作
num.getAndIncrement(); // 通过 CAS 操作加一
}
public static void main(String[] args) {
for (int i = 1; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while(Thread.activeCount()>2){ // 如果线程数量大于2,就让线程礼让
// 基本上出不来循环,因为 java 默认两个线程 main 和 gcc
Thread.yield();
/**
* Thread.yield( )方法:
* 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
*
* Java线程中有一个Thread.yield( )方法,很多人翻译成线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。
*
* 打个比方:现在有很多人在排队上厕所,好不容易轮到这个人上厕所了,突然这个人说:“我要和大家来个竞赛,看谁先抢到厕所!”,然后所有的人在同一起跑线冲向厕所,有可能是别人抢到了,也有可能他自己有抢到了。我们还知道线程有个优先级的问题,那么手里有优先权的这些人就一定能抢到厕所的位置吗? 不一定的,他们只是概率上大些,也有可能没特权的抢到了。
*/
}
System.out.println(Thread.currentThread().getName()+" "+ num);
}
}
// 输出不一定是理论值 20000
可以通过加锁 或者 synchronized 解决
如何不通过锁操作解决这个问题?
通过原子类:
private volatile static AtomicInteger num = new AtomicInteger();
直接在内存中修改值。unsafe 这个类是一个很特殊的存在
- 禁止指令重排
什么是指令重排?
计算机并不是按照你写的程序那样去执行的。
源代码->编译器优化重排->指令并行可能重排->内存系统坑你重排->执行
int x = 1; //1
int x = 2; //2
x = x + 5; //3
y = x + x; //4
期望执行顺序:1234
可能执行顺序:2134 1324
不可能执行顺序:4123
volatile 避免指令重排:
内存屏障、CPU指令、作用:
- 保证特定的操作执行顺序
- 可以保证某些变量的内存可见性(利用这些实现了可见性)
深入理解 CAS
什么是 CAS
CAS compare and swap 比较并交换
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BF4w1jFL-1614071229157)(https://cdn.nlark.com/yuque/0/2021/png/12539997/1614067693337-9beaf4f9-7767-4062-9639-8a561fce3e77.png)]
CAS 比较当前工作内存中的值和主存中的值,如果这个值是期望的,则执行,如果不是就一直循环(自旋锁)
缺点:
- 循环耗时
- 一次性只能保证过一个共享变量的原子性
- ABA 问题
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
/*
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}*/
// 如果我期望的值达到了(2020)就更新(2021),否者不更新
// CAS 是 CPU 并发原语
// -------------------捣乱的线程------------------------------
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021,2020));
System.out.println(atomicInteger.get());
// -------------------期望的线程------------------------------
System.out.println(atomicInteger.compareAndSet(2020,6666));
System.out.println(atomicInteger.get());
}
}
线程 A 修改了资源值后又修改了回去,对线程 B 来说并不知道。希望能让操作透明怎么办?
原子引用
带版本号的原子操作(类似乐观锁)
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间。
解决 ABA 问题
public class CASDemo {
public static void main(String[] args) {
// 正常 Integer 应该是一个对象
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(123, 1);
new Thread(()->{
// 获取版本号
int stamp = atomicInteger.getStamp();
System.out.println("a->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(123,6,
atomicInteger.getStamp(),atomicInteger.getStamp()+1);
System.out.println("a2->"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(6,123,
atomicInteger.getStamp(),atomicInteger.getStamp()+1));
System.out.println("a3->"+atomicInteger.getStamp());
}, "A").start();
new Thread(()->{
// 获取版本号
int stamp = atomicInteger.getStamp();
System.out.println("b->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(123,3,
atomicInteger.getStamp(),atomicInteger.getStamp()+1));
System.out.println("b2->"+atomicInteger.getStamp());
}, "B").start();
}
}