1、什么是JUC
JUC指的是:Java里的三个包
java.util.concurrent
java.util.concurrent.atomic:原子性
java.util.concurrent.locks:lock锁
2、线程和进程
进程:程序执行的一次过程,一个进程包含一个或多个线程。进程是资源分配的单位。
线程:可以指程序执行过程中,负责实现某个功能的单位。线程是CPU调度和执行的单位。
并发和并行
并发:同一时刻,多个线程交替执行。(一个CPU交替执行线程)
并行:同一时刻,多个线程同时执行。
# 获取cpu的核数(cpu密集型;io密集型)
System.out.println(Runtime.getRuntime().availableProcessors());
线程的状态:
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死的等待
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
wait/sleep
1.来自不同的类
wait——Object
sleep——Thread
2.关于锁的释放
wait会、sleep不会
3.使用范围
wait必须在同步代码块中
4.sleep不需要被唤醒(时间到了自动退出阻塞);wait需要被唤醒
3、锁
公平锁和非公平锁(锁的底层)
公平锁:十分公平,不能插队。
非公平锁:十分不公平,可以插队。(默认非公平锁)
Lock锁
Lock锁是一个接口,他有三个实现类:
ReentrantLock类
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
Lock锁 & synchronized
1、Synchronized是内置Java关键字;Lock是一个Java类。
2、Synchronized无法判断获取锁的状态;Lock可以判断是否获取到了锁。(boolean b = lock.tryLock();)
3、Synchronized会自动释放锁;Lock必须要手动释放锁,如果不释放锁,死锁。
4、Synchronized线程1获得锁阻塞时,线程2会一直等待下去;Lock锁线程1获得锁阻塞时,线程2不一定会一直等待下去。
5、Synchronized可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)。
6、Synchronized适合锁少量的代码同步问题;Lock适合锁大量的同步代码。
生产者 & 消费者
传统的synchronized
// 判断等待,业务,通知
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();
}
}
if改成while解决虚假唤醒
JUC版
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();
}
}
}
Condition实现精准通知唤醒
condition2.signal();
关于锁的八个问题
staic修饰符锁的是类
无static修饰符时锁的是方法的调用者
4、集合类不安全
java.util.ConcurrentModificationException
并发修改异常
ArryList
/**
* 并发下arrayList不安全
* 解决方法:
* 1.List<String> list = new Vector<>();\\
* 2.List<String> list= Collections.synchronizedList(new ArrayList<>());
* 3.List<String> list= new CopyOnWriteArrayList<>();
*/
//CopyOnWrite 写入时复制 一种优化策略
HashSet
//Hashset不安全
Set<String> set= Collections.synchronizedSet(new HashSet<>());
Set<String> set= new CopyOnWriteArraySet<>();
hashset集合的底层是hashmap的key
HashMap
Map<String, String> map=new ConcurrentHashMap<>();
5.Callable
Callable接口类似于Runnable接口,线程第三种创建方式。
可以抛出异常。
可以有返回值。
方法不同与Runnable接口。Call方法
futureTask
FutureTask
类实现了RunnableFuture接口,继承了Runnable接口和Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
//两个构造器
public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread thread=new MyThread();
FutureTask futureTask=new FutureTask<>(thread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();// 打印一个Call,结果会被缓存,提高效率
System.out.println(futureTask.get()); //获取callable返回接口,get方法可能会产生阻塞
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
return "1024";
}
}
6.常用辅助类
CountDownLatch 减法计数器
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();
},String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println("close door");
}
原理:countDownLatch.countDown();
//数量-1
countDownLatch.await();
//等待计数器归零,再向下执行
CyclicBarrier 加法计数器
例子:七龙珠唤神龙
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{
System.out.println("召唤神龙成功");// 如果计数器为7,线程只有6个,则会等待,不进行召唤神龙
});
for (int i = 1; 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();
}
System.out.println(Thread.currentThread().getName());
}).start();
}
}
Semaphore:信号量
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(); //释放,唤醒在等待的线程
作用:多个共享资源的互斥使用。并发限流,控制最大的线程数
7.读写锁
独占锁(写锁):一次只能被一个线程占有,不能读和写
共享锁(读锁):多个线程可以同时占有,可以同时读
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();
}
}
}
8.阻塞队列BlockingQueue
使用场景:多线程并发处理,线程池
// 阻塞,超时等待
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()
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();
}
9.线程池
池化技术:事先准备好一些资源,有人使用来我这里拿,用完归还。
好处:
1、降低资源的消耗
2、提高响应的速度
3、方便管理
3大方法
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool =Executors.newFixedThreadPool(5); //固定线程池大小
ExecutorService threadPool =Executors.newCachedThreadPool();
7大参数
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;
}
自定义创建线程池
ExecutorService threadPool=new ThreadPoolExecutor(2,5,
3, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//银行满了,有人进,抛出异常
4大拒绝策略
new ThreadPoolExecutor.AbortPolicy(); // 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy();// 哪来的去哪(主线程来的,就回去让主线程执行)
new ThreadPoolExecutor.DiscardPolicy();// 丢掉任务,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy();// 尝试和最早的竞争,竞争失败了也丢掉任务,也不抛出异常
最大线程定义
CPU密集型
最大线程数,CPU几核的就是几,可以保持CPU效率最高。
IO密集型
判断程序中十分耗IO的线程数量,大于这个数,一般是这个数的两倍。
10.四大函数式接口
新时代程序员必须掌握:lambda表达式、链式编程、函数式接口、stream流式计算
函数式接口:只有一个方法的接口。
Function函数型接口
有一个输入参数,有一个输出(返回值)。
Function<String,String> function = (str)->{return str;};
predicate断定型接口
有一个输入参数,返回值只能是boolean值。
Predicate<String> predicate = (str)->{return str.isEmpty(); };
consumer消费型接口
有一个输入参数,没有返回值。
Consumer<String> consumer = (str)->{System.out.println(str);};
supplier供给型接口
没有输入参数,有一个输出(返回值)。
Supplier supplier = ()->{ return 1024; };
11.流计算
大数据:存储 + 计算
集合、MySQL 本质就是存储东西的;
计算都应该交给流来操作
/*** 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* * 1、ID 必须是偶数
* * 2、年龄必须大于23岁
* * 3、用户名转为大写字母
* * 4、用户名字母倒着排序
* * 5、只输出一个用户!
* */
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流
// lambda表达式、链式编程、函数式接口、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);
}
}
12.ForkJoin
ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量!
ForkJoin 特点:工作窃取、里面维护的都是双端队列
/**
* * 求和计算的任务! * 3000 6000(ForkJoin) 9000(Stream并行流)
* * 如何使用 forkjoin
* * 1、forkjoinPool 通过它来执行
* * 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* * 3. 计算类要继承 ForkJoinTask
* */
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long temp = 10000L;
public ForkJoinDemo(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 { // forkjoin 递归
long middle = (start + end) / 2; // 中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork(); // 拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork(); // 拆分任务,把任务压入线程队列
return task1.join() + task2.join();
}
}
}
stream并行流
public static void test3(){
long start = System.currentTimeMillis();
// Stream并行流 () (]
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum="+"时间:"+(end-start));
}
13、异步回调
/*** 异步调用: CompletableFuture
* * 异步执行
* * 成功回调
* * 失败回调
* */
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t=>" + t); // 正常的返回结果
System.out.println("u=>" + u); // 错误信息: java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 233; // 可以获取到错误的返回结果
}).get());
14、JMM
Volatile 是 Java 虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
JMM : Java内存模型
lock&unlock、read&load、use&assign、store&write
15、volatile
① 保证可见性
public class JMMDemo {
// 不加 volatile 程序就会死循环!
// 加 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args){
new Thread(()->{ // 线程 1 对主内存的变化不知道的
while (num==0){
}
}).start();
num = 1;
System.out.println(num); } }
不保证原子性
// 原子类的 Integer
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
// num++; 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 方法, CAS }
禁止指令重排
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
Volatile 由于内存屏障,可以保证避免指令重排的现象产生!
16、彻底玩转单例
①饿汉式
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry hungry=new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
②懒汉式
public class LazyMan {
private static boolean qinjiang = false;
private LazyMan() {
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
}
return lazyMan;
}
}
多线程下不安全,修改
//双重检测
if (lazyMan == null) {
synchronized (LazyMan.class){
if (lazyMan == null) {
lazyMan = new LazyMan(); //不是一个原子性操作
}
}
}
避免指令重排
private volatile static LazyMan lazyMan;
③静态内部类
但由于反射,①②③都不安全
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视私有构造器
LazyMan lazyMan = declaredConstructor.newInstance();
解决方法
private LazyMan() {
synchronized (LazyMan.class){
if (lazyMan != null) {
throw new RuntimeException("不要试图使用反射破坏单例");
}
}
}
但仍然不安全,反射不能破坏枚举的单例:
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance() {
return INSTANCE;
}
}
17、深入理解CAS
比较并交换:compare and swap !
比较当前内存中的值和主内存中的值,如果主内存中的值是期望的,就执行操作,否则不执行操作。
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类
正常情况下Java只能通过调用c++操作内存
Java的后门,可以通过这个类操作内存
缺点:
1、 循环会耗时
2、一次性只能保证一个共享变量的原子性
3、ABA问题
18、原子引用解决ABA
ABA 问题(狸猫换太子)
注意:
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实
例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;
//注意,如果泛型是一个包装类,注意对象的引用问题
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
int stamp = atomicStampedReference.getStamp(); // 获得版本号
19、各种锁
公平&非公平
公平锁: 非常公平, 不能够插队,必须先来后到!
非公平锁:非常不公平,可以插队 (默认都是非公平)
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可重入锁
某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。(加锁的方法可以相互调用)
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。
排查问题:
1、日志
2、堆栈:查看堆栈信息
使用jps -l
命令查看进程号。(该命令在JDK的bin目录下)
使用jstack
+进程号,找到死锁问题。