JUC的多线程进阶
进程和线程
程序运行起来后就是一个进程,进程包含多个线程,java默认有两个线程:main,g(c)线程
Java是没有权限开启线程的,需要调用native调用本地的方法
并发和并行
并发(多个线程操作一个资源)
- cpu一核,cpu快速交替模拟出多核的效果
并行
- cep多核,多个线程可以同时执行
并发编程的本质:想要充分利用cpu的资源
wait()和sleep()的区别
-
wait会释放锁,sleep不会释放锁
-
wait只能在同步代码块中使用
Lock锁(重点)
-
创建锁
-
加锁
-
解锁
Lock lock = new ReentranLock(); lock.lock(); Try{ ... }catch(){ lock.unLock(); }
/**
* 真正的多线程开发公司不会像new Thread(new Runnable)这种方式实现创建线程
* 线程就是一个资源类没有多余的操作,资源和对资源的操作
* new Thread(()->{ // 资源类的方法调用 }) // 函数式接口的Lambda简写
* 并发只的就是多个线程对同一个资源的操作,所以资源调用,就用同一个类的对象去调用
* */
public class TestLock {
public static void main(String[] args) {
Resources resources = new Resources();
new Thread(()->{
for (int i = 0; i < 30; i++) {
resources.buy();
}
},"我").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
resources.buy();
}},"可恶的黄牛党").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
resources.buy();
}},"一样悲催的别人").start();
}
}
// 购票资源类 这种线程作为单独的资源类称为OOP的思想
class Resources{
private int num = 30;
// buy
@SneakyThrows
public synchronized void buy() {
if (num <= 0) return;
System.out.println(Thread.currentThread().getName()+"购买了第" + num-- + "张票; 剩余:" + num + "张票。");
}
}
Synchronized和Lock区别
- Synchronized无法获取锁的状态
- Synchronized会自动释放锁,Lock必须手动释放,不释放会形成死锁
- Synchronized 线程1(获得锁,阻塞)、线程2(等待,继续等待);lock锁不会一直等待下去,因为Lock里面有tryLock()方法尝试获取锁,等不到就结束了。
- Synchronized可重入锁,非公平锁,不可以中断;Lock锁,可重入锁,非公平锁(可以自己设置,在创建锁时ReentranLock()加入参数boolean)
- Synchronized适合少量的代码的块
生产者和消费者问题
Synchronized版本(传统):判断资源的状态是否需要线程等待--执行业务--唤醒另外的线程 (两个线程)
- 有多个线程的时候生产者消费者会出现问题(虚假唤醒),判断资源状态,决定线程是否等待时只判断了一次,用while()循环每次进程被唤醒时再次进行判 断就可以解决虚假唤醒问题了
JUC版(lock,Condition):Conditionl类中的方法await(),让进程等待;signal(),唤醒进程; lock.newCondition();
Condition可以实现对进程的精准唤醒
如现在要实现生产者A唤醒消费者B,B唤醒C,C唤醒D,D唤醒A;
conditionA.await();//让A线程等待
conditionA.signal();//唤醒A线程
condtionB.signal(); // 唤醒B线程
// 进程之间的通信,生产者消费者问题
public class TestShop {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
buffer.pushA();
}
}, "生产者AAA号").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
buffer.popB();
}
}, "消费者BBB号").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
buffer.pushC();
}
}, "生产者CCC号").start();
new Thread(() -> {
for (int i = 0; i < 30; i++) {
buffer.popD();
}
}, "消费者DDD号").start();
}
}
//JUC解决方案
class Buffer {
Lock lock = new ReentrantLock();
// 监视器
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
Condition conditionD = lock.newCondition();
int num = 0;
//生产者添加商品
public void pushA() {
lock.lock();
// 产品满了停止生产等待消费者消费
try {
while (num >= 1) {
conditionA.await();
}
//生产,通知消费者
num++;
System.out.println(Thread.currentThread().getName() + "生产了" + num + "份产品");
conditionB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void popB() {
lock.lock();
//没有商品等待生产
try {
while (num <= 0) {
conditionB.await();
}
//消费,通知生产者生产
num--;
System.out.println(Thread.currentThread().getName() + "消费了,剩余" + num + "份产品");
conditionC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//生产者添加商品
public void pushC() {
lock.lock();
// 产品满了停止生产等待消费者消费
try {
while (num >= 1) {
conditionC.await();
}
//生产,通知消费者
num++;
System.out.println(Thread.currentThread().getName() + "生产了" + num + "份产品");
conditionD.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void popD() {
lock.lock();
//没有商品等待生产
try {
while (num <= 0) {
conditionD.await();
}
//消费,通知生产者生产
num--;
System.out.println(Thread.currentThread().getName() + "消费了,剩余" + num + "份产品");
conditionA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
线程的8锁问题
- 两个方法的共用同一把锁,先抢到锁的方法先执行
- 锁的对象就是,方法的调用者
- 当资源是两个对象时,两个对象的锁互不相关
- 方法用static修饰后,方法锁的对象是类的Class对象,每个类的Class对象只有一个。所以多个static修饰方法时,锁只有一个。
集合类不安全
在单线程中集合类是安全的,但在多线程中是不安全的:例如:多个线程同时对集合类添加数据;
容易出现ConcurrentModificationException(迸发修改异常)
- 通过工具类Collection转换
- 写入时复制
-
List:解决方案:
-
List
list = new Vector(); -
List<String> list = Collection.synchronizedList(new ArrayList());// 利用了同步锁的方法,但执行效率变低 -
List<String> list = new CopyOnWriteArrayList(); // 利用lock锁
-
-
set:
-
Set
set= Collection.synchronizedSet(new hashSet<>());// 利用了同步锁的方法,但执行效率变低 -
Set set = new CopyOnWriteArraySet(new HashSet());
-
-
HashMap
-
Map<String,String> map = new ConcurrentHashMap<>();
-
1.Callable
- 可以有返回值
- 可以抛出异常
- 方法不同call/run
启动callable线程:
new Thread(new FutureTask(new Callble1())).start();
回顾Callable接口
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callble1 callble1 = new Callble1();
//1.创建内部有五个线程的线程池
ExecutorService service = Executors.newFixedThreadPool(5);
Future<String> submit = service.submit(callble1);
try {
submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdownNow();
FutureTask futureTask = new FutureTask(new Callble1());
new Thread(futureTask).start();
String o = (String) futureTask.get(); // 获取返回的内容
System.out.println(o);
}
}
class Callble1 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("CallableTest");
return "test sccesion";
}
}
2.JUC的常用辅助类
-
减法计数器CountDownLatch: 一般用在有些线程必须执行的时候使用,使用后,计数器-1,程序等待计数器归零(countDownLatch.await();)及线程执行了才继续运行
两个方法:
- countDownLatch.countDown(); // -1
- countDownLatch.await(); //等待计数器归零在执行下面操作
实现:
// 计数器CountDownLatch public class CountDownLatchTest { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(5); // 总数是5 for (int i = 0; i < 5; i++) { new Thread(()->{ System.out.println("线程执行"); countDownLatch.countDown(); // -1 }).start(); } countDownLatch.await(); // 等待计数器归零在执行下面操作 System.out.println("多个线程执行完毕"); } } -
加法计数器CyclicBarrier:只有一个await()方法,等待一次,就+1;
// CyclicBarrier有两个构造器,一个参数的int 单纯的计数;
// 两个参数,计数达到第一个参数的值时,执行第二个参数(Runnable接口)的线程;无法达到时,线程就会死在等待的地方实现:
// 加法计数器 public class CyclicBarrierTest { public static void main(String[] args) { // CyclicBarrier有两个构造器,一个参数的int 单纯的计数; // 两个参数,计数达到第一个参数的值时,执行第二个参数(Runnable接口)的线程 CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("召唤神龙"); }); for (int i = 1; i <= 7; i++) { final int temp = i ; new Thread(()->{ System.out.println("集齐了"+temp+"颗龙珠"); try { cyclicBarrier.await(); // 从这个地方看await();等待一次,计数器就会+1等待多次达到计数器值时就执行线程 } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } -
计数信号量:Semaphore // 运用在限流方面,在有限的容量里让线程有序,容量满了其他线程等待,有线程释放,则其他线程才能获取
两个方法:
- acquire() // 获取 ,一直阻塞,直到有可用的信号量,即可获取
- release() // 释放 , 释放所占有的信号量
实现:抢车位(3个车位,6个车)
// 抢车位,6抢3 // Semaphore信号量 public class SemaphoreTest { public static void main(String[] args) { Semaphore semaphore = new Semaphore(3); // 有3个信号量 for (int i = 1; i <= 6; i++) { new Thread(()->{ // 获取信号量 try { semaphore.acquire(); System.out.println(Thread.currentThread().getName()+"号车获取到了车位"); TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }finally { // 释放信号量 System.out.println(Thread.currentThread().getName()+"号车离开了车位"); semaphore.release(); } },""+i).start(); } } }
3. 读写锁(ReadWriteLock)
实现类:ReentrantReadWriteLock
允许多个线程读取,写的时候只允许一个线程写;相比Lock锁,更细了一点点
实现:多个线程排队写入,多个线程同时读取
-
reentrantReadWriteLock.readLock().lock(); // // 获取读锁,加锁
reentrantReadWriteLock.writeLock().lock(); // // 获取写锁,加锁
public class ReadWriteLockTest {
public static void main(String[] args) throws InterruptedException {
Resources resources = new Resources();
// 写东西
for (int i = 1; i <= 7; i++) {
final String temp= ""+i;
new Thread(()->{resources.write(temp,temp);},temp).start();
}
TimeUnit.SECONDS.sleep(3);
for (int i = 8; i <= 14; i++) {
final String temp= ""+(i-7);
new Thread(()->{resources.read(temp);},temp).start();
}
}
}
// 自定义缓冲区,线程锁操作的资源
class Resources{
Map<String,Object> map = new HashMap();
//读写锁
ReentrantReadWriteLock lock= new ReentrantReadWriteLock();
//读取
public void read(String i){
// 获取读锁,加锁
lock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在读取");
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"读取OK"+"读取了"+map.get(i));
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放读锁
lock.readLock().unlock();
}
}
//写入
public void write(String key,String value){
// 获取写锁,加锁
lock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在写入");
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
//释放读锁
lock.writeLock().unlock();
}
}
}
4.阻塞队列(BlockingQueue)
- 写入:队列满了的时候,阻塞
- 取出:队列为空的时候,阻塞
一般用在多线程并发处理,线程池里面用队列去维护里面的线程
学会使用队列: 添加,移除!
BlockingQueue的四组API
每种方法对应的响应结果:
队列满时添加,队列为空时移除

四组API的测试:
public class BlockingQueueTest {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> os = new ArrayBlockingQueue<>(2); //队列长度为2
//添加测试 add(e),offer(e),put(e),offer(e,time,unit)
os.put("a");
os.put("b"); //队列已满
//os.put("c"); // 队列满时,用put()继续添加,会进入等待状态,程序不会退出
//System.out.println(os.add("c")); // 队列满了add()方法抛出异常
//System.out.println(os.offer("c"));*/ // 队列满了offer(e)再添加时返回false
//System.out.println(os.offer("c",2, TimeUnit.SECONDS));*/ //offer(e,time,unit)等待超过参数的时间还未添加成功,就结束等待
//移除测试 remove() poll() take() poll(time,unit)
System.out.println(os.remove());
System.out.println(os.remove()); // 队列为空了
//System.out.println(os.remove()); // remove()抛出异常
//System.out.println(os.poll()); // poll() 返回false
//System.out.println(os.take()); // take()进入等待状态,程序还在运行,不会退出
System.out.println(os.poll(1,TimeUnit.SECONDS)); // poll(time,unit) 等待超过参数的时间后,移除方法就会结束,退出程序
// 检测队首元素
//System.out.println(os.element()); //队首为空,element()抛出异常
System.out.println(os.peek()); // 队首为空,peek(),返回null
}
}
5.同步队列(SynchronousQueue)
没有容量,进入一个元素后,必须取出来才能继续放元素
put(),take();
验证:从输出的结果看,添加和取出的线程是交替进行的(进入一个元素后,必须取出来才能继续放元素)

)
public class SynchronousQueueTest {
public static void main(String[] args) {
BlockingQueue<String> synchronousQueue = new SynchronousQueue();
new Thread(() -> {
try {
System.out.println("放入第1个");
synchronousQueue.put("1");
TimeUnit.SECONDS.sleep(1);
System.out.println("放入第2个");
synchronousQueue.put("2");
TimeUnit.SECONDS.sleep(1);
System.out.println("放入第3个");
synchronousQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("取出了" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println("取出了" + synchronousQueue.take());
TimeUnit.SECONDS.sleep(1);
System.out.println("取出了" + synchronousQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
6.线程池(重点)
线程池:三大方法,七大参数,四种拒绝策略
池化技术:
- 优化程序对系统资源的使用
- 事先准备好一些资源,给程序备用,用完后归还
好处:
- 降低资源的消耗
- 提高响应的速度
- 方便管理
线程复用,可以控制最大并发数,管理线程
一、线程池的三大方法
Executors 工具类、三大方法
及线程池运用Executors 工具类的三种创建方法
-
executorService = Executors.newSingleThreadExecutor(); //创建只有一个线程的线程池 -
executorService =Executors.newFixedThreadPool(5);//创建有5个线程的线程池 -
executorService = Executors.newCachedThreadPool(); //可伸缩,遇强则强,遇弱则弱
创建和效果实现:
public class ThreadNew {
public static void main(String[] args) {
ExecutorService executorService ;
//executorService = Executors.newSingleThreadExecutor(); //创建只有一个线程的线程池
executorService =Executors.newFixedThreadPool(5);//创建有5个线程的线程池
//executorService = Executors.newCachedThreadPool(); //可伸缩,遇强则强,遇弱则弱
try {
for (int i = 0; i < 30; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown(); // 关闭线程池
}
}
}
二、七大参数
阿里巴巴的开发 手册,说明,不要运用上面的Executors 工具类的三种创建方式创建线程池,
Executors 工具类创建的线程池不安全,工作中用ThreadPoolExecutor()自定义线程池
-
因为这前两种方式允许的队列长度为为Integer.MAX_VALUE约等于21亿,可能导致大量请求堆积,从而OOM(内存用完了)
-
第三种CachedThreadPool()方式允许创建的最大线程数为Integer.MAX_VALUE,可能导致大量创建线程,从而OOM
-
建议通过底层的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; }
三、四种拒绝策略
自定义线程池测试四种拒绝策略
1.AbortPolicy() //抛出异常
2.CallerRunsPolicy() //从哪儿来的就返回哪儿执行,如果从主线程main来的就由main线程来执行这个线程的代码
3.DiscardPolicy() //不会抛出异常,丢掉该任务,不执行
4.DiscardOldestPolicy()//尝试和最早进入线程池的线程竞争,如果刚好结束就接上,没有就放弃,丢掉任务,第三个的升级版
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// 自定义线程池
/** 四种拒绝策略在最大线程和阻塞队列都满了以后该做什么操作
* 1.AbortPolicy() //抛出异常
* 2.CallerRunsPolicy() //从哪儿来的就返回哪儿执行,如果从主线程main来的就由main线程来执行这个线程的代码
* 3.DiscardPolicy() //不会抛出异常,丢掉该任务,不执行
* 4.DiscardOldestPolicy()//尝试和最早进入线程池的线程竞争,如果刚好结束就接上,没有就放弃,丢掉任务
*/
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(), //默认的线程工厂,一般不会改动
//new ThreadPoolExecutor.AbortPolicy() //抛出异常
//new ThreadPoolExecutor.CallerRunsPolicy() //从哪儿来的就返回哪儿执行,如果从主线程main来的就由main线程来执行这个任务的代码
//new ThreadPoolExecutor.DiscardPolicy() //不会抛出异常,丢掉该任务,不执行
new ThreadPoolExecutor.DiscardOldestPolicy()//尝试和最早进入线程池的线程竞争,如果刚好结束就接上,没有就放弃,丢掉任务
);
for (int i = 0; i < 9; i++) {
threadPoolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行");
});
}
}
}
自定义的线程池的最大线程应该如何定义
-
CPU密集型:cpu几核就是几,可以保持cpu的效率最高,代码获取电脑的cpu核数
Runtime.getRuntime().availableProcessors());// 获取电脑的cpu核数 -
IO密集型:判断程序中有多少个十分耗IO的线程有多少个,大于即可,一般两倍
-
调优的用处
7.四大函数式接口(必须掌握)
函数式接口: 只有一个方法的接口
// 简化编程模型,在新版本的框架底层大量应用
一、Interface Function<T,R> //一个T参数类型,一个R返回值类型
简单实现:
Function<Object,Object> function = str -> {return str;}; // Lamda表达式简化函数式接口
public class FunctionTest {
public static void main(String[] args) {
/* Function<Object, Object> function = new Function<Object, Object>(){ //匿名内部类
@Override
public Object apply(Object o) {
return o;
}
};*/
Function<Object,Object> function = str -> {return str;}; // Lamda表达式简化函数式接口
System.out.println(function.apply(9));
}
}
二、Interface Predicate
断定行接口
有一个输入参数,返回值只能是boolearn
// 比如用来实现判断传入的字符串是否为龙空之类的
三、Interface Consumer
消费型接口 // 只有一个输入参数,没有返回值
四、Interface Supplier
供给型接口 // 只有返回值,没有参数
Supplier<Integer> supplier = ()->{return 1024;};
System.out.println(supplier.get());
8.Stream流式计算
什么是Stream流式计算
大数据 = 存储 + 计算
集合,数据库用来存储,计算就由Stream流式计算来完成
运用四个新时代程序员应具备的技巧:
public class TestDemeo1 {
public static void main(String[] args) {
User user1 = new User(1,24,"a");
User user2 = new User(2,19,"b");
User user3 = new User(3,22,"c");
User user4 = new User(4,22,"d");
User user5 = new User(5,32,"e");
List<User> list = Arrays.asList(user1,user2,user3,user4,user5); //转换成ArrayList 流(stream)
// 这行代码运用到了Lamda表达式,函数式接口,链式编程,Stream流式计算
list.stream().filter(data->{return data.getId()%2 == 1;}) // filter()过滤
.filter(data->{return data.getAge()>=23;})
.map(data->{return data.getName().toUpperCase();}) // 映射 返回一个流,包括将给定函数应用到该流元素的结果。
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);}) //返回一个包含该流的元素流,根据提供的 Comparator排序。
.limit(1) // 分页,一页显示几个元素
.forEach(System.out::println); // 让里面的元素执行一个操作
}
}
class User{
int id;
int age;
String name;
public User() { }
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
9.ForkJoin
大化小,小任务执行后在汇总

ForkJoin的特点:工作窃取
B线程自己的任务执行完后窃取别的线程的子任务,不让线程等待从而提高效率
这里面的是双端队列,两端都可以操作

ForkJoin自己的理解和使用:ForkJoin需要通过ForkJoinPool.execute() 类似于添加线程来执行 RecursiveTask(计算任务)抽象类的任务,抽象类继承并实现里面的抽象方法
计算的类需要继承RecursiveTask实现其中的抽象方法(计算任务),任务的思想,把任务划分,递归调用,把拆分的任务压入线程队列(.fork()),返回及汇总拆分的任务的结果.join()获取子任务的结果。
计算1到10亿的加法计算:
经过测试三种计算方法: 严重推翻了ForkJoin优于暴力算法的效果;
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Test().test3();
}
public void test2(){ //时间:322
long num = 0L;
long time1 = System.currentTimeMillis();
for (int i = 0; i <= 10_0000_0000; i++) {
num += i;
}
long time2 = System.currentTimeMillis();
System.out.println("值:"+num+" 时间 :"+(time2-time1));
}
public void test1() throws ExecutionException, InterruptedException { //时间4807
ForkJoinPool forkJoinPool = new ForkJoinPool();
long start = System.currentTimeMillis();
ForkJoinTask<Long> forkJoinTest = new ForkJoinTest(0L,10_0000_0000L);
//forkJoinPool.execute(); // execute执行任务是没有返回值的,无法得到计算的结果
ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinTest);// 提交任务,有返回值
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("计算的值:"+sum+" 执行的时间:"+(end - start));
}
public void test3(){
long time1 = System.currentTimeMillis();
// Stream并行流 //时间 206
long reduce = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long time2 = System.currentTimeMillis();
System.out.println("计算的值:"+reduce+" 执行的时间:"+(time2 - time1));
}
}
forkoin:
public class ForkJoinTest extends RecursiveTask<Long> {
private Long start;
private Long end;
public ForkJoinTest() { }
public ForkJoinTest(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if((end - start) < 1_0000){
Long num = 0L;
for (Long i = start; i <= end; i++) {
num+= i;
}
return num;
}else {
Long middle = (start+end)/2;
ForkJoinTest task1 = new ForkJoinTest(start, middle); //拆分
task1.fork(); // 把任务亚茹线程队列中
ForkJoinTest task2 = new ForkJoinTest(middle+1, end);
task2.fork();
return task1.join() + task2.join(); // 汇总子任务的结果
}
}
}
10.异步回调Future
似于ajax
在程序中发起一个异步请求,不会影响到当前程序向下执行,可以利用这个实现类似于多线程的操作,并且可以具有返回值,
Future的实现类CompletableFuture:
- runAsync();不具有返回值
- supplyAsync() ;有返回值 里面是,供给型函数式接口
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{}); // 发起一个请求
future.get(); //程序处于阻塞状态,等待异步请求执行出结果
可能出现的问题,在异步请求执行中,主线程和其他线程已经执行完毕,程序就会退出,不会等待异步请求执行完。可以在主程序中加入.get() //程序处于阻塞状态,等待异步请求执行出结果
// 具有返回值的时候
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int i = 10/0;
return 1024;
});
future
.whenComplete((t,u)->{
System.out.println("t=>"+ t); // t代表正常的返回结果
System.out.println("u=>"+ u); }) // u代表错误的信息
.exceptionally(e->{ //编译失败的时候执行
System.out.println(e.getMessage());
return 404; });
11. JMM
JMM:java内存模型,约定;
JMM的理解:
- 保证了可见性(主内存的改变能让其他线程的及时的知道)
- 不保证原子性
- 禁止指令重排
关于JMM的一些同步的约定: 线程就只在工作内存和主内存之间的约定
- 线程解锁前,必须把共享变量立刻刷回主存。
- 线程加锁前,必须读取主存中的最新值到工作内存中!
- 加锁和解锁是同一把锁

