1.什么是JUC
java.util工具包、包、分类
Runnable 没有返回值、效率相比于Callable相对较低
2.线程和进程
进程:一个程序,qq.exe 程序的集合
一个进程往往可以包含多个线程,至少包括一个!
java默认有两个线程。main GC
native:
native是一个计算机函数,一个Native Method就是一个Java调用非Java代码的接口。方法的实现由非Java语言实现,比如C或C++。
**java真的可以开启线程吗?**不可以的
new thread().start();start方法底层调用的是一个本地方法(native),调用的底层的C++,java无法操作硬件。所以java不可以开启线程
并行,并发
并发(多个线程操作一个资源)交替的
- CPU在一核的情况下,模拟出来多条线程,解决方法,天下武功,唯快不破,快速交替
并行(多个人一起走)同时的
- CPU多核,多个线程可以同时执行;线程池
查看cpu的核数的三种方式:
**第一种:**此电脑–>管理–>设备管理器
**第二种:**任务管理器–>CPU—>逻辑处理器
**第三种:**通过java
public class Test1 {
public static void main(String[] args) {
//获取CPU的核数
//CPU密集型 Io密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
线程有几个状态
通过源码分析:线程有六个状态NEW /RUNNABLE/BLOCKED/WAITING/TIMED_WAITING/TERMINATED
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//死死的等
WAITING,
//超时等待
TIMED_WAITING,
//阻止
TERMINATED;
}
wait/sleep区别
- 来自不同的类
- wait—>Object
- sleep—>Thread
- 关于锁的释放
- wait会释放锁
- sleep不会释放锁抱着锁睡觉
- 使用的范围不同
- wait必须在同步代码块中使用
- sleep可以在任何的地方使用
- 是否需要捕获异常
- wait不需要捕获异常
- sleep需要捕获异常
3.Lock锁(重点)
传统的synchronized
通过传统的synchronized锁来解决卖票的问题
/*
* 真正的多线程开发,公司中的开发 降低耦合性。
*线程就是一个单独的资源类,没有任何附属的操作
* 1.属性
* 2.方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多个线程操作同一个资源类
Ticket ticket =new Ticket();
//@FunctionalInterface 函数式接口, jdk1.8之后 lambda表达式 (参数)->{代码块}
//通过资源来操作 ticket.sale()来调用操作买票 lambda表达式实现的是Runnable接口
//并发三个线程操作同一个资源,ticket类就解耦,不需要实现runnable接口
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}},"A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}},"B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
ticket.sale();
}},"C").start();
}
}
//一个标准的资源类 OOP原则
class Ticket{
//属性、方法
private int num =50;
//卖票的方式
//使用传统的synchronized
//锁:锁的是对象和class
public synchronized void sale(){
//如果num>0就可以卖
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+num-- +"票"
+"剩余了"+num);
}
}
}
Lock接口
lock接口的三个实现类:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mhiijwry-1606389346898)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201015162101314.png)]
进入ReentrantLock锁:
RenntrantLock锁有两个构造:
一个无参构造:它是一把非公平锁
一个是一个boolean值参数的锁:如果为true则为公平锁,如果为false则为非公平锁
公平锁与非公平锁的区别:
公平锁:十分的公平,可以先来后到
非公平锁:十分不公平,可以插队**(默认)**
使用lock的三步使用
//第一步创建锁
Lock lock= new ReentrantLock();
//第二步打开锁
lock.lock();
try{
//业务代码
}
finally{
//第三步关锁
lock.unlock();
}
使用lock锁完成卖票的代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo02 {
public static void main(String[] args) {
Ticket02 ticket02 = new Ticket02();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket02.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket02.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket02.sale();
}
},"C").start();
}
}
//lock锁
class Ticket02{
//属性方法
private int num =50;
Lock lock =new ReentrantLock();
public void sale(){
//解锁
lock.lock();
//业务代码块
try {
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+num-- +"票"
+"剩余了"+num);
}
} finally {
//解锁
lock.unlock();
}
}
}
Lock锁与Synchronized锁的区别
- synchronized是一个内置的java关键字,Lock是一个java类
- synchronized是无法判断获取锁的状态,Lock是可以判断是否获得锁
- synchronized会自动释放锁,lock锁必须要手动释放锁,如果不释放锁就会发生死锁
- synchronized线程1(获得锁)和线程2(等待)如果线程1阻塞那么线程2会一直等下去但是lock锁不会一直等待下去。lock.trylock();
- synchronized是一个可重入锁、不可以中断的,非公平的:Lock,是一个可重入锁,可以中断的,可以设置公平性。在RenntrantLock(参数“true”或者false)
- synchronized适合少量的代码同步问题,lock可以锁大量同步代码
4.生产者和消费者问题
第一种:用synchronized锁来实现生产者和消费者问题:
生产者和消费者都各有一个时:此线程可以正常运行
//通过synchronized来实现生产者和消费者模式
public class Sale01 {
public static void main(String[] args) {
date date = new date();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"P").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C01").start();
}
}
//资源类
class date{
//属性
private int num=0;
//方法
// 生产的方法
public synchronized void product() throws InterruptedException {
if(num != 0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
this.notifyAll();
}
//方法
//售卖的方法
public synchronized void sale() throws InterruptedException {
if(num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
this.notifyAll();
}
}
但是当有4个线程,两个生产者和两个消费者时就会出现问题:出现问题的原因是虚假唤醒
wait()方法:虚假唤醒
解决方案:等待应该一直 存在在while循环中,
将if循环改为while循环即可解决虚假唤醒的问题
//通过synchronized来实现生产者和消费者模式
public class Sale01 {
public static void main(String[] args) {
date date = new date();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"P1").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"P2").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C01").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
try {
date.sale();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C02").start();
}
}
//资源类
//判断等待、业务、通知
class date{
//属性
private int num=0;
//方法
// 生产的方法
public synchronized void product() throws InterruptedException {
while(num != 0){
//等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
this.notifyAll();
}
//方法
//售卖的方法
public synchronized void sale() throws InterruptedException {
while (num==0){
//等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
this.notifyAll();
}
}
第二种:用Lock锁来实现生产者和消费者问题:
1.传统的锁解决线程通信问题
2.lock锁解决线程通信问题
Condition:监视器
代码实现:不过此部分的代码实现的功能紧紧跟传统的synchronized锁实现的一样,但是condition并不仅仅代替synchronized锁,任何一个新技术的出现并不仅仅覆盖原来的技术,优势和补充。
import java.nio.file.FileAlreadyExistsException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Sale02 {
public static void main(String[] args) {
Date02 date02 = new Date02();
new Thread(()->{
for (int i = 0; i < 40; i++) {
date02.product();
}
},"p1").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
date02.product();
}
},"p2").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
date02.sale();
}
},"c1").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
date02.sale();
}
},"c2").start();
}
}
//资源类
class Date02{
//属性
private int num=0;
//方法创造产品的方法
Lock lock =new ReentrantLock();
//condition
Condition condition = lock.newCondition();
// condition.await();//等待
// condition.signalAll();//唤醒
public void product(){
lock.lock();
try{
while (num!=0){
//等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//做出产品
num++;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
condition.signalAll();
}finally {
lock.unlock();
}
}
public void sale(){
lock.lock();
try {
while (num==0){
//等待
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费产品
num--;
System.out.println(Thread.currentThread().getName()+"==>"+num);
//通知
condition.signalAll();
} finally {
lock.unlock();
}
}
}
以上代码出现的问题:
其线程的执行时随机的,并不是有序的。
p1 —>c1\c2并不是p1–>c1和p2–>c2.
有序执行:精准的通知和唤醒
3.通过condition来做到精准的线程执行
有多个线程A,B,C,通过condition监视器来完成线程的顺序执行A–>B–>C
通过多个监视器去通知
package com.mahui.pc;
/*
* 线程的执行顺序
* A-->B-->C
* */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class A {
public static void main(String[] args) {
Date03 date03 = new Date03();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date03.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date03.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
date03.printC();
}
},"C").start();
}
}
//资源类
class Date03{
//创建锁
Lock lock =new ReentrantLock();
//创建监视器
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
//标志位
private int num = 1;
//业务的三步:判断-->运行--->通知
public void printA(){
//获得锁
lock.lock();
try {
while (num != 1){
//等待
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//改变标志位
num =2;
System.out.println(Thread.currentThread().getName()+"===>AAAAAAA");
//唤醒B线程
condition2.signal();
}
finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while(num!=2){
//等待
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//该变标志位
num=3;
System.out.println(Thread.currentThread().getName()+"===>"+"BBBBBBBB");
//唤醒c线程
condition3.signal();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (num!=3){
//等待
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num=1;
System.out.println(Thread.currentThread().getName()+"===>"+"CCCCCCC");
//唤醒线程A
condition1.signal();
} finally {
lock.unlock();
}
}
}
运行结果:
5. 深刻理解锁
Test1
一个对象两个方法加锁
对于以下的代码思考是会进行先打电话还是先发短信呢?
import java.util.concurrent.TimeUnit;
/*
* 对于phone是先打电话还是先发短信
* */
public class Test01 {
public static void main(String[] args) {
//synchronized 锁的对象是方法的调用者
//两个方法用的是用一个锁
//只有一个对象phone
Phone phone =new Phone();
new Thread(()->{
phone.sendms();
},"A").start();
//A执行完之后休眠1秒
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果是:先发短信然后打电话。
分析原因:
synchronized锁的对象是方法的调用者,在本例方法的调用者是phone,只有一个对象phone,所以两种方法持有一把锁,发短信拿到锁然后释放锁之后打电话才会拿到锁。sleep是不会释放锁的。
如果sleep4秒?
同样的,先是发短信之后是打电话。
Test2
一个对象,一个方法加锁一个不加锁
分析下列代码会先输出什么?
import java.util.concurrent.TimeUnit;
public class Test02 {
public static void main(String[] args) {
//synchronized 锁的对象是方法的调用者
Phone02 phone =new Phone02();
new Thread(()->{
phone.sendms();
},"A").start();
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone02{
public synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁
public void hello(){
System.out.println("hello");
}
}
结果是:先输出hello后输出发短信
分析原因:在本例中hello方法并没有锁,不受锁的影响,所以它并不需要等待sendms方法执行完之后再去执行,而sendms方法休眠了四秒,所以hello先输出,而发短信后输出。
Test3
两个对象
分析下列代码先输出什么?
import java.util.concurrent.TimeUnit;
public class Test03 {
public static void main(String[] args) {
//synchronized 锁的对象是方法的调用者
//有了两个对象有两把锁
Phone03 phone1 =new Phone03();
Phone03 phone2 =new Phone03();
new Thread(()->{
phone1.sendms();
},"A").start();
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone03{
public synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
输出结果是:
分析原因:一定是先输出打电话,后输出发短信,因为这个例子中有两个对象phone1和phone2所以每个对象都有一把锁,在本例中call方法不需要等phone1的锁,它自己有自己的锁,而sendms方法休眠了四秒钟所以,先输出打电话后输出发短信。
Test4
静态方法:一个对象 两个静态方法static判断先输出什么后输出什么?
import java.util.concurrent.TimeUnit;
public class Test04 {
public static void main(String[] args) {
Phone04 phone1 =new Phone04();
new Thread(()->{
phone1.sendms();
},"A").start();
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.call();
},"B").start();
}
}
class Phone04{
//synchronized 锁的对象是方法的调用者
//static 静态方法
//类一加载就有了!锁的是class 全局唯一所以只有一把锁
public static synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
输出结果:
分析原因:在该例中,方法使用了static修饰,其是一个静态的方法,类一加载就有的,锁的是class,全局唯一的所以也只有一把锁,所以当sendms方法释放锁之后,call才会获得锁。因此发短信先输出之后是打电话输出。
Test5
问题:两个对象两个静态的方法先输出什么?
import java.util.concurrent.TimeUnit;
public class Test05 {
public static void main(String[] args) {
//synchronized 锁的对象是方法的调用者
//两个方法用的是用一个锁
//只有一个对象phone
Phone05 phone1 =new Phone05();
Phone05 phone2 =new Phone05();
new Thread(()->{
phone1.sendms();
},"A").start();
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone05{
public static synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
输出的结果:
分析原因:加了static之后锁的是class,phone1和phone2的class文件是同一个,所以塔两使用的是同一把锁
所以需要等待sendms方法释放锁之后,call方法才能获得锁
Test6
问题:一个普通的同步方法,一个静态的同步方法,两个对象
package com.mahui.demo03;
import java.util.concurrent.TimeUnit;
public class Test06 {
public static void main(String[] args) {
//synchronized 锁的对象是方法的调用者
//两个方法用的是用一个锁
//只有一个对象phone
Phone06 phone1 =new Phone06();
Phone06 phone2 =new Phone06();
new Thread(()->{
phone1.sendms();
},"A").start();
try {
//import java.util.concurrent.TimeUnit;包下的休息类
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone06{
//静态的同步方法
public static synchronized void sendms(){
//休眠四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通的同步方法
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
结果显示:
分析原因:先发短信后打电话,static 静态方法锁的是class类的锁,在这个例子中有两把锁,一把是class锁,一把是对象的锁,所以他们的锁不一样,谁睡眠的时间长谁就后打印。
总结:锁就两种
new 创建对象出来的一把锁,锁的是调用者this对象
static class唯一的一个,只有一个class文件
6.集合类不安全
并发下集合类不安全
1.list
在多线程下使用Arraylist是不安全的
import java.util.ArrayList;
import java.util.UUID;
public class ListTest {
public static void main(String[] args) {
//java.util.ConcurrentModificationException 并发修改异常!
ArrayList<String> list = new ArrayList<>();
for (int i =1; i <=10; i++) {
new Thread(()->{
list.add(
UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
显示输出结果:java.util.ConcurrentModificationException 并发修改异常!
有可能是会报错:
如何解决并发修改异常呢?
1. 使用 List list =new Vector<>();
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.Vector;
public class ListTest {
public static void main(String[] args) {
//java.util.ConcurrentModificationException 并发修改异常!
//ArrayList<String> list = new ArrayList<>();
List<String> list =new Vector<>();
for (int i =1; i <=10; i++) {
new Thread(()->{
list.add(
UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
为什么Vector可以解决?
Vector add方法使用的是synchronized
2.使用工具类
List list = Collections.synchronizedList(new ArrayList<>());
package com.mahui.Unsafe;
import java.util.*;
public class ListTest {
public static void main(String[] args) {
//java.util.ConcurrentModificationException 并发修改异常!
//ArrayList<String> list = new ArrayList<>();
//1. List<String> list =new Vector<>();
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i =1; i <=10; i++) {
new Thread(()->{
list.add(
UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
3. CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
package com.mahui.Unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ListTest {
public static void main(String[] args) {
//java.util.ConcurrentModificationException 并发修改异常!
//ArrayList<String> list = new ArrayList<>();
//1. List<String> list =new Vector<>();
//2. List<String> list = Collections.synchronizedList(new ArrayList<>());
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i =1; i <=10; i++) {
new Thread(()->{
list.add(
UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
为什么CopyOnWriteArrayList可以解决这个问题?
CopyOnWriteArrayList的add 方法使用的是lock锁。这是与Vector方法的本质区别。
add源码:
2.set不安全
在多线程下使用HashSet是不安全的:
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SetTest {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 1; i <= 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
会出现:java.util.ConcurrentModificationException并发修改异常
如何解决呢?
1.使用工具类将其转换为安全的.
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();不安全的
//2.使用工具类安全的
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i <= 10; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
2.写入时复制,保证效率
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();不安全的
//2.使用工具类安全的
//1.Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; 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;
}
//SPRESENT是一个不变的值
private static final Object PRESENT = new Object();
3.Map不安全
package com.mahui.Unsafe;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
//java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
//map 是这样使用的吗? Map<String,String> map=new HashMap<>();
//不是这样使用的,工作中不使用hashmap
Map<String,String> map=new HashMap<>();
// 默认等价于?new HashMap<>(16,0.75);
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
//加载因子、初始化容量
}
}
会出现:java.util.ConcurrentModificationException 并发修改异常
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
//java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
//map 是这样使用的吗? Map<String,String> map=new HashMap<>();
//不是这样使用的,工作中不使用hashmap
// Map<String,String> map=new HashMap<>();
// 默认等价于?new HashMap<>(16,0.75);
Map<String,String> map=new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
//加载因子、初始化容量
}
}
2.使用ConcurrentHashMap
--
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
//java.util.ConcurrentModificationException 并发修改异常
public class MapTest {
public static void main(String[] args) {
//map 是这样使用的吗? Map<String,String> map=new HashMap<>();
//不是这样使用的,工作中不使用hashmap
// Map<String,String> map=new HashMap<>();
// 默认等价于?new HashMap<>(16,0.75);
Map<String,String> map=new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
//加载因子、初始化容量
}
}
7.Callable
通过API文档可以了解到:callable
1.可以有返回值
2.可以抛出异常
3.方法不同,run() call()
在callable中。
它的构造方法可以跟runnable和callable挂上关系:
package com.mahui.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 {
//实现runnable接口的启动方式 new Thread(new MyThread()).start();
//callable实现的关系
//new thread(new Runnable()).start()
//new thread(new FutureTask<V>()).start()
//new thread(new FutureTask<V>(callable)).start()
//callable无法直接调用Runnable 需要一个适配类 该适配类是FutureTask 通过适配类来启动callable
MyThread myThread = new MyThread();
FutureTask futureTask =new FutureTask(myThread);//适配类
new Thread(futureTask).start();
Integer o = (Integer)futureTask.get();//获取callable返回结果 这个get方法可能会产生阻塞!把他放在最后
System.out.println(o);
}
}
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("call方法执行了");
return 123;
}
}
运行结果:
8.常用辅助类
1.CountDownLatch
减法计数器
import java.util.concurrent.CountDownLatch;
//计数器 down减法
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(8);
for (int i = 1; i <= 8; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"GO OUT");
//线程数量-1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
//等待计数器归0执行完再继续往下执行
countDownLatch.await();
System.out.println("close door");
}
}
原理:
两个方法:
countDownLatch.countDown();
数量-1
countDownLatch.await();
等待计数器归零,然后再向下执行
每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await()就会被唤醒
2.CyclicBarrier
加法计数器:
package com.mahui.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDome {
public static void main(String[] args) {
/*
* 集齐7颗龙珠
* */
//召唤龙珠的线程
CyclicBarrier cyclicBarrier =new CyclicBarrier(7,()->{
System.out.println("召唤龙珠成功");
});
for (int i = 1; i <= 7; i++) {
//lambda表达式拿不到i的值 将i设置为fial值即可
final int temp =i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"收集"+temp+"个龙珠");
try {
//等待
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
3.Semaphore
Semaphore:信号量,限流的使用多
例子:6车只有3停车位 如何解决
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量,三个停车位
Semaphore semaphore = new Semaphore(3);
//6个车
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 {
//release()释放
semaphore.release();
}
}).start();
}
}
}
原理:
semaphore.acquire();
获取,假设已经满了就等待,等待释放为止
semaphore.release();
释放 会将当前的信号量释放+1,然后唤醒等待的线程。
作用:多个共享资源互斥的使用!并发限流,控制最大的线程数!
9.读写锁
读可以被多个线程使用但是写只能是独家的
package com.mahui.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁)一次只能一个线程占有
* 共享锁(读锁)可以多个线程同时占有
* ReadWriteLock
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCacheLock myCache = new MyCacheLock();
//写入
for (int i = 1; i <=5; i++) {
final int temp = i;
new Thread(()->{myCache.put(temp+"", temp+"");},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int temp =i;
new Thread(()->{myCache.get(temp+"");},String.valueOf(i)).start();
}
}
}
//加锁
class MyCacheLock{
private volatile Map<String,Object> map =new HashMap<>();
//读写锁,更加细粒度的控制
private ReadWriteLock readWriteLock =new ReentrantReadWriteLock();
//存、写 写入的时候同时只有一个线程写入
public void put(String key,Object value){
//加一把写锁
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//取、读 所有的线程都可以读
public void get(String key){
readWriteLock.readLock().lock();
try{
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
}
/**
* 自定义缓存 没有加锁
*/
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()+"写入ok");
}
//取、读
public void get(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
}
}
10.阻塞队列
FIFO:先进先出
不得不阻塞:
写入 :如果队列满了,就必须阻塞等待
取:如果队列是空的必须阻塞等待生产什么情况下我们会使用阻塞队列:多线程并发处理
如何使用队列:添加、移除
方式 | 抛出异常 | 不会抛出异常,又返回值 | 阻塞等待 | 超时等待 |
添加 | add() | offer() | put() | offer(三个参数) |
移除 | remove() | poll() | take() | poll(两个参数) |
判段队列首 | element() | peek() | - | - |
1.抛出异常
package com.mahui.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.test1();
}
/**
* 抛出异常
*/
public 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"));
//java.lang.IllegalStateException: Queue full 抛出队列已满的异常
//System.out.println(arrayBlockingQueue.add("d"));
//返回队首元素是谁
System.out.println(arrayBlockingQueue.element());
System.out.println("======================================");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//java.util.NoSuchElementException 抛出异常没有元素的
// System.out.println(arrayBlockingQueue.remove());
}
}
2.有返回值
package com.mahui.bq;
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.test2();
}
/**
* 抛出异常
*/
public 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"));
//java.lang.IllegalStateException: Queue full 抛出队列已满的异常
//System.out.println(arrayBlockingQueue.add("d"));
//返回队首元素是谁
System.out.println(arrayBlockingQueue.element()); System.out.println("======================================");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//java.util.NoSuchElementException 抛出异常没有元素的
// System.out.println(arrayBlockingQueue.remove());
}
/**
* 又返回值不抛出异常
*/
public 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"));
//队列满了之后输出false 不跑出异常
System.out.println(arrayBlockingQueue.offer("d"));
System.out.println(arrayBlockingQueue.poll());
//检测队首元素
System.out.println(arrayBlockingQueue.peek());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//返回null 不抛出异常
System.out.println(arrayBlockingQueue.poll());
}
}
3.阻塞等待
public void test3() throws InterruptedException {
//队列大小
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
//一直阻塞 没有返回值
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
//队列没有位置了,一直阻塞程序不执行
//arrayBlockingQueue.put("d");
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
//没有元素会一直等待
//System.out.println(arrayBlockingQueue.take());
}
4.超时等待
/**
* 等待,阻塞(等待超时,超过时间就不等了)
*/
public void test4() throws InterruptedException {
ArrayBlockingQueue<Object> arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
//等待等待两秒如果还是没有位置就会退出
arrayBlockingQueue.offer("d",2,TimeUnit.SECONDS);
System.out.println("=============================================");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
//取不出来 等待两秒结束
System.out.println(arrayBlockingQueue.poll(2,TimeUnit.SECONDS));
}
SynchronousQueue 同步队列
没有容量的,进去一个元素,必须等待取出来之后,才能往里面放一个元素!
对应的方法 put() take()
package com.mahui.bq;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class SynchronousQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue<String> blockingQueue = new SynchronousQueue<>();
//
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
//
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
结果显示:
一个线程写入一个元素,必须等待下一个线程把它取出来,否则不能在put进去
11.线程池
池化技术
程序的运行,本质:占用系统的资源!优化资源的使用!==>池化技术
线程池、连接池、内存池、对象池…
池化技术:事先准备好的一些资源,需要用时来拿,用完之后还回来。
线程池的优点:
1.降低资源的消耗
2.提高响应速度
3.方便管理。
线程复用、可以控制最大的并发数、管理线程
1.线程池三大方法:
创建线程的三大方法:
- 单个线程
- 固定数目的线程池
- 可伸缩的线程池。
package com.mahui.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo01 {
public static void main(String[] args) {
//1.单个线程
//ExecutorService threadpool = Executors.newSingleThreadExecutor();
//2.创建一个固定的线程池大小
// ExecutorService threadpool = Executors.newFixedThreadPool(5);
//3.可伸缩的,遇强则强,遇弱则弱
ExecutorService threadpool =Executors.newCachedThreadPool();
try{
//线程池用完,程序结束,关闭线程池
for (int i = 1; i <= 10; i++) {
//使用线程池后,使用线程池创建线程
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//关闭线程池
threadpool.shutdown();
}
}
}
2.7大参数:
创建单一线程的源码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建固定数目线程的源码;
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建可伸缩的线程的源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//21亿
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通过分析:开启线程调用的是ThreadPoolExecutor
ThreadPoolExecutor的源码:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
//进入this方法
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;
}
3.手动创建线程池ThreadPoolExecutor
public class Demo02 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool = new ThreadPoolExecutor(2,
5,
3,
TimeUnit.MILLISECONDS,
//阻塞队列
new LinkedBlockingDeque<>(3),
//默认的
Executors.defaultThreadFactory(),
//默认的拒绝策略,如果对队列满了,还有需要进来的,就不处理个抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
}
4.四种拒绝策略
1.AbortPolicy()默认的拒绝策略
package com.mahui.pool;
import java.util.concurrent.*;
public class Demo02 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool= new ThreadPoolExecutor(2,
5,
3,
TimeUnit.MILLISECONDS,
//阻塞队列
new LinkedBlockingDeque<>(3),
//默认的
Executors.defaultThreadFactory(),
//默认的拒绝策略,如果对队列满了,还有需要进来的,就不处理这个 ,抛出异常
new ThreadPoolExecutor.AbortPolicy()
);
try {
//最大量maximumPoolSize+队列容量 超出最大容量会抛出异常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();
}
}
}
2.CallerRunsPolicy()哪里来的回哪去,不会抛出异常
package com.mahui.pool;
import java.util.concurrent.*;
public class Demo03 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool= new ThreadPoolExecutor(2,
5,
3,
TimeUnit.MILLISECONDS,
//阻塞队列
new LinkedBlockingDeque<>(3),
//默认的
Executors.defaultThreadFactory(),
//哪来的就回哪里去main线程执行
new ThreadPoolExecutor.CallerRunsPolicy()
);
try {
//最大量maximumPoolSize+队列容量 超出最大容量会抛出异常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();
}
}
}
3.DiscardPolicy() 队列满了它不会抛出异常,丢掉任务
package com.mahui.pool;
import java.util.concurrent.*;
public class Demo04 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool= new ThreadPoolExecutor(2,
5,
3,
TimeUnit.MILLISECONDS,
//阻塞队列
new LinkedBlockingDeque<>(3),
//默认的
Executors.defaultThreadFactory(),
//队列满了它不会抛出异常,丢掉任务。
new ThreadPoolExecutor.DiscardPolicy()
);
try {
//最大量maximumPoolSize+队列容量 超出最大容量会抛出异常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();
}
}
}
运行结果:只运行了八个,有一个不运行,不抛出
4.DiscardOldestPolicy() 队列满了,会尝试跟第一个竞争,看第一个是否完成,如果竞争到就会运行,没竞争到就丢掉,也不会抛出异常
package com.mahui.pool;
import java.util.concurrent.*;
public class Demo05 {
public static void main(String[] args) {
//自定义线程池
ExecutorService threadPool= new ThreadPoolExecutor(2,
5,
3,
TimeUnit.MILLISECONDS,
//阻塞队列
new LinkedBlockingDeque<>(3),
//默认的
Executors.defaultThreadFactory(),
//队列满了,会尝试跟第一个竞争,看第一个是否完成,如果竞争到就会运行,没竞争到就丢掉,也不会抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
//最大量maximumPoolSize+队列容量 超出最大容量会抛出异常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.CPU密集型,最大线程数几核就定义为几,保证效率最高
- 通过代码获取CPU的核数
Runtime.getRuntime().availableProcessors()
2.IO密集型:判断你程序中十分耗IO的线程
一个程序有15个大型人物,io十分占用资源,
12.四大函数式接口
jdk8的新特性:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口,简化编程模型
例子:runnable接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
//FunctionalInterface函数式接口
//简化编程模型
1.Function函数式接口
代码实现:
package com.mahui.Function;
import java.util.function.Function;
public class Demo01 {
public static void main(String[] args) {
//Function 代码实现类 匿名内部类
/* Function function =new Function<String,String>() {
@Override
public String apply(String string) {
return string;
}
};*/
//lambda表达式来实现
Function<String,String> function =(str)->{
return str;
};
System.out.println(function.apply("abcd"));
}
}
2.predicate断定型接口
import java.util.function.Predicate;
/*
* 断定型接口:有一个输入参数,返回值只能是boolean值
* */
public class Demo02 {
public static void main(String[] args) {
//判断字符串是否为空
/*Predicate<String> predicate= new Predicate<String>() {
@Override
public boolean test(String string) {
return string.isEmpty();
}
};*/
//lambda表达式
Predicate<String> predicate =(str)->{return str.isEmpty();};
System.out.println(predicate.test(""));
}
}
3.Consumer消费型接口
package com.mahui.Function;
import java.util.function.Consumer;
/*
* Consumer 消费型接口:只有输入,没有返回值
* */
public class Demo03 {
public static void main(String[] args) {
//消费型接口
/* Consumer<String> consumer =new Consumer<String> () {
@Override
public void accept(String string) {
System.out.println(string);
}
};*/
//lambda表达式
Consumer<String> consumer=(string)->{
System.out.println(string);
};
consumer.accept("abcd");
}
}
4.Supplier 供给型接口
package com.mahui.Function;
import java.util.function.Supplier;
/*
* 供给型接口:没有参数,只有返回值
* */
public class Demo04 {
public static void main(String[] args) {
/* Supplier<String> supplier = new Supplier<String>() {
@Override
public String get( ) {
return "abcd";
}
};*/
//lambda表达式
Supplier<String> supplier =()->{return "abcd";};
System.out.println(supplier.get());
}
}
13.Stream流式计算
1.什么是Stream流式计算
集合跟MySql的本质是用来存储数据的,而stream是用来计算的。
User类
package com.mahui.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor//无参构造
@AllArgsConstructor//有参构造
public class User {
private int id;
private String name;
private int age;
}
测试类:
通过流来计算:
package com.mahui.stream;
import java.util.Arrays;
import java.util.List;
/**
* 题目要求:
* 现在有六个用户!筛选
* 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);
User u6 = new User(6, "f", 26);
//用集合将其存储起来
List<User> list = Arrays.asList(u1, u2, u3, u4, u5, u6);
//将集合转换为流进行计算
//链式编程
list.stream()
//过滤出id为偶数的用户
.filter((u)->{return u.getId()%2==0;})
//过滤出年龄大于23的用户
.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);
}
}
14.ForKJoin(分支合并)
1.什么事ForKJoin
Forkjoin在jdk1.7出现的,并行执行任务!提高效率,大数据量!
将一个任务拆分为多个小任务。每个小任务都有一个结果,将他们的结果合并起来为最终的结果。
2.forkjoin的特点
这里维护的是双端队列从两头都可以执行的队列
工作窃取:一个线程执行完之后,其他线程还没有执行完,这个线程会将其他线程没有执行完的地方拿来执行。
3.如何使用forkjoin
forkjoin类
import java.util.concurrent.RecursiveTask;
/***
*求和计算
* 如何使用forkjoin
* 1.forkjoinpool
* 2.需要继承RecursiveTask
* 3.原理就是递归思想
*/
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 {
//forkjoin
Long middle =(start+end)/2;
ForkjoinDemo task1 = new ForkjoinDemo(start, middle);
//拆分任务,把任务压入线程队列
task1.fork();
ForkjoinDemo task2 = new ForkjoinDemo(middle + 1, end);
//拆分任务
task2.fork();
//返回task1的值和task2的值的和
return task1.join()+task2.join();
}
}
}
测试类,通过三种方法来计算
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* 三种方法计算和:
* 1.使用传统的计算 9703
* 2.使用forkjoin计算 8039
* 创建forkjoinpool
* 通过forkjoinpool来执行
* 3.使用stream流计算 310
*/
public class Test1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//test1();
//test2();
test3();
}
public static void test1(){
long start = System.currentTimeMillis();
long sum =0L;
for (Long i = 1L; i <=10_0000_0000; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("运行的结果是:"+sum+"耗费的时间是"+(end-start));
}
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkjoinDemo(0L,10_0000_0000L);
//执行任务,没有结果
// forkJoinPool.execute(task);
//Long sum = task.get();
//提交任务,是有结果的
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("运行的结果是:"+sum+"耗费的时间是"+(end-start));
}
public static void test3(){
long start = System.currentTimeMillis();
//stream 并行流
//LongStream.range()是开区间()
// LongStream.rangeClosed() 是闭区间(]
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("运行的结果是:"+sum+"耗费的时间是"+(end-start));
}
}
15.异步回调
异步回调,java中通过future来实现
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 异步调用 ajax
* 异步执行
* 成功回调
* 失败回调
*/
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//发起一个请求
//没有返回值的runAsync异步回调
/* CompletableFuture<Void> future =CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"runAsync=>void");
});
System.out.println("1111");
future.get();//因为有延迟,获取阻塞执行结果,如果没有延迟就会直接拿到结果*/
//有返回值的 supplyAsync 异步回调
//ajax 成功和失败的回调
//失败了ajax返回的是错误信息
CompletableFuture<Integer> future =CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"suppilyAsync=>Integer");
//故意制造错误 分母不能为0
int i =10/0;
//成功了有成功的返回值1024
return 1024;
});
System.out.println(future.whenComplete((t, u) -> {
//t为正常执行的返回结果
System.out.println("t=>" + t);
//u为失败执行后的错误信息u=>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
System.out.println("u=>" + u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
//有错误了可以返回出错误的结果233
return 233;
}).get());
}
}
16.JMM
1.什么事JMM
JVM:java虚拟机
JMM:Java的内存模型,不存在的东西,是一个概念、约定。
2.关于JMM的一些同步的约定
1.线程解锁前,必须把共享变量立刻刷回主存。
线程A想拿到主存中的变量X,先从主存中复制一份放入线程A的工作内存中,待线程A操作完成后将改变后的变量X应立即刷入主存中。
2.线程加锁前,必须读取主存中的最新值到工作内存中。
3.加锁和解锁是同一把锁
对原子的八种操作:
八种操作的解释:
示例:
package com.mahui.jmm;
import java.util.concurrent.TimeUnit;
public class JmmDemo {
private static int num =0;
public static void main(String[] args) {//主线程也是一条线程
//线程1
new Thread(()->{
//num=0时让线程一直运行
while (num==0){
}
}).start();//开启一个线程
//休眠两秒使线程1拿到num=0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
运行结果:
分析出现线程运行不停止的原因:
存在问题:程序并不知道主存的值已经被修改过了,需要解决这个问题就需要volatile
17.Volatile
1.什么是Volatile
Volatile是java虚拟机提供的轻量级的同步机制
2.volatile的三大特性
- 保证可见性
package com.mahui.jmm;
import java.util.concurrent.TimeUnit;
public class JmmDemo {
//不加volatile程序就会死循环
//加了volatile程序会在输出1之后立即停止
//所以加了volatile保证了可见性,线程1可以知道num的值发生了变化
private volatile static int num =0;
public static void main(String[] args) {//主线程也是一条线程
//线程1 对主内存的变化是不知道的
new Thread(()->{
//num=0时让线程一直运行
while (num==0){
}
}).start();//开启一个线程
//休眠两秒使线程1拿到num=0
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
- 不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败
案例:结果不为20000 不正确
public class volatileDemo {
private static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上num=20000
//实际上num并不是20000小于20000
//
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
//判断线程是否执行完没
//大于两个线程main 跟 gc 线程
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
add()增加synchronized 结果为20000正确
保证原子性
public class volatileDemo {
private static int num = 0;
public synchronized static void add(){
num++;
}
public static void main(String[] args) {
//理论上num=20000
//实际上num并不是20000小于20000
//
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
//判断线程是否执行完没
//大于两个线程main 跟 gc 线程
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
给变量用volatile修饰 结果不正确 不为20000
不保证原子性
package com.mahui.jmm;
public class volatileDemo {
private volatile static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上num=20000
//实际上num并不是20000小于20000
//
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
//判断线程是否执行完没
//大于两个线程main 跟 gc 线程
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
如果不加lock跟synchronized锁,如何保证原子性?
使用原子类来保证原子性:
package com.mahui.jmm;
import java.util.concurrent.atomic.AtomicInteger;
public class volatileDemo {
//使用原子类
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
num.getAndIncrement();//AtomicInteger +1
}
public static void main(String[] args) {
//理论上num=20000
//实际上num并不是20000小于20000
//
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
//判断线程是否执行完没
//大于两个线程main 跟 gc 线程
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(num);
}
}
运行结果:正确
这些原子类的底层都是直接和操作系统挂钩,直接在内存中修改!
Unsafe类是一个很特殊的存在
- 禁止指令重排
1.什么是指令重排?
你写的程序,计算机并不是按照你写的那样去执行的。
程序运行的过程:
源代码–>编译器优化的重排–> 指令并行也可能重排–>内存系统也会重排–>运行
**处理器在进行指令重排的时候要考虑数据之间的依赖性!**并不会进行乱排序
int x =1;//1
int y =3;//2
x = x+4; //3
y = x*x; //4
我们所期望的执行顺序:1234
但是可能执行的时候是是:2134 1324
但是绝不可能是:4123!
原因是:处理器在进行指令重排的时候要考虑数据之间的依赖性!
案例:
a b x y这四个默认值都是0
线程A | 线程B |
x=a | y=b |
b=1 | a=2 |
正常的结果:x=0; y=0;但是由于指令重排可能出现的
线程A | 线程B |
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果是:x=2;y =1;
volatile可以避免指令重排:
1.保证特定的操作的执行顺序!
2.可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
18.深入理解CAS
1.什么是CAS
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
缺点:
- 由于底层是自旋锁,循环会耗时
- 一次性只能保证一个共享变量的原子性
- 会存在ABA问题
Unsafe类
执行+1的操作:
package com.mahui.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CasDemo {
//CAS compareAndSet 缩写 比较并交换
public static void main(String[] args) {
//原子类
AtomicInteger atomicInteger= new AtomicInteger(2020);
//两个参数第一个期望,第二个更新的值
// public final boolean compareAndSet(int expectedValue, int newValue)
//如果我期望的值达到了 那么就更新,否则就不更新 CAS是CPU的并发原语!cpu的指令
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
//
// atomicInteger.getAndIncrement();//++ 执行+1操作
//false 期望值已经变为2021 并不是2020
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
19.原子引用
解决ABA问题,就使用原子引用,思想是乐观锁。
CAS:ABA问题(狸猫换太子)
代码演示:
package com.mahui.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class AbaDemo {
//CAS compareAndSet 缩写 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger= new AtomicInteger(2020);
//====捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
//false 期望值已经变为2021 并不是2020
//====自己期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 99999));
System.out.println(atomicInteger.get());
}
}
如何解决这个问题:需要原子引用!带版本号的原子操作!
使用Atomicrerfence类来解决!
Integer包装类的坑:
使用版本号的原子引用代码演示:
package com.mahui.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class AbaDemo02 {
public static void main(String[] args){
//泛型是Integer 是在-128 至 127 范围内的赋值 所以其期望值应该在-127—128之间 包装类要注意对象的引用问题
//一般泛型的引用都是一个对象
AtomicStampedReference<Integer> atomicInteger= new AtomicStampedReference<>(11,1);
//跟乐观锁一样的
new Thread(()->{
//获得版本号
int stamp = atomicInteger.getStamp();
System.out.println("A1==>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//cas 操作
System.out.println(atomicInteger.compareAndSet(11, 2,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
//输出版本号
System.out.println("A2==>"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(2, 11,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
//输出版本号
System.out.println("A3==>"+atomicInteger.getStamp());
},"A").start();
new Thread(()->{
//获得版本号
int stamp = atomicInteger.getStamp();
System.out.println("B1==>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(11, 6,
atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
//输出版本号
System.out.println("B2==>"+atomicInteger.getStamp());
},"B").start();
}
}
运行结果:
20.锁
1.公平锁,非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队(默认都是非公平)
2.乐观锁 悲观锁
3.可重入锁
所有的锁都是可重入锁(递归锁)
4.自旋锁
5.死锁
死锁测试:如何测试死锁?
排查问题:
1.日志
2.堆栈排查