1.入门
JUC就是java.util.concurrent包及其子包
java默认有两个线程,一个是main线程,一个是GC线程
对于java而言有三中开启线程的方式:Thread,Runnable,Callable,但是实际上并不是java开启的线程,调用的是底层的C++开启线程
并发:多个线程操作同一个资源
并行:多个线程同时进行,只有多核cpu才能并行
java获取CPU核心数量
//获取CPU的核心数量
Runtime.getRuntime().availableProcessors();
并发编程的本质:充分利用CPU的资源
线程的状态:
- NEW 新生
- RUNNABLE 运行
- BLOCKED 阻塞
- WATING 等待
- TIMED_WATING 超时等待
- TERMINATED 终止
wait和sleep的区别
来自不同类,wait来自Object,sleep来自Thread
企业中休眠使用的是TimeUnit类在java.util.concurrent包下
wait会释放锁,sleep方法不会释放锁
wait只能在同步代码块中使用,sleep可以在任何地方使用
wait不需要捕获异常,sleep必须要捕获异常
2.Lock锁
2.1.synchronized
卖票的案例
public class SaleTicket1 {
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();
}
}
//资源类
class Ticket{
//票的数量
private Integer number = 50;
//卖票
public synchronized void sale(){
if(number > 0){
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票"+",剩余"+number+"张票");
}
}
}
2.2.Lock接口
在java.util.concurrent包下,有三个实现类
- ReentrantLock 可重用锁(常用)
- 公平锁 需要排队
- 非公平锁 可以插队(默认)
- ReentrantReadWriteLock.ReadLock 读锁
- ReentrantReadWriteLock.WriteLock 写锁
使用lock的步骤
- 在使用前加锁
- 要用try…catch语句
- 在finally中释放锁
仍然是卖票案例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicket2 {
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();
}
}
//资源类
class Ticket2{
//票的数量
private Integer number = 50;
Lock lock = new ReentrantLock();
//卖票
public void sale(){
lock.lock();//加锁
try {
if(number > 0){
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票"+",剩余"+number+"张票");
}
}catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();//解锁
}
}
}
lock和synchronized的区别
- synchronized是内置java关键字 Lock是一个java类
- synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- synchronized会自动释放锁,Lock需要自己手动释放锁,如果不释放就是死锁
- synchronized加锁之后,如果得到锁的对象产生了阻塞就会一直等下去,但是Lock就不一定会一直等待,lock会有一个方法就是
lock.tryLock();
会尝试获取锁 - synchronized是可重用锁,非公平的,不可以被中断的,Lock更自由可重用,可以判断锁,可以自己设置公平与非公平,设置方法就是在构造方法中传入boolean值,true为公平
- synchronized适合锁少量的代码同步问题,Lock适合锁大量的同步代码
2.3.生产者消费者问题
传统版使用synchronized
/*
* 线程之间的通信问题:生产者消费者问题
* 线程交替执行 A B线程操作同一个变量 num = 0
* A num+1
* B num-1
* */
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();
}
}
class Data{//数字资源类
private int number = 0;
//+1操作
public synchronized void increment() throws InterruptedException {
if(number != 0) {
//等待
this.wait();
}
number ++;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
this.notifyAll();
}
//-1操作
public synchronized void decrement() throws InterruptedException {
if(number == 0){
//等待
this.wait();
}
number --;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
this.notifyAll();
}
}
问题:如果有更多的线程,还是会出现问题,会出现虚假唤醒,因为只判断了一次number的值,我们应该使用while防止问题的发生
/*
* 线程之间的通信问题:生产者消费者问题
* 线程交替执行 A B线程操作同一个变量 num = 0
* A num+1
* B num-1
* */
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();
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;
//+1操作
public synchronized void increment() throws InterruptedException {
while(number != 0) {
//等待
this.wait();
}
number ++;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
this.notifyAll();
}
//-1操作
public synchronized void decrement() throws InterruptedException {
while(number == 0){
//等待
this.wait();
}
number --;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
this.notifyAll();
}
}
使用lock实现
在Lock中,等待和唤醒使用的都是同包下另一个接口condition
中的方法await
和singalAll
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
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();
//+1操作
public void increment() throws InterruptedException {
lock.lock();
try {
while(number != 0) {
//等待
condition.await();
}
number ++;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1操作
public void decrement() throws InterruptedException {
lock.lock();
try {
while(number == 0) {
//等待
condition.await();
}
number --;
System.out.println(Thread.currentThread().getName() + "->" + number);
//通知其他线程
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
以上这种写法产生的效果和传统方式是一样的,所以这并不是真正使用Lock的原因,使用Condition我们可以实现精准唤醒,可以让线程按照A,B,C,D的顺序执行
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//A执行完调用B,B执行完调用C,C执行完调用A
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(() -> {
for(int i=0;i<10;++i){
data3.printA();
}
},"A").start();
new Thread(() -> {
for(int i=0;i<10;++i){
data3.printB();
}
},"B").start();
new Thread(() -> {
for(int i=0;i<10;++i){
data3.printC();
}
},"C").start();
}
}
class Data3{//数字资源类
private int number = 1;//1执行A 2执行B 3执行C
private Lock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA(){
lock.lock();
try {
//业务
while(number != 1){
//等待
conditionA.await();
}
System.out.println(Thread.currentThread().getName()+"AAAA");
number = 2;
//唤醒B
conditionB.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务
while(number != 2){
//等待
conditionB.await();
}
System.out.println(Thread.currentThread().getName()+"BBBB");
number = 3;
//唤醒C
conditionC.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
//业务
while(number != 3){
//等待
conditionC.await();
}
System.out.println(Thread.currentThread().getName()+"CCCC");
number = 1;
//唤醒A
conditionA.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
2.4.进阶问题
https://www.bilibili.com/video/BV1B7411L7tE?p=10&share_source=copy_web
synchronized锁普通方法锁的是方法调用者
synchronized锁静态方法锁的是class
3.集合类不安全
在多线程操作集合的时候会产生ConcurrentModificationException
并发修改异常
3.1.List集合
解决方案:
- 使用线程安全的集合
Vector
List<String> list = Collections.synchronizedList(new ArrayList<>());
使用Collections工具类- 使用juc下的集合
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWrite写入时复制,COW,计算机程序设计领域的一种优化策略,多个线程调用的时候是该固定的,写入的时候复制一份,避免覆盖
CopyOnWrite比Vector优秀的地方:读的地方没有锁,只有写的时候有锁
3.2.Set集合
由于set集合没有可以替代的安全集合,所以我们只能使用后两种方式
CopyOnWriteArraySet
HashSet底层就是Hashmap
3.3.Map集合
Map集合也是不安全的
安全的集合是ConcurrentHashMap
,还有Collections中的synchronizedMap
4.Callable
Callable类似于Runnable,他们都是为其实例可能由另一个线程执行的类设计的,然而,Runnable不返回结果,也不能够抛出被检查的异常
所以Callable的特点
- 可以有返回值
- 可以抛出异常
- 方法不同,Callable中是call()方法
由于创建线程只能使用new Thread的方法,但是使用new Thread方法只能接收runnable接口的实现类,所以我们需要找打Callable和runnable的联系
runnable有一个实现类叫做Futuretask,这个类的构造方法既可以传入runnable还可以传入一个callable,所以我们需要通过传入futuretask来传入callable
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask, "threadA").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 "hello";
}
}
如果启动了两个线程使用的是同一个futuretask,那么只会执行一次,因为有缓存,提高了效率
get的结果可能需要等待,会产生阻塞
5.常用的辅助类
5.1.CountDownLatch
计数器,可以让让指定的一些线程任务在另一些任务的前面完成
案例:只有学生全部离开才能关门
package com.hty.add;
import java.util.concurrent.CountDownLatch;
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//倒计时 总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for(int i=1;i<=6;++i){//因为计数器设置的就是6 那么如果是5个学生的话就会导致线程阻塞,如果大于6的话就会导致一部分学生没有出教室就关门了
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " go out");
countDownLatch.countDown(); // -1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零然后再向下执行(等待前面的线程任务结束)
System.out.println("关门");
}
}
5.2.CyclicBarrier
加法计数器
package com.hty.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierTest {
public static void main(String[] args) {
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> {
System.out.println("召唤神龙成功");
});
//集齐七颗龙珠召唤神龙
for(int i=1;i<=7;++i){
final int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集"+temp+"个龙珠");
//+1
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
只有等待的线程数量达到7的时候才会执行给CyclicBarrier传入的线程
5.3.Semaphore
Semaphore:信号量
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreTest {
public static void main(String[] args) {
//参数一 线程数量(停车位) 限流的时候会使用这个工具类
Semaphore semaphore = new Semaphore(3);
for(int i=1;i<=6;++i){
new Thread(() -> {
//acquire()得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);//停车2s
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//release()释放
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
semaphore.acquire();开始执行,如果信号量到达阈值已经满了就会等待,直到可以执行
semaphore.release();释放,将当前线程占用的信号量释放
6.读写锁
readwritelock
接口,一个ReadWriteLock维护一对关联的locks,一个用于只读操作,一个用于写入,read lock可以由多个阅读器线程同时进行,write lock只能有一个线程去写
使用这个锁的原因是提高多线程的效率
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class readwritelockTest {
public static void main(String[] args) {
MyCache2 myCache = new MyCache2();
for(int i=0;i<10;++i){
final int temp = i;
new Thread(() -> {
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
for(int i=0;i<10;++i){
final int temp = i;
new Thread(() -> {
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
//自定义缓存 加锁
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() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入成功");
} catch (Exception e) {
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//取 读
public Object get(String key) {
readWriteLock.readLock().lock();
Object o = null;
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return o;
}
}
//自定义缓存 未加锁
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() + "写入成功");
}
//取 读
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
return o;
}
}
这个案例如果加锁就可以保证写入的时候按顺序写,读的时候任意顺序读
还有两个名词,独占锁(写锁),共享锁(读锁)
7.阻塞队列(blockingqueue)
写入的时候,如果队列满了就会阻塞,取得时候如果队列为空就会阻塞
在多线程并发处理和线程池中会使用到阻塞队列
7.1.阻塞队列的四组API
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer的重载方法 |
移除 | remove | poll | take | poll的重载方法 |
查看队列首部 | element | peek | - | - |
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
Test.test2();
}
//抛出异常
public static void test1(){
//要传入队列容量
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
// System.out.println(arrayBlockingQueue.add("c")); //再次添加就会报错
System.out.println("===========");
//出队
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
//不抛出异常
public static void test2(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
// System.out.println(arrayBlockingQueue.offer("a"));
System.out.println("====");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
}
//一直阻塞
public static void test3() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
System.out.println("===");
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
}
//等待超时
public static void test4() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
arrayBlockingQueue.offer("d",2,TimeUnit.SECONDS);//设置超时时间两秒
System.out.println("===");
arrayBlockingQueue.poll();
arrayBlockingQueue.poll();
arrayBlockingQueue.poll();
arrayBlockingQueue.poll(2,TimeUnit.SECONDS);
}
}
8.synchronousQueue(同步队列)
没有容量,必须进去一个元素,然后等待被取出之后才能放入元素,相当于只能存储一个元素,操作的方法put
,take
package com.hty.bq;
import java.util.concurrent.SynchronousQueue;
//同步队列
public class SynchronousQueueTest {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(() -> {
for(int i=0;i<3;++i){
final int temp = i;
try {
queue.put(temp+"");
System.out.println(Thread.currentThread().getName()+"线程放入"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
new Thread(() -> {
for(int i=0;i<3;++i){
final int temp = i;
try {
String take = queue.take();
System.out.println(Thread.currentThread().getName()+"线程取出"+take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2").start();
}
}
9.线程池
线程池的优点:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
9.1.线程池三大方法
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
public static void main(String[] args) {
Executors.newSingleThreadExecutor();//单个线程
Executors.newFixedThreadPool(5);//固定线程池的大小
Executors.newCachedThreadPool();//可变大小线程池
}
}
案例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
public static void main(String[] args) {
ExecutorService threadPool = null;//单个线程
try {
threadPool = Executors.newSingleThreadExecutor();
for(int i=0;i<10;++i){
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(5);//固定线程池的大小
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();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Executors工具类,三大方法
public class Demo1 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();//可变大小线程池
try {
for(int i=0;i<100;++i){
//通过线程池创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
//关闭线程池
threadPool.shutdown();
}
}
}
9.2.七大参数
观察源码我们可以发现,开辟线程池的本质就是调用了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;
}
在真实的业务中一般都是用的是ThreadPoolExecutor
类来自定义线程池,因为juc提供的方法容易造成OOM
案例:手动创建线程池
package com.hty.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo2 {
public static void main(String[] args) {
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
5,
3L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
try {
//最大承载8
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就可以设置为几
//使用代码获取CPU核心数量 Runtime.getRuntime().availableProcessors();
-
IO密集型 判断程序中十分耗IO的线程,那么就设置为IO线程数量的两倍
9.3.四种拒绝策略
当线程池满了,阻塞队列也满了的时候,就会触发四种拒绝策略的一种
new ThreadPoolExecutor.AbortPolicy()
抛出异常new ThreadPoolExecutor.CallerRunsPolicy()
哪里来的去哪里,上面的例子就是抛给main线程执行new ThreadPoolExecutor.DiscardPolicy()
不会抛出异常,会丢失任务new ThreadPoolExecutor.DiscardPolicy()
队列满了,就会和最先执行的线程去竞争,也不会抛出异常
10.四大函数式接口
函数式接口就是只有一个方法的接口,例如Runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
简化编程模型,在新版本框架底层大量应用
10.1.function接口
函数型接口
import java.util.function.Function;
/*
Function函数型接口,有一个输入参数,有一个输出参数
只要是函数型接口,就可以使用lambda表达式简化
*/
public class Demo1 {
public static void main(String[] args) {
//工具类 输出输入的值
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String str) {
return str;
}
};
//lambda表达式写法
Function<String,String> function = (str) -> {return str;};
//简化lambda表达式
Function<String,String> function = str -> {return str;};
System.out.println(function.apply("123"));
}
}
10.2.Predicate接口
这个接口返回的是一个布尔值
import java.util.function.Predicate;
//断定型接口 有一个输入参数 返回值只能是布尔值
public class Demo2 {
public static void main(String[] args) {
//判断字符串是否为空
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s == null || s.equals("");
}
};
//lambda表达式简化
Predicate<String> predicate = (s) -> {return s == null || s.equals("");};
System.out.println(predicate.test("123"));
}
}
10.3.Consumer接口
消费型接口
import java.util.function.Consumer;
//消费型 只有输入没有返回值
public class Demo3 {
public static void main(String[] args) {
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
consumer.accept("asd");
}
}
10.4.Supplier接口
供给型接口
import java.util.function.Supplier;
//供给型 没有参数只有返回值
public class Demo4 {
public static void main(String[] args) {
Supplier<String> stringSupplier = new Supplier<String>() {
@Override
public String get() {
return "asd";
}
};
System.out.println(stringSupplier.get());
}
}
11.Stream流式计算
案例
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/*
* 题目 使用一行程序完成
* 有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(5,"e",25);
//集合用来存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//流用来计算
list.stream().filter(u -> {return u.getId() % 2 == 0;})
.filter(u -> {return u.getAge() > 23;})
.map(u -> {return u.getName().toUpperCase(Locale.ROOT);})
.sorted((uu1,uu2) -> {return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::print);
}
}
12.ForkJoin
在1.7之后ForkJoin就有了,这个主要是用来并行执行任务,提高效率。
Forkjoin特点:工作窃取
A线程和B线程同时执行任务,B线程先执行完,就会将A线程中未执行完的一部分偷取过来执行
里面维护的都是双端队列
案例
任务类
package com.hty.forkjoin;
import java.util.concurrent.RecursiveTask;
/*
* 求和计算
* 使用forkjoin需要通过forkjoinPool来执行,使用其中的execute方法执行
* 继承RecursiveTask这个任务类代表这个类是一个任务类 这个任务类是ForkJoinTask的实现类
* */
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
//临界值
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{
//分支合并计算
long mid = (start + end) / 2;//中间值
ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(start, mid);//任务
forkJoinDemo1.fork();//拆分任务 把线程加入队列
ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(mid + 1, end);
forkJoinDemo2.fork();
//获取结果
long sum = forkJoinDemo1.join() + forkJoinDemo2.join();
return sum;
}
}
}
测试类
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
long sum = 0L;
for(long i=1;i<10_0000_0000L;++i) sum += i;
long end = System.currentTimeMillis();
System.out.println(end - start);//使用普通运算的时间
long start1 = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo forkJoinDemo = new ForkJoinDemo(1L,10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
Long compute = submit.get();
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);//使用分支任务的时间
long start2 = System.currentTimeMillis();
LongStream.rangeClosed(1L,10_0000_0000L).parallel().reduce(0,Long::sum);
long end2 = System.currentTimeMillis();
System.out.println(end2 - start2);//使用stream并行流
}
}
13.异步回调
Future,设计的初衷就是对将来的某个事件的结果进行建模
我们一般使用Future的实现类CompletableFuture
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//异步执行 先打印111过了2s之后才输出异步任务中的内容 runAsync没有返回值
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
});
//获取执行结果
System.out.println("111");
completableFuture.get();
//--------------------------------------------------------------------------
//有返回值的异步回调 要么成功,要么失败
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {//成功的回调
System.out.println(t);//正常的返回结果
System.out.println(u);//失败的返回结果
}).exceptionally((e) -> {//失败的回调
e.printStackTrace();
return 233;
}).get());
}
}
14.JMM
JMM:java内存模型,不存在的东西,是一个概念,约定
JMM的同步约定
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
JMM的操作有4组八个
- lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
- unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
- read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
规则:
- 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
- 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
- 不允许线程将没有assign的数据从工作内存同步到主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
- 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
- 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。
问题:程序不知道主内存的值被修改了,此时就需要使用volatile关键字
15.Volatile
Volatile是Java虚拟机提供的轻量级的同步机制
保证可见性
import java.util.concurrent.TimeUnit;
public class JMMDemo {
/*
* 如果没有添加volatile关键字,程序就会一直卡顿在线程1处,加上之后,
* 线程1就可以知道主内存的值被修改了,从而终止循环,体现volatile的可见性
* */
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {//main线程
new Thread(() -> {//线程1
while(num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num=1;
System.out.println(num);
}
}
不保证原子性 原子性就是不可分割的意思 线程A在执行任务的时候,不能被打扰的,也不能被分割,要么同时成功,要么同时失败
//不保证原子性
public class VolatileDemo1 {
private volatile static int num = 0; //加上了volatile关键字发现结果仍然不正确 就验证了不保证原子性
public static void add(){
num++;
}
public static void main(String[] args) {
for(int i=0;i<20;++i){
new Thread(() -> {
for(int j=0;j<1000;++j){
add();
}
}).start();
}
System.out.println(num);//理论上结果应该为20000,但是结果并不是20000
}
}
不加lock和synchronized的解决方案: 使用juc下的atomic包下的类
import java.util.concurrent.atomic.AtomicInteger;
//不保证原子性
public class VolatileDemo1 {
//原子类的int
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();//+1方法 使用到了CAS
}
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){//判断当前执行的线程是否大于2
Thread.yield();//继续执行没有执行完的线程
}
System.out.println(num);//现在就是20000
}
}
这些类的底层都和操作系统直接挂钩,是会在内存中直接修改值,Unsafe类是一个很特殊的存在
禁止指令重排
指令重排:自己写的程序,计算机并不是按照自己写的那样去执行的,
源代码->编译器优化的重排->指令并行也可能会重排->内存系统也会重排
处理器在进行指令重排的时候会考虑数据之间的依赖性
16.单例模式
饿汉式
//饿汉式单例
public class Hungry {
//私有构造方法,防止其他地方new对象
private Hungry(){}
public static final Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
//懒汉式 单线程下没问题 多线程有问题
public class LazyMan {
private LazyMan (){}
public static LazyMan LAZY_MAN;
public static LazyMan getInstance(){
if(LAZY_MAN == null){
LAZY_MAN = new LazyMan();
}
return LAZY_MAN;
}
}
DCL懒汉式
package com.hty.single;
//懒汉式 单线程下没问题 多线程有问题
public class LazyMan {
private LazyMan (){
System.out.println(Thread.currentThread().getName()+"ok");
}
//需要禁止指令重排
public volatile static LazyMan LAZY_MAN;
//双重检测锁模式的懒汉式单例 简称DCL懒汉式
public static LazyMan getInstance(){
if(LAZY_MAN == null){
synchronized (LazyMan.class){
if(LAZY_MAN == null){
LAZY_MAN = new LazyMan();//这不操作并不是一个原子性操作 所以应该禁止指令重排
}
}
}
return LAZY_MAN;
}
public static void main(String[] args) {
//多线程并发出现问题
for(int i=0;i<20;++i){
new Thread(() -> {
LazyMan.getInstance();
}).start();
}
}
}
17.CAS
CAS:compareAndSet比较工作内存的值和主内存的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环(底层是自旋锁)
import java.util.concurrent.atomic.AtomicInteger;
public class Demo1 {
public static void main(String[] args) {
//原子类 底层使用了CAS CAS是CPU的并发原语
AtomicInteger atomicInteger = new AtomicInteger(2020);
//第一个参数是期望,第二个是更新
//如果期望的值达到了,就更新,否则就不更新
System.out.println(atomicInteger.compareAndSet(2020, 2021));//CAS
System.out.println(atomicInteger.get());//2021
System.out.println(atomicInteger.compareAndSet(2020, 2021));//CAS
System.out.println(atomicInteger.get());//修改失败
}
}
CAS底层是靠Unsafe类实现的,unsafe类是java的后门,可以使用这个类操作内存
底层其实就是利用内存偏移量来计算下标,然后判断当前值是否满足期望,满足就修改
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- 存在ABA问题
18.CAS的ABA问题
ABA问题
- 在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
- 1.线程1,期望值为A,欲更新的值为B
- 2.线程2,期望值为A,欲更新的值为B
- 线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。
ABA问题带来的危害:
- 小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
线程1(提款机):获取当前值100,期望更新为50,
线程2(提款机):获取当前值100,期望更新为50,
线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
线程3(默认):获取当前值50,期望更新为100,
这时候线程3成功执行,余额变为100,
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)这就是ABA问题带来的成功提交。 - 解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+1,即A->B->A就变成了1A->2B->3A。
19.原子引用
使用原子引用可以解决ABA问题,原子引用就是带版本号的原子操作
Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用过缓存,而new一定会创建新的对象分配新的内存空间
package com.hty.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Demo1 {
public static void main(String[] args) {
//原子类 底层使用了CAS
// AtomicInteger atomicInteger = new AtomicInteger(2020);
//使用这个类解决ABA问题
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,2,stamp,stamp+1);
stamp = atomicStampedReference.getStamp();
System.out.println("a2=>" + stamp);
atomicStampedReference.compareAndSet(2,1,stamp,stamp+1);
},"a").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(1,3,stamp,stamp+1);
stamp = atomicStampedReference.getStamp();
System.out.println("b2=>" + stamp);
atomicStampedReference.compareAndSet(3,1,stamp,stamp+1);
},"b").start();
}
}
20.公平锁、非公平锁
公平锁:非常公平,不能插队,先来后到
非公平锁:非常不公平,可以插队
ReentrantLock就是一个非公平锁
21.可重入锁(递归锁)
可重入 就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
而锁的操作粒度是”线程”,而不是调用(至于为什么要这样,下面解释).同一个线程再次进入同步代码的时候.可以使用自己已经获取到的锁,这就是可重入锁。
java里面内置锁(synchronize)和Lock(ReentrantLock)都是可重入
//synchronized 版
public class Demo1 {
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");
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//Lock 版
public class Demo2 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sms();
},"A").start();
new Thread(() -> {
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();//
try {
System.out.println(Thread.currentThread().getName() + "---sms");
call();//这里还有一把锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "---call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
22.自旋锁(spinlock)
案例:unsafe源码
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
自定义自旋锁
package com.hty.lock;
import java.util.concurrent.atomic.AtomicReference;
//自旋锁
public class spinDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "===>mylock");
//自旋锁
while(!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "===>myunlock");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) {
}
}
测试 B线程会等到A线程执行完毕之后才会执行
package com.hty.lock;
import java.util.concurrent.TimeUnit;
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
spinDemo spinDemo = new spinDemo();
new Thread(() -> {
spinDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinDemo.myUnlock();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
spinDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinDemo.myUnlock();
}
},"B").start();
}
}
23.死锁
死锁就是互相获取对方的锁
死锁的排查:
- 使用
jps
命令定位进程号 - 使用
jstack 进程号
查看详细信息 找到死锁问题