public class JMMDemo {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}
出现了线程A把主内存的值拷贝到自己的工作内存中使用,线程B修改了主内存的值,然而线程A不知道值被修改任然继续使用自己的工作内存的值。解决:Volatile
private volatile static int num = 0; // volatile关键字,保证可见性
内存交互的8种操作:
12.Volatile
1.volatile 可以保证可见性;
public class JMMDemo {
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num == 0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}
2.不保证原子性
多个线程对同一个资源做加法操作时由于加法操作不是原子性操作,会出现问题。Volatile不保证原子性
解决方案:
-
lock锁,,synchronized同步锁
-
java里面有原子类java.util.concurrent.atomic
private static AtomicInteger num= new AtomicInteger(0);
这些类的底层都直接和操作系统挂钩!在内存中修改值! Unsafe类是一个很特殊的存在 !
public class JMMDemo2 {
private static AtomicInteger num= new AtomicInteger(0);
public static void add(){
// num++; // 不是原子性操作
num.getAndIncrement(); //底层利用CAS
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2){ // 判断存活的线程是否大于2,main线程G(c)线程
Thread.yield(); // 线程礼让
}
System.out.println(num);
}
}
3.禁止指令重排
指令重排:处理器编译器之类的会对源代码进行优化,对代码的执行顺序可能有所改变提高效率,对具有依赖关系的代码行没影响,但是对于对没有依赖关系的代码行执行顺序可能不同。
由于内存屏障,可以保证避免指令重排的现象产生!
Volatile会禁止Volatile上下的代码行有交换。
Volatile的禁止指令重排在单例模式(DCL懒汉式)运用得较多
单例模式
-
懒汉式单例
-
需要用的时候再去加载进入内存(解决饿汉式的一开始就加载的弊端)
-
具有双重检测锁下的懒汉式单例模式 简称: DCL懒汉式单例
-
DCL懒汉式单例在极端情况下(指令重排)是不安全的,因为在single = new SingletonPattern(); 的时候由于这不是原子型操作,可能发生指令重排,我们期望的执行顺序是(开辟空间,构造初始化对象,把这个对象指向这个空间),当出现对象未初始化就指向空间后,其他线程检测到对象不为null就会返回这个没有初始化的对象引起异常。
-
解决方案:利用关键字volatile修饰对象,禁止指令重排
// 懒汉式单例 public class SingletonPattern { private volatile static SingletonPattern single; private SingletonPattern(){ } // 私有的构造函数,外部不能够创建类的对象 // 双重检测锁下的懒汉式单例模式 简称: DCL懒汉式单例 public static SingletonPattern newSingle(){ if(single == null){ synchronized (SingletonPattern.class){ if (single == null) single = new SingletonPattern(); // 不是原子型操作,可能发生指令重排 } } return single; } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(()->{ newSingle(); }).start(); } } } -
-
饿汉式单例: 类一开始就加载了一个对象private final static SingletonDemo1 SINGLE = new SingletonDemo1(); 不需要在创建对象
- 弊端:当该类占用的内存很大,一开始就加载进来了,而这些空间又没有使用,就会造成空间的浪费
//饿汉式单例模式 public class SingletonDemo1 { private SingletonDemo1(){} private final static SingletonDemo1 SINGLE = new SingletonDemo1(); public static SingletonDemo1 getSingle(){ return SINGLE; } } -
反射:由于反射的存在,任何代码都不安全,任何的私有都可以访问到
可以
// 反射破坏懒汉式单例 public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { //正常的创建 SingletonPattern single1 = single.newSingle(); // 利用反射 //反射获取类的构造器 Constructor<SingletonPattern> s = SingletonPattern.class.getDeclaredConstructor(null); s.setAccessible(true); //关闭处理器的安全检测,即可以访问私有属性 SingletonPattern single2 = s.newInstance(); // 反射创建 System.out.println(single1.hashCode()); // hashCode:1956725890 System.out.println(single2.hashCode()); // hashCode:356573597 }被反射破解的单例模式:
利用反射先破坏标志位,在创建对象
// 懒汉式单例 public class SingletonPattern { private volatile static SingletonPattern single; private static boolean lang = false ; private SingletonPattern(){ // 私有的构造函数,外部不能够创建类的对象 // 防止反射破解,辣鸡,还是能破解 破解你设置的标志位 if (lang == false){ lang =true; }else { throw new RuntimeException("不要试图用反射破坏,我很牛×的"); } } // 双重检测锁下的懒汉式单例模式 简称: DCL懒汉式单例 public static SingletonPattern newSingle(){ if(single == null){ synchronized (SingletonPattern.class){ if (single == null) single = new SingletonPattern(); // 不是原子型操作,可能发生指令重排 } } return single; } // 反射破坏懒汉式单例 public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException { //正常的创建 SingletonPattern single1 = single.newSingle(); // 利用反射 //反射获取类的构造器 Constructor<SingletonPattern> s = SingletonPattern.class.getDeclaredConstructor(null); //破解标志位 Field lang = SingletonPattern.class.getDeclaredField("lang"); lang.setAccessible(true); //关闭检测 lang.set(lang,false); // 破解 ,修改标志位 System.out.println(lang.get(lang)); // 返回这个对象上指定字段的值 s.setAccessible(true); //关闭处理器的安全检测,即可以访问私有属性 SingletonPattern single2 = s.newInstance(); System.out.println(single1.hashCode()); // hashCode:1956725890 System.out.println(single2.hashCode()); // hashCode:356573597 } }反射不能破解枚举类:idea中编译生成的目标文件以及把编译生成的.class用javap反编译成java文件都会显示枚举类所具有的是午餐构造函数,但是在使用反射获取构造函数时会抛出这个枚举类不具有无参构造,利用jad工具把class反编译回去,得到的是一个具有有参的构造函数,而当反射获取有参构造函数创建对象的时候,会抛出不能使用反射创建对象的异常(预期的结果)。
-
防止单例模式被破坏的解决方案:枚举类型的单例
13.深入理解CAS
原子类的操作(AtomicInteger等),底层运用CAS
CAS: 全称:compareAndSet(int expect, int update),当对象的值等于我所期望的值(expect)时,就修改为指定的值(update)。
缺点:
-
底层是自旋锁,循环耗时
-
由于是cpu的操作,一次性只能保证一个共享变量的原子性
-
会出现ABA问题:两个线程操作同一个资源,都把同一个资源拷到自己的工作区域,都是自己所期望的值,而其中一个线程修改了这个值又把修改的值还原回去,另外的线程不知道这个值是被修改过的

