学习视频来源:【狂神说Java】JUC并发编程最新版通俗易懂
一个十分优秀且励志的技术大牛+Java讲师,十分推荐他的频道:遇见狂神说
至于为啥这篇文章不是”全网最全“了,是因为狂神自己总结了一份学习笔记哈哈,本文是根据学习资料自己总结,方便以后查漏补缺
狂神置顶评论:
(置顶) 白漂有罪,拒绝白嫖,从点赞转发关注做起!
文章同步在公众号:狂神说 (公众号日更,记得关注)
视频文档地址:https://gitee.com/kuangstudy/openclass 记得三连➕转发本文截止至异步回调 ,从 JMM 开始分为下篇。
好,接下来就正式开始 ”勾优西 “ 的学习了~
前置知识:学习JUC前需要对java的多线程有一定的掌握。不过本期视频狂神也在第1~4节带着我们回顾了多线程的一些知识点:
详细资料可参考博客:厚积薄发打卡Day25 :狂神说Java之多线程详解<全网最全(代码+笔记)>
源码+官方文档:
其实官方文档就是源码编译出来的,其本质还是看源码,不过文档会比较方便学习
Java.Util.concurrent
包的缩写java.util.concurrent
java.util.concurrent.atomi
java.util.concurrentlocks
是 java.util 工具包、包、分类
回顾开启线程的三种方式:
Thread
Runnable
Callable
线程、进程,如果不能使用一句话说出来的技术,不扎实!
参考博客:什么是线程?什么是进程
进程:
官方定义:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础
简单理解:
进行(运行)中的程序,如打开任务管理器后中各种.exe程序
线程:
官方定义:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
简单理解:
线程是真正执行资源调度(使程序跑起来)的主体,一个进程往往可以包含多个线程,但至少包含一个线程。
如:开一个idea进程,其中至少有—> 线程1:输入代码,线程2:自动保存
老是强调多线程,那么 Java真的可以开启线程吗?
答案是 : 不能。查看Thread.start()
源码可得知:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// 本地方法,底层的C++ ,Java 无法直接操作硬件
private native void start0();
并发编程:并发、并行
public class Test1 {
public static void main(String[] args) {
// 获取cpu的核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
//输出为8
//说明笔者电脑为八核处理器
}
}
从源码回答,有理有据
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}
看源码说话嗷
//Object.wait()
public final void wait() throws InterruptedException {
wait(0);
}
//Thread.sleep()
public static native void sleep(long millis) throws InterruptedException;
来自不同的类
关于锁的释放
是否需要捕获异常?
需要。源码都在上面写的死死的,throws InterruptedException
都不知网上随手一搜的博客说wait() 不用捕获异常怎么搞得。
好吧,回看了下视频,狂神好像也翻车了哈哈
使用范围:
wait() 需要在同步代码块中使用
// wait、notify/notifyAll必须在同步控制块、同步方法里面使用。而sleep的使用在任意地方。
synchronized(x){
x.notify()
//或者wait()
}
sleep()可以在任何地方睡
作用对象:
方法属性:
static
回顾用传统的 synchronized 实现 线程安全的卖票例子
真正的多线程开发,公司中的开发,降低耦合性
线程就是一个单独的资源类,没有任何附属的操作!
对于资源类只有: 属性、方法
开启线程错误方式:
class Ticket implements Runnable{}
耦合度高,违背了oop(面向对象)思想
public class SaleTicket_WithSynchronized {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类, 把资源类丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "C").start();
}
}
//资源类 OOP:
class Ticket {
//属性、方法
private int number = 30;
//卖票方法
//用synchronized 上锁
public synchronized void saleTicket() {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,剩余" + number + "张");
}
}
}
用Lock接口
常用上锁语句:
Lock l = ...;
l.lock(); //加锁
try { // access the resource protected by this lock
}
finally
{ l.unlock(); //解锁}
公平锁与非公平锁(简单认识,后面详解)
上案例
// Lock 三部曲
// 1、 new ReentrantLock();
// 2、 lock.lock(); // 加锁
// 3、 finally=> lock.unlock(); // 解锁
public class SaleTicket_WithLock {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类, 把资源类丢入线程
Ticket ticket = new Ticket();
// @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 }
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "A").start();
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "B").start();
new Thread(() -> {
for (int i = 1; i < 40; i++) {
ticket.saleTicket();
}
}, "C").start();
}
}
class Ticket2 {
// 属性、方法
private int number = 30;
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(); // 解锁
}
}
}
Java中的锁——Lock和synchronized
4、相比于synchronized,Lock接口所具备的其他特性
①尝试非阻塞的获取锁tryLock():当前线程尝试获取锁,如果该时刻锁没有被其他线程获取到,就能成功获取并持有锁
②能被中断的获取锁lockInterruptibly():获取到锁的线程能够响应中断,当获取到锁的线程被中断的时候,会抛出中断异常同时释放持有的锁
③超时的获取锁tryLock(long time, TimeUnit unit):在指定的截止时间获取锁,如果没有获取到锁返回false
synchronized | Lock | |
---|---|---|
性质 | Java的内置关键字,在JVM层面上 | Java类(接口) |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 |
必须要手动释放锁,如果不释放锁,会造成死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。 如果A线程阻塞,B线程会一直等待 |
分情况而定,Lock有多个锁获取的方式:大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可,可手动设置) |
从pc(producer<–>consumer)问题看锁的本质
生产者消费者synchronized版本
案例:对一个数字不停进行 +1 -1操作,加完了减,减完了加
public class PC_WithSynchronized {
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();
}
}
},"ADD").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"MINUS").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();
}
}
此时输出:
加减交替执行,一片祥和~
..
ADD-->1
MINUS-->0
ADD-->1
MINUS-->0
ADD-->1
MINUS-->0
ADD-->1
MINUS-->0
ADD-->1
MINUS-->0
...
问题来了,现在只有"ADD"和"MINUS"两个线程执行操作,如果增加多两个线程会怎样呢?
于是在main方法中增加了"ADD2" 和 "MINUS2"两条线程:
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ADD2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"MINUS2").start();
出大问题了:出现了数据错误甚至死锁问题
原因如下:
用if判断只执行了一次判断,而wait()方法会导致的释放
具体说明:以两个加法线程ADD、ADD2举例:
上述情况称为:虚假唤醒
此时解决方法:将 if 改为 while
package com.kuangstudy.JUC;
/**
* @BelongsProject: itstack-demo-design-1-00
* @BelongsPackage: com.kuangstudy.JUC
* @Author: lgwayne
* @CreateTime: 2021-02-20 17:32
* @Description:
*/
public class PC_WithSynchronized {
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();
}
}
},"ADD").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"MINUS").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ADD2").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"MINUS2").start();
}
}
//判断等待-->业务代码-->通知
//数字:资源类
class Data{
//data类相同,不再赘述
}
结果一片祥和~
...
ADD2-->1
MINUS2-->0
ADD2-->1
MINUS2-->0
ADD2-->1
MINUS2-->0
ADD2-->1
MINUS2-->0
ADD2-->1
MINUS2-->0
...
通过Lock找到condition来配合控制对线程的唤醒
public interface Condition
Condition
因素出Object
监视器方法(wait
,notify
和notifyAll
)成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock
个实现。Lock
替换synchronized
方法和语句的使用,Condition
取代了对象监视器方法的使用。
public class PC_WithLock {
public static void main(String[] args) {
//主线程测试方法与上述一致,不再赘述
}
}
//判断等待-->业务代码-->通知
//数字:资源类
class Data_withLock {
//属性
private int number = 0;
private Lock lock = new ReentrantLock();
private 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 (InterruptedException 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 (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
两种在表现上有点相似,但是作用机制是有区别的
wait()来自Object await()来自condition
object wait() 不能单独使用,必须是在synchronized 下才能使用
为啥强制wait必须用在synchronized中?否则IllegalMonitorStateException
(1)为什么wait()必须在同步(Synchronized)方法/代码块中调用?
答:调用wait()就是释放锁,释放锁的前提是必须要先获得锁,先获得锁才能释放锁。
(2)为什么notify(),notifyAll()必须在同步(Synchronized)方法/代码块中调用?
答:notify(),notifyAll()是将锁交给含有wait()方法的线程,让其继续执行下去,如果自身没有锁,怎么叫把锁交给其他线程呢;
————————————————
原文链接:https://blog.csdn.net/qq_42145871/article/details/81950949
wait()必须要通过notify()方法进行唤醒
await() 必须是当前线程被排斥锁 lock 后,,获取到condition 后才能使用
条件变量的await()释放锁吗?
await()的作用是能够让其他线程访问竞争资源,所以挂起状态就是要释放竞争资源的锁。 在javaSE5的java.util.concurrent类库中,使用互斥并允许任务挂起的基本类就是Condition,你可以通过await()来挂起一个任务,当外部条件改变时,意味着某个任务可以继续执行,你可以通过signal()来通知这个任务。
每个lock()的调用必须紧跟一个try-finally子句,用来保证在所有情况下都可以释放锁。任务在可以调用await(),signal(),signalAll()之前,必须拥有这个锁
condition await() 必须通过 signal() 方法进行唤醒
用condition搭配状态位置试试
public class PC_WithAwakeByCondition {
public static void main(String[] args) {
Data_AwakeInOrder data = new Data_AwakeInOrder();
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 Data_AwakeInOrder{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1;
public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number != 1) {
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAA"+"-----number为->"+number);
// 唤醒,唤醒指定的人,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() + "=>BBBBBB"+"-----number为->"+number);
// 唤醒,唤醒指定的人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() + "=>CCCCCC"+"-----number为->"+number);
// 唤醒,唤醒指定的人A
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果:一片祥和~依序执行
...
线程A=>AAAAAA-----number为->1
线程B=>BBBBBB-----number为->2
线程C=>CCCCCC-----number为->3
线程A=>AAAAAA-----number为->1
线程B=>BBBBBB-----number为->2
线程C=>CCCCCC-----number为->3
...
看视频的时候看到有人用wait和notify 也实现了精准唤醒,接下来稍作尝试
public class PC_AwakeWithWait {
public static void main(String[] args) {
Data_AwakeInOrderByWait data = new Data_AwakeInOrderByWait();
new Thread(()->{
for (int i = 0; i < 100; i++) {
data.printA();
}
},"T_A").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
data.printB();
}
},"T_B").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
data.printC();
}
},"T_C").start();
}
}
//资源类
class Data_AwakeInOrderByWait{
private int number = 1;
public synchronized void printA() {
// 业务,判断-> 执行-> 通知
while (number != 1) {
// 等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAA->"+number);
// 唤醒,唤醒指定的人,B
number = 2;
this.notifyAll();
}
public synchronized void printB(){
// 业务,判断-> 执行-> 通知
while (number != 2) {
// 等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBB->"+number);
// 唤醒,唤醒指定的人,B
number = 3;
this.notifyAll();
}
public synchronized void printC(){
// 业务,判断-> 执行-> 通知
while (number != 3) {
// 等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "=>CCCCCC->"+number);
// 唤醒,唤醒指定的人,B
number = 1;
this.notifyAll();
}
}
结果也确实可以实现精准唤醒:
...
T_A=>AAAAAA->1
T_B=>BBBBBB->2
T_C=>CCCCCC->3
T_A=>AAAAAA->1
T_B=>BBBBBB->2
T_C=>CCCCCC->3
...
后来的技术是迭代出的更优秀的版本。等学成归来对比下这两种唤醒的效率
用4套八种加锁的例子对比理解锁的本质
连续十次执行线程,A/B两条线程交替执行
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
for (int i = 0; i < 10; i++) {
//锁的存在
new Thread(() -> {
phone.sendSms();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
}
}
}
class Phone {
public synchronized void sendSms() {
System.out.println("发短信###");
}
public synchronized void call() {
System.out.println("打电话******");
}
}
...
发短信###
打电话******
打电话******
打电话******
发短信###
...
在线程执行的过程中增加sleep()休眠(Phone类不变)
public static void main(String[] args) {
Phone phone = new Phone();
for (int i = 0; i < 10; i++) {
//锁的存在
new Thread(() -> {
phone.sendSms();
}, "A").start();
//增加0.5s的休眠时间
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
发短信###
打电话******
发短信###
打电话******
结果:
发短信—>打电话
依次执行
在Phone的 发短信的方法中增加2s的休眠(main方法不变)
class Phone {
public synchronized void sendSms() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信###");
}
public synchronized void call() {
System.out.println("打电话******");
}
}
结果:
发短信—>打电话
依次执行
总结
发短信
方法两个对象,两个调用者,两把
两个问题:
3. 增加了一个普通方法后!先执行发短信还是Hello? 普通方法
4. 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
例子:
public class Test2 {
public static void main(String[] args) {
// 两个对象,两个调用者,两把锁!
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
//锁的存在
new Thread(phone1::sendSms,"A").start();
new Thread(phone1::hello,"C1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone2::call,"B").start();
new Thread(phone2::hello,"C2").start();
}
}
class Phone2{
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信sssss <--"+Thread.currentThread().getName());
}
public synchronized void call(){
System.out.println("打电话cccc <--"+Thread.currentThread().getName());
}
public void hello(){
System.out.println("hello <--"+Thread.currentThread().getName());
}
}
运行结果(多次)
hello <--C1
打电话cccc <--B
hello <--C2
发短信sssss <--A
总结:
问题:
5.增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
6.两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
例子1(实例化一个对象)
public class Test3 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone3{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
运行结果:
发短信 -->打电话
例子2:(实例化两个对象)
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//另一个实例化对象
new Thread(()->{
phone2.call();
},"B").start();
运行结果:
发短信 -->打电话
总结:
问题:
7.1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
8.1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
例子7:
public class Test4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone4{
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
例子8:
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
插播一个知识点,双冒号(::)
表达式
详见:JAVA 8 ‘::’ 关键字,带你深入了解它!
语法种类 示例 引用静态方法 ContainingClass::staticMethodName 引用特定对象的实例方法 containingObject::instanceMethodName 引用特定类型的任意对象的实例方法 ContainingType::methodName 引用构造函数 ClassName::new
举例:
//表达式转换
new Thread(() -> {
phone.call();
}, "B").start();
//可以转换成:
new Thread(phone::call,"B").start();
在狂神多线程 的 三个线程不安全的案例中有提及,今天在并发角度测试下
public class TestList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
执行程序后会抛出:
java.util.ConcurrentModificationException
异常(后续抛出同样异常不做细说)
也就是不同线程同时操作了同一list索引元素抛出的异常。
解决方法:
public static void main(String[] args) {
// List list = new ArrayList<>();
//1.集合自带的线程安全的list
// List list = new Vector<>();
//2.Collections工具类强行上锁
// List list =Collections.synchronizedList(new ArrayList<>());
//3.用JUC包下的读写数组CopyOnWriteArrayList,读写分离
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
例子:
public class TestSet {
public static void main(String[] args) {
Set<Object> set = new HashSet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
解决方法:
public static void main(String[] args) {
// Set
//1.用collections工具类强行上锁:
// Set
//2.用CopyOnWriteArrayList 实现的CopyOnWriteArraySet
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
hashSet本质:
public HashSet() {
map = new HashMap<>();
}
// add set 本质就是 map key是无法重复的!
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object(); // 不变的值,做value
例子:
public class TestMap {
public static void main(String[] args) {
// 默认等价于什么? new HashMap<>(16,0.75);
// Map map = new HashMap<>();
//1.用juc下线程安全的map
Map<String, Object> map = new ConcurrentHashMap<>();
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();
}
}
}
下列博客对callable有详细的介绍,值得一看✌
Callable接口及Futrue接口详解
目录
- Callable接口
- Futrue接口
- 1.使用Callable和Future的完整示例
- 2.使用Callable和FutureTask的完整示例
- 3.使用Runnable来获取返回结果的实现
有两种创建线程的方法-一种是通过创建Thread类,另一种是通过使用Runnable创建线程。但是,Runnable缺少的一项功能是,当线程终止时(即run()完成时),我们无法使线程返回结果。为了支持此功能,Java中提供了Callable接口。
public class CallableFutureTest {
public static void main(String[] args) throws Exception {
// FutureTask is a concrete class that implements both Runnable and Future
FutureTask[] randomNumberTasks = new FutureTask[5];
for (int i = 0; i < 5; i++) {
Callable callable = new CallableExample();
// Create the FutureTask with Callable
randomNumberTasks[i] = new FutureTask(callable); //适配器模式
// As it implements Runnable, create Thread with FutureTask
Thread t = new Thread(randomNumberTasks[i], String.valueOf(i));
t.start(); //结果会被缓存,效率高
}
for (int i = 0; i < 5; i++) {
// As it implements Future, we can call get()
System.out.println(randomNumberTasks[i].get());
// This method blocks till the result is obtained
// The get method can throw checked exceptions
// like when it is interrupted. This is the reason
// for adding the throws clause to main
}
}
}
class CallableExample implements Callable {
public Object call() throws Exception {
Random generator = new Random();
Integer randomNumber = generator.nextInt(5);
TimeUnit.SECONDS.sleep(1);
return randomNumber;
}
}
顾名思义:倒计时锁存器
不管你线程中间执行的情况,结果若是线程执行完了,那就再执行最后语句,如果没达到条件就一直等
(领导:不看过程只看结果)
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数是6,必须要执行任务的时候,再使用!
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" Go out");
countDownLatch.countDown(); // 数量-1
},String.valueOf(i)).start();
}
countDownLatch.await(); // 等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
1 Go out
5 Go out
4 Go out
3 Go out
2 Go out
6 Go out
Close Door
如果创建了7条任务线程,但只countDown了6次,那么将会一直阻塞线程
总结:
CountDownLatch countDownLatch = new CountDownLatch(6);
创建线程总数
countDownLatch.countDown();
实行完线程数-1
countDownLatch.await();
等待计数器归零,然后再向下执行
每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续执行!
见名之意:循环障碍,与8.1 方法相反的,加法计时器
public class CyclicBarrierDemo {
public static void main(String[] args) {
/**
* 集齐7颗龙珠召唤神龙
*/
// 召唤龙珠的线程:7条线程
// 创建成功后执行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(Thread.currentThread().getName() + "收集到第" + temp + "个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e){
e.printStackTrace();
}
}).start();
}
}
}
创建线程Barrier后开启线程执行:
CyclicBarrier(int parties, Runnable barrierAction)
创建一个新的 CyclicBarrier ,当给定数量的线程(线程)等待时,它将跳闸,当屏障跳闸时执行给定的屏障动作,由最后一个进入屏障的线程执行。
并在调用await()方法时自动+1:
原创来源: lambda里面赋值局部变量必须是final原因
简单复习下lambda函数:匿名实现函数式接口的方法
Runnable r= new Runnable() {
@Override
public void run() {
System.out.println("这是常用实例化函数式接口");
}
};
Runnable rl = ()->System.out.println("这是lambda实例化接口");
四、为什么lambda里面要访问局部变量必须是final
现在再来解释为什么lambda里面要访问局部变量必须是final?首先:lambda表示可以无限制捕获实例变量(即表达式主体中的引用)和静态变量
但是,局部变量必须是显示声明为final或事实上是final
那么这lambda里面要赋值局部变量必须是final有什么关系?
实例变量:存储在堆中
局部变量:则保存在栈上
lambda表达式以内联的形式创建一个函数式接口的实例,保存在堆中,而局部变量则保存在栈中,可能造成实例对象得生命周期很有可能超过局部变量得生命周期
总结:
Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量,因此需要final作限制
// int k = 1;
// Runnable rl = ()->System.out.println("这是lambda实例化接口"+k);//正常
// int k = 1;
// Runnable rl = ()->System.out.println("这是lambda实例化接口"+k);//Variable used in lambda expression should be final or effectively final
// k=3;
final int k = 1;
Runnable rl = ()->System.out.println("这是lambda实例化接口"+k);
见名之意:信号量,可近似看作资源池。
举例子(抢车位):
public class SemaphoreDemo {
public static void main(String[] args) {
// 线程数量:停车位! 限流!
//创建只有3个停车位的停车场
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);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {、
//停车结束,离开车位
semaphore.release(); // release() 释放
}
},String.valueOf(i)+"号车-->").start();
}
}
}
1号车-->抢到车位
3号车-->抢到车位
2号车-->抢到车位
2号车-->离开车位
1号车-->离开车位
4号车-->抢到车位
3号车-->离开车位
6号车-->抢到车位
5号车-->抢到车位
5号车-->离开车位
6号车-->离开车位
4号车-->离开车位
每个人都能停到车
ReadWriteLock 接口
所有已知实现类: ReentrantReadWriteLock
public class TestReadWriteLock {
public static void main(String[] args) {
//未上锁:
// MyCache myCache = new MyCache();
//上了读写锁:
MyCacheWithLock myCache = new MyCacheWithLock();
//写入:
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.write(temp + "", temp + "");
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.read(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCacheWithLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁:对数据更精准控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock lock = new ReentrantLock();
//写数据:只希望有一个线程在执行
public void write(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 void read(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取数据:" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取数据完成-->" + o);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
/**
* 存入数据过程上锁,安全
*/
}
/**
* 未上锁:
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
//写数据:
public void write(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入完成!");
}
//读数据:
public void read(String key) {
System.out.println(Thread.currentThread().getName() + "读取数据:" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取数据完成-->" + o);
}
/**
* 运行结果:
* 写入线程会被 读取线程中断,造成脏读,对数据不安全
*/
}
Interface BlockingQueue
是数据结构中队列Queue的延展使用。
什么情况下我们会使用阻塞队列?
对线程并发处理,线程池!
学会使用队列的操作: 添加 和 移除
第一组 | 第二组 | 第三组 | 第四组 | |
---|---|---|---|---|
实现结果 | 有返回值会抛异常的方法 | 有返回值但不抛异常的方法 | 线程会阻塞和等待的方法(一直等待) | 超时等待的方法(过时不候) |
添加元素 | add(E e) | offer(E e) | put(E e)(无返回值) | offer(E e, long timeout, TimeUnit unit) |
移除元素 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) |
检测队首元素 | element() | peek() | 无。上述两个方法会throws InterruptedException |
无。上述两个方法会throws InterruptedException |
第一组:
@Test
public void test1(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a")); //true
System.out.println(blockingQueue.add("b")); // true
System.out.println(blockingQueue.add("c")); //true
// System.out.println(blockingQueue.add("d"));
// 会抛异常:java.lang.IllegalStateException: Queue full
System.out.println("=============================");
System.out.println(blockingQueue.remove()); //a
System.out.println(blockingQueue.remove()); //b
System.out.println(blockingQueue.remove()); //c
// System.out.println(blockingQueue.remove());
//会抛出 NoSuchElementException异常
System.out.println(blockingQueue.element()); //如无元素则会报NoSuchElementException异常
}
第二组:
@Test
public void test2(){
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));//true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d"));//false
System.out.println("=============================");
System.out.println(blockingQueue.poll()); //a
System.out.println(blockingQueue.poll()); //b
System.out.println(blockingQueue.poll()); //c
System.out.println(blockingQueue.poll()); //null
System.out.println("=============================");
System.out.println(blockingQueue.offer("e"));//true
System.out.println(blockingQueue.offer("f"));//true
System.out.println(blockingQueue.peek()); //e : 队首元素
}
第三组:
@Test
public void test3() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("d"); // 队列没有位置了,一直阻塞
System.out.println("=============================");
System.out.println(blockingQueue.take()); //a
System.out.println(blockingQueue.take()); //b
System.out.println(blockingQueue.take()); //c
// System.out.println(blockingQueue.take()); // 队列没有值可以取,一直阻塞
}
第四组:
@Test
public void test4() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));//true
System.out.println(blockingQueue.offer("b"));//true
System.out.println(blockingQueue.offer("c"));//true
System.out.println(blockingQueue.offer("d", 2, TimeUnit.SECONDS));//false
System.out.println("=============================");
System.out.println(blockingQueue.poll()); //a
System.out.println(blockingQueue.poll()); //b
System.out.println(blockingQueue.poll()); //c
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS)); //null
}
没有容量==> 进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素,put
了一个元素,必须从里面先take
取出来,否则不能再put
进去值!
public class TestSynchronousQueue {
public static void main(String[] args) {
SynchronousQueue<String> bq = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " put 1");
bq.put("1");
System.out.println(Thread.currentThread().getName() + " put 2");
bq.put("2");
System.out.println(Thread.currentThread().getName() + " put 3");
bq.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " get =>" + bq.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " get =>" + bq.take());
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + " get =>" + bq.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();
}
}
/**
T1 put 1
T2 get =>1
T1 put 2
T2 get =>2
T1 put 3
T2 get =>3
*/
学习目标:
在系统开发过程中,我们经常会用到池化技术来减少系统消耗,提升系统性能。
说人数: 简单点来说,就是提前保存大量的资源,以备不时之需,池化技术就是通过复用来提升性能。
常见池:
使用内存池的优点
使用内存池的缺点
缺点就是很可能会造成内存的浪费,因为要使用内存池需要在一开始分配一大块闲置的内存,而这些内存不一定全部被用到。
阿里巴巴 Java 开发手册中 对线程池的规范:
Executors 工具类中3大方法(详见API)
public static ExecutorService newSingleThreadExecutor()
//创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程。
public static ExecutorService newFixedThreadPool(int nThreads)
//创建一个线程池,使用固定数量的线程操作了共享无界队列
public static ExecutorService newCachedThreadPool()
//创建一个线程池,根据需要创建新的线程,但在可用时将重用先前构建的线程。
public class TestMethods {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();
// 单个线程:此时只有pool-1-thread-1
// ExecutorService threadPool = Executors.newFixedThreadPool(5);
// 创建一个固定的线程池的大小: 此时最多有pool-1-thread-5 ok
ExecutorService threadPool = Executors.newCachedThreadPool();
// 可伸缩的,遇强则强,遇弱则弱 : 此时最多开启到pool-1-thread-31 ok 去执行
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();
}
}
}
源码分析:
//创建单一线程的线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//创建固定线程数的线程池
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
//创建代缓存的线程池:
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;
}
银行办理业务举例:
public class TestDefPool {
public static void main(String[] args) {
// 自定义线程池!工作 ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(
2, //当天值班员工(核心线程池大小)
5, //柜台总数:最大核心线程池大小
3, //临时工的超时等待:超时了没有人调用就会释放
TimeUnit.SECONDS, //超时等待单位
new LinkedBlockingDeque<>(3),//候客区:阻塞队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()); //满了后告诉客人办不了业务了:抛异常RejectedExecutionException
try {
// 最大承载:Deque + max 此处为:5+3=8
// 超过 RejectedExecutionException
for (int i = 1; i <= 9; i++) {
// 使用了线程池之后,使用线程池来创建线程
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " ok");
});
}
} catch (
Exception e)
{
e.printStackTrace();
} finally
{
// 线程池用完,程序结束,关闭线程池
threadPool.shutdown();
}
}
}
/**
1. new ThreadPoolExecutor.AbortPolicy()
// 银行满了,还有人进来,不处理这个人的,抛出异常:RejectedExecutionException
2.new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
//公司叫你来银行办业务,银行满人办不了,回公司找人办
//新开辟的线程搞不定调用主线程
3.new ThreadPoolExecutor.DiscardPolicy()
//银行办不了了,不办你业务
//队列满了,丢掉任务,不会抛出异常!
4.new ThreadPoolExecutor.DiscardOldestPolicy()
//排队人满了,你看看最早开始的客户搞定没,没搞定就被拒绝了。
//队列满了,尝试去和最早的竞争,也不会抛出异常!
*/
根据上述参数对线程池调优:主要针对线程池大小调优
IO密集型
一般来说:文件读写、DB读写、网络请求等。
这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
CPU密集型
一般来说:计算型代码、Bitmap转换、Gson转换等。
这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
// 获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());
Java 1.8之前的程序员:泛型、枚举、反射
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口(截至目前用的最多的是Runnable
接口)
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
// 超级多FunctionalInterface
// 简化编程模型,在新版本的框架底层大量应用!
四大函数式接口:
package java.util.function;
消费型接口
/**
* Consumer 消费型接口: 只有输入,没有返回值
*/
public class Demo03 {
public static void main(String[] args) {
// Consumer consumer = new Consumer() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer = (str)->{System.out.println(str);};
//可以简化为:
// “函数式接口 变量名 = 类实例::方法名” 的方式对该方法进行引用
Consumer<String> consumer2 = System.out::println;
consumer.accept("这是消费型接口----1");
consumer2.accept("这是消费型接口----2");
}
}
双冒号用法:Java8新特性之方法引用中的双冒号
System.out::print 与 System.out.print的区别
双冒号运算操作符是类方法的句柄:
lambda 表达式的一种简写,这种简写的学名叫 eta-conversion 或者叫 η-conversion。
双冒号运算就是 Java 中的
[方法引用]
。[方法引用]
格式为:类名::方法名
。//表达式1: person -> person.getAge(); //可以替换成1`: Person::getAge //表达式2: () -> new HashMap<>(); //可以替换成2` HashMap::new
/**
* Function 函数型接口, 有一个输入参数,有一个输出
* 只要是 函数型接口 可以 用 lambda表达式简化
*/
public class Demo01 {
public static void main(String[] args) {
//
// Function function = new Function() {
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function<String,String> function = str-> str;
System.out.println(function.apply("This is Function"));
}
}
断定型接口:有一个输入参数,返回值只能是 布尔值
/**
* 断定型接口:有一个输入参数,返回值只能是 布尔值!
*/
public class Demo02 {
public static void main(String[] args) {
// 判断字符串是否为空
// Predicate predicate = new Predicate(){
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> predicate = String::isEmpty;
System.out.println(predicate.test(""));//true
System.out.println(predicate.test("Test"));//false
}
}
供给型接口
/**
* Supplier 供给型接口 没有参数,只有返回值
*/
public class Demo04 {
public static void main(String[] args) {
// Supplier supplier = new Supplier() {
// @Override
// public Integer get() {
// System.out.println("get()");
// return 1024;
// }
// };
Supplier supplier = ()-> 1024;
System.out.println(supplier.get());
}
}
详见文章:JAVA流式计算
流的简单介绍
Java 8 中,引入了流(Stream)的概念,利用提供的Stream API,我们可以方便的操作集合数据,这种方式很类似于使用SQL对数据库的操作。
如何生成流
利用Stream API
1、所有继承自Collection的接口都可以直接转化为流:
List
l1 = Arrays.asList(1, 2, 3, 4, 5); Stream stream = l1.stream(); Map s1 = new HashMap<>(); Stream > stream1 = s1.entrySet().stream(); 2、利用Arrays类中的stream()方法:
Integer[] i1 = new Integer[]{1,2,3,4,5}; Stream
stream = Arrays.stream(i1); 3、使用Stream类中的静态方法:
Stream
stream = Stream.of(1,2,3,4,5); 4、利用BufferedReader读取文本中的内容转化为流:
BufferedReader reader = new BufferedReader(new FileReader("D:\\stream.txt")); Stream
stream = reader.lines(); 我们经常使用的还是方式1,将集合转化为流。
举例:
大数据:存储 + 计算
集合、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);
//上述操作可以简写为:
list.stream()
.filter(u-> u.getId()%2==0)
.filter(u-> u.getAge()>23)
.map(u-> u.getName().toUpperCase())
.sorted(Comparator.reverseOrder())
.limit(1)
.forEach(System.out::println);
}
}
涉及方法说明:
filter()
Stream<T> filter(Predicate<? super T> predicate)
//用于对数据进行过滤,筛选出符合条件的数据;
//接收一个Predicate的函数接口,用于进行条件的过滤;返回符合条件的数据组成的一个新的流;
map()
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//对数据进行处理,将T类型的对象转化为R类型的对象,简单来说就是对流中的数据进行同一项操作; //接收一个Function的函数接口,用于对数据处理;返回R类型的数据组成的一个新的流;
sorted()
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);
//将流中的数据进行排序,然后排序后的数据组合成一个新的流;
//无参的方法,默认按照升序升序进行排列;
//有参的方法,需要传入Comparator接口的一个实现类,按照该实现进行排序操作;
limit()
Stream<T> limit(long maxSize)
//返回由该流的元素组成的流,截断长度不能超过maxSize 。
foreach()
void forEach(Consumer<? super T> action)
//对此流的每个元素执行操作。
/**最后的System.out::println打印的是传入的参数,
也就是Customer的参数泛型T,这个T就是从list的流对象获取的T,最终都是List对象创建时指定的泛型*/
从JDK1.7开始,Java提供Fork/Join框架用于并行执行任务,它的思想就是讲一个大任务分割成若干小任务,最终汇总每个小任务的结果得到这个大任务的结果。
这种思想和MapReduce
很像(input --> split --> map --> reduce --> output)
相关文章:
我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一个线程内完成:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
还有一种方法,可以把数组拆成两部分,分别计算,最后加起来就是最终结果,这样可以用两个线程并行执行:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
如果拆成两部分还是很大,我们还可以继续拆,用4个线程并行执行:
┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘
这就是Fork/Join任务的原理:判断一个任务是否足够小,如果是,直接计算,否则,就分拆成几个小任务分别计算。这个过程可以反复“裂变”成一系列小任务。
简单来说就是种分而治之
的思想
Fork/Join 特点:工作窃取(work-stealing)
假如我们需要做一个比较大的任务,我们可以把这个任务分割为若干互不依赖的子任务,为了减少线程间的竞争,于是把这些子任务分别放到不同的队列里,并为每个队列创建一个单独的线程来执行队列里的任务,线程和队列一一对应,比如A线程负责处理A队列里的任务。
但是有的线程会先把自己队列里的任务干完,而其他线程对应的队列里还有任务等待处理。干完活的线程与其等着,不如去帮其他线程干活,于是它就去其他线程的队列里窃取一个任务来执行。
而在这时它们会访问同一个队列,所以为了减少窃取任务线程和被窃取任务线程之间的竞争,通常会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。
工作窃取算法:
- 优点是充分利用线程进行并行计算,并减少了线程间的竞争,
- 缺点是在某些情况下还是存在竞争,比如双端队列里只有一个任务时。并且消耗了更多的系统资源,比如创建多个线程和多个双端队列。
举例:
/**
* 对比 遍历、Fork/Join、和流计算的运算速度
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1(); // 12224
test2(); // 10038
test3(); // 153
}
// 普通程序员:遍历
public static void test1() {
Long sum = 0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_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 ForkJoinDemo(0L, 10_0000_0000L);
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();
// Stream并行流 () (]
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("sum =>" + sum +" 时间:" + (end - start));
}
}
/**
* 求和计算的任务!
* 3000 6000(ForkJoin) 9000(Stream并行流)
* // 如何使用 forkjoin
* // 1、forkjoinPool 通过它来执行
* // 2、计算任务 forkjoinPool.execute(ForkJoinTask task)
* // 3. 计算类要继承 ForkJoinTask
*/
class ForkJoinDemo extends RecursiveTask<Long> {
private Long start; // 1
private Long end; // 1990900000
// 临界值
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();
}
}
}
小结:
Fork/Join是一种基于“分治”的算法:通过分解任务,并行执行,最后合并结果得到最终结果。
ForkJoinPool
线程池可以把一个大任务分拆成小任务并行执行,任务类必须继承自RecursiveTask
或RecursiveAction
。
使用Fork/Join模式可以进行并行计算以提高效率。
Java内部的ajax方法:Future 设计==》对将来的某个事件的结果进行建模
狂神此处讲解的是
CompletableFuture
类,但是网上文章大多都以CallBack
接口进行讲解,以后有空再研究。
/**
* 异步调用: CompletableFuture
* // 异步执行
* // 成功回调
* // 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 没有返回值的 runAsync 异步回调
// CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
// try {
// TimeUnit.SECONDS.sleep(2);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println(Thread.currentThread().getName()+"runAsync=>Void");
// });
//
// System.out.println("1111");
//
// completableFuture.get(); // 获取阻塞执行结果
// 有返回值的 supplyAsync 异步回调
// ajax,成功和失败的回调
// 返回的是错误信息;
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());
/**
* succee Code 200
* error Code 404 500
*/
}
}