1.CompletableFuture异步回调 像ajax,未来再得到执行结果,想服务器不分先后顺序执行,可以用异步回调
//调用的函数没有返回值的
CompletableFuture<Void> future=CompletableFuture.runAsync(()->{
TimeUnit.SECONDS.sleep(2);
sout(Thread.currentThread.getName+"async=>Void")
});
sout("111");
future.get();//获取结果,没有结果一直阻塞
//调用的函数有返回值的
CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
TimeUnit.SECONDS.sleep(2);
sout(Thread.currentThread.getName+"async=>Void")
int i=10/0;//模拟错误
return 1024
});
//BiConsumer是需要插入2个参数的函数式接口
future.whenComplete((t,u)->{
sout(t)
sout(u)
}).exceptionally((e)->{ sout(e.getMessage()
return 23;
)}).get().sout //结果输出一下
public class CompeleteF {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> future=CompletableFuture.runAsync(()->{
System.out.println("aaa");
});
System.out.println(future.get());
CompletableFuture<Integer> future1=CompletableFuture.supplyAsync(()->{
return 10;
});
System.out.println("-------------返回结果的----------------");
System.out.println(future1.whenComplete((s, a) -> {
// System.out.println(s);//调用后返回的结果
// System.out.println(a); //null
int a1=10/0;
}).exceptionally((e) -> {
System.out.println("异常返回结果"+e.getMessage());
return 11;
}).get());
}
}
2.JMM
- 面试:对 Volatile的理解
答: Volatile是jvm通过轻量级的同步机制,比sychronized更轻
1.保证可见性 2.不保证原子性 3.禁止指令重排- 什么是JMM?
答:java内存模型,是一种规定,不存在的东西
一些同步的约定
1.线程加锁前,必须读取主存中的最新值到工作工作内存中
2.线程解锁前,必须把共享变量"立即"刷回主存
3.加锁和解锁是同一把锁
图 JMM解决的问题 (2个执行8组操作,会产生线程变量不同步的问题,由于共用一个变量)volatile可以解决(程序不知道主内存的值被修改,主线程通知B线程
某个变量可见[被修改])- 代码验证线程间的可见性 不保证原子性(要么同时成功,要么同时失败)
//可见性
//图JMM
private volatile int num=0; //不加volatile来测试
new Thread(()->{
while(num==0){ //没有volatile,不知道变量的值被修改,一直死循环
}
}).start();
TimeUnit.SECONDS.sleep(2);
num=1;
//不保证原子性,怎么不加synchronized或lock怎么实现
//使用原子类的类型,底层使用CAS cpu并发原理
//打开 target文件夹我们写的volatile class字节码文件的文件夹(右键然后optimize imports)
//cmd窗口 反编译 javap -c Demo.class可以看到字节码 图volatile底层代码分析
//多线程操作,比锁高效很多倍
private volatile static AutomicInteger num=new AutomicInteger();
public static void add(){
num.getAndIncrement();
}
public class AtmoticDemo {
static volatile int num= 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //main和gc线程,不停的判断执行完的线程数量,如果是>2main线程让步给其他线程执行
//直到线程数量为2
Thread.yield();
}
System.out.println(num);
}
}
------改进后-----
public class AtmoticDemo1 {
static volatile AtomicInteger num= new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){ //main和gc线程,不停的判断执行完的线程数量,如果是>2main线程让步给其他线程执行
//直到线程数量为2
Thread.yield();
}
System.out.println(num);
}
}
//Unsafe类是很特殊的存在,里面都是native方法
- 可以禁止指令重排(程序不是按我们写的那样去执行,而是通过系统的优化后才执行)
1. 保证特定的操作的执行顺序
2. 报错模型变量内存可见性
图 volatile防止指令重排底层原理
//一个线程如果两个变量没有依赖关系可以指令重排 如:
//这个是我们写的代码
int a=10;
int b =20;
a=30;
//操作系统进行指令重排进行优化可能结果是
int a=10;
a=30;
int b =20;
b=40;
//也可能是....多种情况的,会根据实际情况优化
int b =20;
int a=10;
a=30;
b=40;
//但是多线程不保证指令重排, 1000万次可能只出现1次 大厂的海量大数据才可能遇到这种情况
3.单例模式(volatile使用得最多)
1.饿汉式的懒汉式DCL(double check双重检测)多线程会指令重排
//普通懒汉会导致资源浪费,没有使用的资源会大量闲置
public class Hungry {
//饿汉式模拟 这个类有大量数据
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private byte[] data4 = new byte[1024];
private Hungry() {
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance() {
return hungry;
}
}
//DCL方式
public class SingleonDoubleCheck {
private SingleonDoubleCheck(){
}
private static volatile SingleonDoubleCheck instance; instace改volatile,禁止指令重排
public static SingleonDoubleCheck getInstance(){
if(instance==null){ //可能指令重排
synchronized (SingleonDoubleCheck.class){//可能指令重排
if(instance==null){//可能指令重排
instance=new SingleonDoubleCheck();
}
}
}
return instance;
}
}
class test2{
public static void main(String[] args) {
CopyOnWriteArrayList list=new CopyOnWriteArrayList();
for (int i = 0; i <20 ; i++) {
new Thread(()->{
SingleonDoubleCheck instance = SingleonDoubleCheck.getInstance();
list.add(instance.hashCode());
}).start();
}
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list);
}
}
2.静态内部类懒汉,使用反射可以破坏单例(可以写外挂,破坏java写的游戏,可以以后看看cf的源代码) 但是对枚举行不通
//使用jad反编译类cmd的 jad -s java Demo.class
//枚举是class类继承Enum类,枚举不会被破坏,没有无参构造
public class Holder { //既保证线程安全性,又保证懒加载,因为内部类不会被优先创建,调用时才创建
private Holder() {
}
public static Holder getInstance() {
return InnerClass.holder;
}
private static class InnerClass {
private static final Holder holder = new Holder();
}
}
class test02{
public static void main(String[] args) {
System.out.println(Holder.getInstance().hashCode());
System.out.println(Holder.getInstance().hashCode());
}
}
//但是反射可以无视private方法 ,我们可以加个flag判断对象是否为空,但是恶意者直接使用newInstance搞破坏
4.CAS(compareAndSwap) 比较并交换 计算机并发原理(如果达到期望值,则更新值,否则不更新)
atom=new AtomicInteger(2020);
atom.compareAndSet(2020,2021);//可以修改
atom.compareAndSet(2020,2021);//不可以修改
//java无法操作内存,通过native方法调用c++
atom.getAndIncrement(); //unsafe底层原理,向得到内存地址的值,然后如果和原来值相等,设置期望值,然后设置值+1 ,如果不是期望值就一直循环(使用自旋锁)
//缺点 1.循环会耗时 2.一次性只能保证一个共享变量的原子性 3.产生ABA问题
5.CAS ABA问题(狸猫换太子,就是原来是A改为B,再改回A) 一个线程有期望值,然后修改为其他值,再修改回来,另外一个线程不知情,以为是原来的值,修改了期望值
atom.compareAndSet(20,21);//一个线程修改了值
atom.compareAndSet(21,23);
atom.compareAndSet(23,20);//更改回来原来的值,其他线程并不知道他被改变了
//另外一个线程
atom.compareAndSet(20,21); //修改了值,相当于线程不安全(后面加个版本号可以解决)
//CAS他的底层代码 自旋锁
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1); //valueOffset是默认的,最后一个是增加的值
}
//unsafe类
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2); //通过 对象和地址偏移量得到他对应的volatile其他内存可见性的值
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); // 对值+1,如果没有期望值 就一直循环等待得到这个值 ,(期望值,固定的偏移,内存的值,设置的值)
return var5;
}
6.原子引用(带版本的CAS) 解决ABA问题需要如果修改一次版本号就+1
//如果泛型是包装类,需要注意对象的引用问题,正常使用对象来原子操作
//多线程效率更快
AtomicStampedReference<Integer> atomic =new AtomicStampedReference<>(2020,1);//第二个参数搜索版本号
int stamp=atomic.getStamp();
sleep(2);
int stamp= atomic.getStamp();//得到版本号
atomic.compareAndSet(2020,2022,atomic.getStamp(),atomic.getStamp()+1);//版本号+1,代表现在2020这个值的版本号为 不是原来的,其他的线程不能修改
public class ABADemo {
static AtomicStampedReference<Integer> atomicStampedReference = new
AtomicStampedReference<>(100,1);
public static void main(String[] args) {
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("T1 stamp 01=>"+stamp);
// 暂停2秒钟,保证下面线程获得初始版本号
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(100, 101,
atomicStampedReference.getStamp()
,
atomicStampedReference.getStamp()+1);
System.out.println("T1 stamp 02=>"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
System.out.println("T1 stamp 03=>"+atomicStampedReference.getStamp());
},"T1").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp(); // 获得版本号
System.out.println("T2 stamp 01=>"+stamp);
// 暂停3秒钟,保证上面线程先执行
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(100, 2019,
stamp, stamp + 1);
System.out.println("T2 是否修改成功 =>"+ result);
System.out.println("T2 最新stamp =>"+atomicStampedReference.getStamp());
System.out.println("T2 当前的最新值 =>"+atomicStampedReference.getReference());
},"T2").start();
}
}
//执行结果
T1 stamp 01=>1
T2 stamp 01=>1
T1 stamp 02=>2
T1 stamp 03=>3
T2 是否修改成功 =>false
T2 最新stamp =>3
T2 当前的最新值 =>100
7.锁的分类
- 公平锁(不能插队),非公平锁(默认 synchronized或者lock都是.可以插队执行)
ReentrantLock lock = new ReentrantLock(false); //非公平锁- 可重入锁 拿到外面的锁 可以拿到里面的锁 而synchronized不可以,差别在效率上,结果一致 注意锁要配对,不可以两个lock 一个unlock,会导致死锁
//普通synchronized
public class ReentrantLockDemo {
public static void main(String[] args) throws Exception {
Phone phone = new Phone();
// T1 线程在外层获取锁时,也会自动获取里面的锁
new Thread(()->{
phone.sendSMS();
},"T1").start();
new Thread(()->{
phone.sendSMS();
},"T2").start();
}
}
class Phone{
public synchronized void sendSMS(){
System.out.println(Thread.currentThread().getName()+" sendSMS");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getName()+" sendEmail");
}
}
//使用可重入锁
public class ReentrantLockDemo1 {
public static void main(String[] args) throws Exception {
Phone1 phone = new Phone1();
// T1 线程在外层获取锁时,也会自动获取里面的锁
new Thread(phone,"T1").start();
new Thread(phone,"T2").start();
}
}
class Phone1 implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
public void get(){
lock.lock();
// lock.lock(); 锁必须匹配,如果两个锁,只有一个解锁就会失败
try {
System.out.println(Thread.currentThread().getName()+" get()");
set();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
// lock.lock();
}
}
public void set(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+" set()");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3.自旋锁 spinLock 不断得到锁,直到成功文章来源:https://www.toymoban.com/news/detail-480798.html
//自己写,自旋锁 加锁解锁
AtomicReference<Thread> atomic =new AtomicReference<>(); //默认为null,Integer默认为1
public void myLock(){
Thread thread=Thread.currentThread();
while(!atomic.compareAndSet(null,thread)){ //如果为设置值成功则退出,如果失败不停执行设置值
}
}
public void myUnLock(){//底层使用CAS效率高,发现也可以线程有序调用
Thread thread=Thread.currentThread();
atomic.compareAndSet(thread,null); //直接设置为null
}
------完整代码-------
class MyspinLock {
AtomicReference<Thread> reference=new AtomicReference<>();
public void lock(){
Thread thread = Thread.currentThread();
while (!reference.compareAndSet(null,thread)){ //是会根据整个线程对象来设置
}
}
public void unlock(){
Thread thread = Thread.currentThread();
reference.compareAndSet(thread,null);
}
}
public class usingLock {
public static void main(String[] args) {
Reasouce reasouce = new Reasouce();
for (int i = 0; i <1000 ; i++) {
new Thread(reasouce).start();
}
}
}
class Reasouce implements Runnable{
int num=0;
MyspinLock lock=new MyspinLock();
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
while (true){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try{
num++;
System.out.println(Thread.currentThread().getName()+",num:"+num );
}finally {
lock.unlock();
}
}
}
}
8.死锁 怎么排查死锁(jdk自带)
jps -l //查看java进程
jstack 11444//查看对应的进程号的信息 看最后一行的 waiting to lock 和 locked,两个线程有没有交叉文章来源地址https://www.toymoban.com/news/detail-480798.html
public class DeadLock {
public static void main(String[] args) {
MyDead myDead = new MyDead();
new Thread(myDead).start();
new Thread(myDead).start();
}
}
class MyDead implements Runnable{
int num=1;
Object lock1=new Object();
Object lock2=new Object();
@Override
public void run() {
try {
sell();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void sell() throws InterruptedException {
synchronized (lock1){
TimeUnit.SECONDS.sleep(2);
synchronized (lock2){
System.out.println(num);
num--;
}
System.out.println(Thread.currentThread().getName()+"解锁");
}
}
public void produce() throws InterruptedException {
synchronized (lock2){
TimeUnit.SECONDS.sleep(10);
synchronized (lock1){
num++;
System.out.println(num);
}
}
}
}
到了这里,关于5.多线程之JUC并发编程2的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!