14.原子引用(AtomicStampedReference)
AtomicStampedReference
-
引入AtomicReference记录每次的变化,就可以解决ABA问题,对应的思想是乐观锁
AtomicReference:有版本号
: 每次对资源有所操作时都会有记录
- 也具有:compareAndSet(),参数:期望值,修改为多少,期望什么版本号,版本号变为多少,原子类的升级版,对每次值得变化都有记录

// CAS 在多线程的时候会出现ABA问题,数据A被线程修改为B在修改为A
// 利用原子引用AtomicReference,记录
public class CASTest {
public static void main(String[] args) {
AtomicStampedReference<Integer> i = new AtomicStampedReference<Integer>(1,1); // 参数:值 和 开始的版本号
int stamp = i.getStamp(); //获取最初版本号
new Thread(()->{
// ABA问题
System.out.println(i.compareAndSet(1, 2, i.getStamp(), i.getStamp() + 1));
// 还原数据
System.out.println(i.compareAndSet(2, 1, i.getStamp(), i.getStamp() + 1));
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 用最初的版本号尝试修改
System.out.println(i.compareAndSet(1, 3, stamp, i.getStamp() + 1));
//
System.out.println(i);
}).start();
}
}
15.各种锁
使用lock锁的时候,可以使用多把锁,但是每个锁都需要配对,
1.公平锁、非公平锁
-
公平锁:非常公平,不允许插队,必须先来后到
-
非公平锁:非常不公平,允许插队,(默认非公平锁)
-
Lock lock = (Lock) new ReentrantLock(true); //有参构造true,为公平锁
2.可重入锁
- 拿到了外面的锁之后,就可以拿到里面的锁,自动获得
3.自旋锁
// 自定义自旋锁 利用AtomicReference原子引用CAS实现 // 不具有版本号
//CAS底层不断循环,直到占用锁的线程释放后才能退出并获取锁
// 自定义自旋锁 利用AtomicReference原子引用CAS实现 // 不具有版本号
public class LockDemo {
AtomicReference<Thread> lock =new AtomicReference<>(); // 引用是线程默认为空
public void myLock(){
//获取此线程
Thread thread = Thread.currentThread();
//加锁
while (!lock.compareAndSet(null,thread)){ // 如果不为空就一直循环
}
System.out.println(Thread.currentThread().getName()+"获取");
}
public void unMyLock(){
Thread thread = Thread.currentThread();
lock.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"释放");
}
public static void main(String[] args) {
LockDemo lock = new LockDemo();
new Thread(()->{
lock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unMyLock();
}
},"A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
lock.myLock();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unMyLock();
}
},"B").start();
}
}
4.死锁
16.死锁排查
jps-1 //查看当前进程号,运行状态下终端输入
jstack 进程号 //查询到死锁的信息,堆栈中描述死锁进程之间获取的锁和等待的锁信息

浙公网安备 33010602011771号