java之并发编程(下)

9、线程的创建

-传统的创建线程:

使用Runnable接口

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //普通方法创建线程
        A a=new A();
        new Thread(a).start();
    }
    class A implements Runnable{
    @Override
    public void run() {
        System.out.println("开始行动了");
    }
}

-新技术:使用Callable接口

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //新方法:使用FutureTask 包装Runnable或Callable
        //      FutureTask实现了Runnable
        //    1、new FutureTask(Callable)  或 new FutureTask(Runnable,返回给定结果)
        B b=new B();
        FutureTask futureTask = new FutureTask(b);
        new Thread(futureTask).start();//结果会被缓存,提高效率
        //futureTask.get()获取返回结果
        System.out.println(futureTask.get());//可能会阻塞,最好放在最后面
    }
}
class B implements Callable<String>{
    @Override
    public String call() throws Exception {
        return "ok";
    }
}

-比较

Runnable:没有返回值,不需要抛出异常

Callable:有返回值,需要抛出异常

10、JUC的辅助类

-CountDownLatch:

减法计数器 ====无法重置计数

辅助理解:就像一个教室,所有同学都走了才能关门

CountDownLatch( int count):指定线程数量

countdown()方法: 执行减一操作,不会阻塞

await()方法: 当线程数量减为0,才释放

jdk文档描述:
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 
用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 
CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。 
CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await
//减法计数器
public class CountDown {
    public static void main(String[] args) throws InterruptedException {
        //参数是减的总次数,用在需要执行某个特定任务时使用
        CountDownLatch count=new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"出去了");
                count.countDown();// -1  每执行完一个线程就减一,该方法不会阻塞
            },String.valueOf(i)).start();
        }
        count.await();//等待计数器归0后,await才会被唤醒,执行后面的操作
        /*就像一个教室,所有同学都走了才能关门
        * 10个Thread同学都走了,主线程才关门
        * */
        System.out.println("我关门了");
    }
}

-CyclicBarrier:加法计数器

辅助记忆:集齐七颗龙珠召唤神龙

public CyclicBarrier(int parties, Runnable barrierAction)
    parties: 线程数
    barrierAction: 突破障碍后最终执行的动作(由最后到达的一个线程执行)
 await():让线程等待,直到等待的线程数为parties,才释放
 
 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。 
CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

//加法计数器
public class Cyclicbarrier {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        //集齐七颗龙珠召唤神龙
        // CyclicBarrier(线程数,突破障碍后最终执行的动作(由最后到达的一个线程执行))
        CyclicBarrier barrier=new CyclicBarrier(7,()->{
            System.out.println(Thread.currentThread().getName()+"召唤神龙");
        });
            for (int i = 1; i < 8; i++) {
                new Thread(() -> {
                    System.out.println("集齐了" + Thread.currentThread().getName());
                    try {
                        //+1操作直到7,才会释放
                        barrier.await();//等待,直到7个线程都到达才唤醒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }, String.valueOf(i)).start();
            }

    }
}

-Semaphore信号量

通常用于限流,限制本地内容访问

Semaphore(int permits, boolean fair):permits:许可的线程数量,

fair:1、true 公平的,可以保证先进先出==== 优点:确保所有线程都可以访问 2、false 非公平的,

new Semaphore(int permits);默认是非公平的

semaphore.acquire():获得许可,当数量已满时,其他线程只能等待

semaphore.release():信号量+1,然后唤醒等待的线程

//Semaphore信号量
public class SemaPhore {
    public static void main(String[] args) {
        /*new Semaphore(int permits, boolean fair)
         *permits:许可的线程数量,
         * fair:1、true  公平的,可以保证先进先出==== 优点:确保所有线程都可以访问
         *      2、false  非公平的,
         * new Semaphore(int permits);默认是非公平的
         */
        Semaphore semaphore=new Semaphore(5,true);
        for (int i =1; i <=20; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();//获得许可,最多停5个车
                    System.out.println(Thread.currentThread().getName()+"获得车位");
                    TimeUnit.SECONDS.sleep(4);//停车2s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println(Thread.currentThread().getName()+"========释放车位");
                    semaphore.release();//释放许可
                }

            },String.valueOf(i)).start();
        }
    }
}

11、读写锁

读-读:可以共存

读-写:不能共存,必须同步

写-写:不能共存,必须同步

独占锁(写锁):必须同步

共享锁(读锁):不需要同步

-不安全的读写

//读写锁
public class ReadWritelock {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count=new CountDownLatch(10);
        myCache cache=new myCache();
        //写入
        for (int i =1; i <=10; i++) {
            final int temp=i;
            new Thread(()->{
                cache.put(temp,temp);
                count.countDown();
            },String.valueOf(i)).start();
        }
        count.await();
        System.out.println(cache.size());
        //读取
        for (int i =1; i <=10; i++) {
            final int temp=i;
            new Thread(()->{
                cache.get(temp);
            },String.valueOf(i)).start();
        }

    }
}
class myCache{
    private volatile Map <Integer,Object>map=new HashMap<Integer,Object>();
    //写入
    public void put(int i,Object o){
        System.out.println(Thread.currentThread().getName()+"--写入");
        map.put(i,o);
        System.out.println(Thread.currentThread().getName()+"写入ok");
    }
    //读取
    public void get(int s){
        System.out.println(Thread.currentThread().getName()+"--读取");
        map.get(s);
        System.out.println(Thread.currentThread().getName()+"读取ok");
    }
    public int size(){
        return map.size();
    }
}

-读取安全:ReadWriteLock

public class ReadWriteLock2 {
    public static void main(String[] args) {
        myCache2 cache=new myCache2();
        //写入
        for (int i =1; i <=10; i++) {
            final int temp=i;
            new Thread(()->{
                cache.put(temp,temp);
            },String.valueOf(i)).start();
        }
        //读取
        for (int i =1; i <=10; i++) {
            final int temp=i;
            new Thread(()->{
                cache.get(temp);
            },String.valueOf(i)).start();
        }
    }
}
class myCache2{
    private volatile Map<Integer,Object> map=new HashMap<Integer,Object>();
    //比ReentrantLock()更加细粒度,锁的用法差不多
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    //Lock lock=new ReentrantLock();
    //写入
    public void put(int i,Object o){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"--写入");
            map.put(i,o);
            System.out.println(Thread.currentThread().getName()+"写入ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    //读取
    public void get(int s){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"--读取");
            map.get(s);
            System.out.println(Thread.currentThread().getName()+"读取ok");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }

    }
}

12、BlockingQueue--阻塞队列

image

应用:多线程并发,线程池

方式 抛出异常 特殊值 一直阻塞 定时阻塞
添加(队列已满时) add() offer() put() offer(element,time,timeunit)
移除(队列为空时) remove() poll() take() poll(time,timeunit)
获取队列首元素 element() peek() 不可用 不可用

抛出异常:BlockingQueue实现了------>Queue接口------>Collecion接口,Collection.add()拒绝添加元素时,必须抛出异常。
特殊值:Queue.offer()会返回插入的成功与否
一直阻塞:BlockingQueue.put()自己新的添加方法

/*
* 抛出异常
* */
public static void demo1(){
    BlockingQueue queue=new ArrayBlockingQueue(3);
    System.out.println(queue.add("a"));//返回布尔值
    System.out.println(queue.add("b"));
    System.out.println(queue.add("c"));
    System.out.println(queue.element());//查看队首元素
    //IllegalStateException 抛出异常
    //System.out.println(queue.add("d"));//队列已满,添加元素,就抛出异常
    System.out.println("==================");
    System.out.println(queue.remove());//返回移除元素
    System.out.println(queue.element());//查看队首元素
    System.out.println(queue.remove());
    System.out.println(queue.remove());
    //NoSuchElementException 抛出异常
    //System.out.println(queue.remove());////队列为空,移除元素,就抛出异常
}
/*
 * 不抛异常
 * */
public static void demo2() throws InterruptedException {
    BlockingQueue queue=new ArrayBlockingQueue(3);
    System.out.println(queue.offer("a"));//返回布尔值
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    System.out.println(queue.peek());//查看队列首部元素
    //队列已满,添加元素,就会返回false
    //System.out.println(queue.offer("d"));
    System.out.println("===============");
    System.out.println(queue.poll());//返回移除元素
    System.out.println(queue.peek());//查看队列首部元素
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    //System.out.println(queue.poll());//队列为空,移除元素,就返回null
}
/*
 * 一直等待、阻塞
 * */
public static void demo3() throws InterruptedException {
    BlockingQueue queue = new ArrayBlockingQueue(3);
    queue.put("a");//无返回值
    queue.put("b");
    queue.put("c");
    //队列已满,添加元素,就一直阻塞,直到有元素出队
    //queue.put("d");
    System.out.println(queue.take());//返回移除元素
    System.out.println(queue.take());
    System.out.println(queue.take());
    System.out.println(queue.take());//队列为空,移除元素,就一直等待直到
}
/*
* 定时等待
* */
public static void demo4() throws InterruptedException {
    BlockingQueue queue=new ArrayBlockingQueue(3);
    System.out.println(queue.offer("a"));//返回布尔值
    System.out.println(queue.offer("b"));
    System.out.println(queue.offer("c"));
    //队列已满,添加元素,只等待4s,如果没有出队就停止阻塞,并返回false
    System.out.println(queue.offer("d",4, TimeUnit.SECONDS));
    System.out.println("===============");
    System.out.println(queue.poll());//返回移除元素
    System.out.println(queue.poll());
    System.out.println(queue.poll());
    //队列为空,移除元素,等待4s,如果没有元素入队就停止等待,并返回null
    System.out.println(queue.poll(4,TimeUnit.SECONDS));
}

13、同步队列-SynchronousQueue

package com.company.queue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
//同步队列,SynchronousQueue 不能存储元素,放入一个必须取出,才能放入下一个
public class synchronousQueue {
    public static void main(String[] args) {
        BlockingQueue<String> queue= new SynchronousQueue<String>();
        new Thread(()->{
            try {
                queue.put("1");
                System.out.println("放了1");
                queue.put("2");
                System.out.println("放了2");
                queue.put("3");
                System.out.println("放了3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"a").start();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());
                System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());
                System.out.println(Thread.currentThread().getName()+"===>拿出"+queue.take());;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"b").start();

    }
}

14、线程池

三大线程池:Executors.newSingleThreadExecutor();//单一线程池,只能创建一个线程

​ Executors.newFixedThreadPool(5);//创建固定大小的线程池

​ Executors.newCachedThreadPool();//缓存线程池,没有固定数量

-Executors创建三大线程池

//线程池的创建,由线程池创建线程
public class threadPool {
    public static void main(String[] args) {
        //三大线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();//单一线程池,只能创建一个线程
        //ExecutorService executorService = Executors.newFixedThreadPool(5);//固定大小的线程池
        //ExecutorService executorService = Executors.newCachedThreadPool();//缓存线程池,没有固定数量
        try {
            for (int i = 0; i <40 ; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行===>");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //确保最后一定能关闭
            executorService.shutdown();//关闭线程池
        }
    }
}

-七大参数

源码分析推导七大参数

public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>(),
                                threadFactory));
}

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,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
//实际线程池创建者

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}
//七大参数
public ThreadPoolExecutor(int corePoolSize,//池子的核心线程数
                          int maximumPoolSize,//池子最大线程数
                          long keepAliveTime,//在池子中住的时间
                          TimeUnit unit,//设定时间
                          BlockingQueue<Runnable> workQueue,//阻塞队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler) {//拒绝执行处理

理解:

//最好的创建线程池的方式
public class threadPool {
    public static void main(String[] args) {
        /*
        *    new ThreadPoolExecutor.AbortPolicy());//不处理,抛出异常RejectedExecutionException
        *    new ThreadPoolExecutor.CallerRunsPolicy());//不处理也不抛出异常,哪里来的回哪里去
        *    new ThreadPoolExecutor.DiscardPolicy());//直接丢掉任务,不处理,不抛出异常
        *    new ThreadPoolExecutor.DiscardOldestPolicy());//丢弃最早的任务,让新的任务替代旧任务的位置,不抛异常
        * */
        ExecutorService executorService=new ThreadPoolExecutor(
                3,     //核心线程数(一直再内存中,不会被回收),任务来了,核心线程不够时,任务就进入队列,队列也满了就再创建非核心线程,最多6个
                6,     //最大线程数
                2,     //如果核心线程超过2s没有任务处理,就关闭3个核心线程。
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(4),  //等候室
                Executors.defaultThreadFactory(),//创建线程的工厂
                new ThreadPoolExecutor.DiscardOldestPolicy());//尝试和最早的竞争,不抛异常
        try {
            for (int i = 0; i <15 ; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"执行===>");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //确保最后一定能关闭
            executorService.shutdown();//关闭线程池
        }


    }
}

-总结:

lambda表达式,函数式接口,链式编程,stream流式计算====>很重要,必须会
1、三大线程池--->必须会
2、七大参数---->必须会
3、最好使用new ThreadPoolExecutor()创建线程池,不要使用Executors,因为它不安全
    
    问题:线程池的最大线程数应该设置为多少最好?
    回答:cpu密集型:=核数,每核分配一个线程,这样效率最高
         I/O密集型:>多个程序都要使用I/O线程

15、四大函数式接口(重点)

新时代程序员必会:lambda表达式,函数式接口,链式编程,stream流式计算

函数式接口:只有一个方法的接口。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}
/**
 *   Function   函数型接口:一个输入参数,返回输入参数
 * */
public class funtionTest {
    public static void main(String[] args) {
//        Function f=new Function<String,String>(){
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        Function f=(str)->{return str;};
        System.out.println(f.apply("wq"));
    }
}
/**
 *  Predicate  判定型接口:一个输入参数,返回布尔值
 * */
public class predicateTest {
    public static void main(String[] args) {
//        Predicate <String>predicate=new Predicate<String>() {
//            @Override
//            public boolean test(String str) {
//                System.out.println("wq");
//                return str.isEmpty();
//            }
//        };

        Predicate <String>predicate=(str)->{return str.isEmpty();};
        System.out.println(predicate.test("wq"));

    }
}
/**
 *  Consumer 消费型接口:一个输入参数,没有返回值。吃掉了一个东西
 * */
public class consumerTest {
    public static void main(String[] args) {
//        Consumer consumer=new Consumer<String>() {
//            @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer consumer=(str)->{System.out.println(str);};
        consumer.accept("wq");
    }
}
package com.company.function;
import java.util.function.Supplier;
/**
 *  Supplier 供给型接口:没有输入参数,有返回值
 * */
public class supplyTest {
    public static void main(String[] args) {
//        Supplier supplier=new Supplier<String>() {
//            @Override
//            public String get() {
//                return new String("wq");
//            }
//        };
        Supplier supplier=()->{return "wq";};
        System.out.println(supplier.get());
    }
}

16、stream流式计算

链式编程+流式计算


/*
*  流式计算stream
* */
public class streamTest {
    public static void main(String[] args) {
        User u1=new User(1,"a",23);
        User u2=new User(2,"b",42);
        User u3=new User(3,"c",30);
        User u4=new User(4,"d",28);
        User u5=new User(5,"e",26);
        User u6=new User(6,"f",24);
        List<User> list= Arrays.asList(u1 ,u2, u3,u4,u5,u6);
        list.stream()
                .filter((u)->{return u.getId()%2==0;}) //过滤出id为偶数的用户
                .filter((u)->{return u.getAge()>24;})  //过滤大于40岁的用户
                .map((u)->{return u.getName().toUpperCase(Locale.ROOT);})//获取过滤出 的用户的姓名的大写
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})   //将姓名倒序排序
                .limit(1)  //输出用户数
                .forEach(System.out::println);  //打印筛选出的用户的姓名
    }
}

17、JMM理解

-计算机cpu,内存分析

问题:什么 是JMM

JMM:java内存模型,屏蔽各系统和硬件之间的差异,让代码在不同的平台达到相同的访问效果。

JMM将内存分为两部分,主内存和工作内存,每个线程都有自己的工作内存。主内存就是物理存储空间,工作内存就是高速缓存和寄存器。他们要将使用的数据从主内存中拷贝一份,最后将数据返回给主内存.
缺陷:当多个线程使用了同一个变量,最后他们返回的数据都不一样,相互之间不能知道变量的赋值情况。

如果每次直接从主内存中读取数据,这样的访问速度是非常慢的,对性能的影响非常大。

所以JMM规定每个线程都有自己的工作内存,JMM规定了一套自己的标准来解决线程之间的问题。

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

    • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
    • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
    • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
    • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
    • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
    • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
    • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
    • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

    • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
    • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
    • 不允许一个线程将没有assign的数据从工作内存同步回主内存
    • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
    • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
    • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
    • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
    • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

线程无法见到主内存的值的变化

//陷入死循环
public class JMMtest {
    private static int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0){

            }
        }).start();
        TimeUnit.SECONDS.sleep(2);
        num=1;
        System.out.println(num);
    }
}

18、异步回调

异步回调的理解

没有返回值的异步回调

//没有返回值  CompletableFuture.runAsync()
CompletableFuture <Void>completableFuture=CompletableFuture.runAsync(()->{
    System.out.println("runAsync====void");
});
System.out.println("wq");
completableFuture.get();

有返回值的异步回调

//有返回值  CompletableFuture.supplyAsync()
CompletableFuture <String>completableFuture=CompletableFuture.supplyAsync(()->{
    //int p=2/0;
    return "123";
});
System.out.println("wq");
completableFuture.whenComplete((t,u)->{//t,u的数据类型取决于CompletableFuture传入的
    System.out.println("t====>"+t);//接收顺利执行的返回值
    System.out.println("u====>"+u);//接收异常执行的返回值,接收e
});
completableFuture.exceptionally((e)->{//执行异常时 的处理
    System.out.println(e.getCause());
    return "error";
});
顺利执行: 
wq
t====>123
u====>null
执行异常:
wq
t====>null
u====>java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero

19、Volatile

Volatile是java虚拟机提供的轻量级的同步机制。

1、保证可见性

原理:使用Volatile的变量,这个变量的存取不能缓存到寄存器。编译器知道这个变量可能会在外部改变,所以每次存取都会重新从内存中读取,这才保证了每个线程访问到的数据都和内存保持一致。以此实现变量的可见性。

//不加volatile就会陷入死循环
public class JMMtest {
    private static  volatile int num=0;
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            while(num==0){

            }
        }).start();
        TimeUnit.SECONDS.sleep(2);
        num=1;
        System.out.println(num);
    }
}

2、不保证原子性

原子性:不可分割

就是在执行过程中不能被中断被打扰。

public class atomicTest {
    private static volatile int num=0;
    public static void add(){
        num++;
    }
    public static void main(String[] args) {
        //让十个线程都执行1000次add方法---理论上num=20000,到实际结果少于20000
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 2000; j++) {
                    add();
                }

            }).start();
        }
        if(Thread.activeCount()>2){//默认活动的线程 main,gc
            Thread.yield();//让main线程礼让
        }
        System.out.println("num="+num);
    }
}

我们知道使用lock和synchronized能够解决问题。使用原子性的数据类型。

这些原子级的类是最底层的,直接与内存进行取值赋值,Unsafe类很特殊。

package com.company.volatileTest;

import java.util.concurrent.atomic.AtomicInteger;
//原子性测试   AtomicInteger原子级的int
public class atomicTest {
    private static volatile AtomicInteger num=new AtomicInteger();
    public static void add(){
        num.getAndIncrement();//执行num+1操作
    }
    public static void main(String[] args) {
        //num一定=20000
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 2000; j++) {
                    add();
                }

            }).start();
        }
        if(Thread.activeCount()>2){//默认活动的线程 main,gc
            Thread.yield();//让main线程礼让
        }
        System.out.println("num="+num);
    }
}

3、禁止指令重排

由于内存访问速度远不及CPU处理速度,为提高机器整体性能,在硬件上引入硬件高速缓存Cache,加速对内存的访问。另外在现代CPU中指令的执行并不一定严格按照顺序执行,没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线,提高执行速度,以上是硬件级别的优化。

编译器优化常用的方法有:将内存变量缓存到寄存器和调整指令顺序充分利用CPU指令流水线等,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。

例如:

int x=0;  1
int y=1;  2
x=x+1;    3
y=x+y;    4
//我们以为的执行顺序:1234
//但是指令重排可能会变成:1324   2134  最后结果都是一样的

不可能出现 3124,cpu的指令重排前提:要保证数据之间的依赖性

Volatile:1、产生内存屏障(单例模式中使用最多),防止指令重排 2、保证可见性

20、单例模式

单例就是构造器私有,单例模式分为两种:饿汉式和懒汉式。

在java中枚举类就是使用单例模式的。

饿汉式单例

//饿汉式单例
public class Hungry {
    //还没使用前就创建了实例加载了内存,如果不使用容易浪费空间
    private int[] b=new int[1024];
    private int[] c=new int[1024];
    private int[] d=new int[1024];
    private static Hungry hungry = new Hungry();//饿汉式,提前创建好对象

    private Hungry() {

    }

    public static Hungry getInstance() {
        return hungry;
    }

}

懒汉式单例DCL

//饿汉式进阶:懒汉式单例DCL
//由于饿汉式对空间的浪费
public class LayzMan {
    //volatile+双重检测锁
    private static volatile LayzMan layzMan;//volatile防止指令重排
    private LayzMan(){
        System.out.println(Thread.currentThread().getName()+"----ok");
    }
    /*
    *  synchronized可以解决问题,但是存在问题
    * new LayzMan()的过程:1、数据加载到内存  2、执行构造方法,初始化对象  3、对象指向内存空间
    *   这个过程不是原子性操作,cpu可以执行 123   极端情况下可能指令重排 132
    *    所以使用volatile
    * */
    public static LayzMan getInstance(){
        if (layzMan == null) {//双重检测懒汉单例
            synchronized (LayzMan.class) {//  1、尝试用synchronized解决并发问题,可以解决
                if (layzMan == null) {//需要时在创建
                    layzMan = new LayzMan();
                }
            }
        }
        return layzMan;
    }
    //多线程并发测试懒汉单例:1,按道理应该只能创建一个懒汉单例,实际不是
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
               getInstance();
            }).start();
        }
    }
}

反射破坏懒汉单例

/*按道理,layzMan和layzMan1他们是同一个对象
* 但是实际不是,说明Class可以破坏单例,懒汉式也不是绝对安全的
*
*/

class test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        LayzMan layzMan=LayzMan.getInstance();
        Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LayzMan layzMan1 = constructor.newInstance();
        System.out.println(layzMan);
        System.out.println(layzMan1);
    }
}

解决反射破坏问题

private LayzMan() {
    synchronized (LayzMan.class) {
        if (layzMan!=null){
            throw new RuntimeException("不要试图通过反射破坏单例");
        }
    }
}

//  结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:480)
	at com.company.single.test.main(LayzMan.java:34)
Caused by: java.lang.RuntimeException: 不要试图通过反射破坏单例
	at com.company.single.LayzMan.<init>(LayzMan.java:14)
	... 6 more

再破坏

/**
** 不去调用getInstance(),直接通过反射创建实例
**/

class test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //LayzMan layzMan=LayzMan.getInstance();
        Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LayzMan layzMan = constructor.newInstance();
        LayzMan layzMan1 = constructor.newInstance();
        System.out.println(layzMan);
        System.out.println(layzMan1);
    }
}

结果:com.company.single.LayzMan@4eec7777
com.company.single.LayzMan@3b07d329

解决再破坏

private static boolean flag=false;
//volatile+双重检测锁
private static volatile LayzMan layzMan;//volatile防止指令重排
private LayzMan() {
    if(flag==false) {
        flag = true;
    }else {
            throw new RuntimeException("不要试图通过反射破坏单例");
        }
    }

再破坏

class test{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        //LayzMan layzMan=LayzMan.getInstance();
        Constructor<LayzMan> constructor = LayzMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Field field = LayzMan.class.getDeclaredField("flag");
        field.setAccessible(true);
        LayzMan layzMan = constructor.newInstance();
        field.set(layzMan,false);//layzMan初始化后重新设值,破坏
        LayzMan layzMan1 = constructor.newInstance();
        System.out.println(layzMan);
        System.out.println(layzMan1);
    }

反射不能破坏枚举

package com.company.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum Enumsola {
    INSTANCE;
    public static Enumsola getInstance(){
        return INSTANCE;
    }

}
//反射不能破坏枚举
class test0{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Enumsola instance1 = Enumsola.getInstance();
        Constructor<Enumsola> constructor = Enumsola.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        Enumsola instance2 = constructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

结果:Exception in thread "main" java.lang.NoSuchMethodException:

结果说没有这个构造方法,但是源码当中有这个方法,通过命令行编译,无法找到解决问题

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.company.single;

public enum Enumsola {
    INSTANCE;

    private Enumsola() {
    }

    public static Enumsola getInstance() {
        return INSTANCE;
    }
}

jad反编译枚举

经过反编译,再cmd中使用命令,jad Enumsola.class ,会生成一个jad反编文件

惊讶发现: private Enumsola(String s, int i)有参构造,java源码骗了我们

public final class Enumsola extends Enum
{

    public static Enumsola[] values()
    {
        return (Enumsola[])$VALUES.clone();
    }

    public static Enumsola valueOf(String name)
    {
        return (Enumsola)Enum.valueOf(com/company/single/Enumsola, name);
    }

    private Enumsola(String s, int i)
    {
        super(s, i);
    }

解决枚举

package com.company.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public enum Enumsola {
    INSTANCE;
    public static Enumsola getInstance(){
        return INSTANCE;
    }

}
//反射不能破坏枚举
class test0{
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Enumsola instance1 = Enumsola.getInstance();
        Constructor<Enumsola> constructor =       Enumsola.class.getDeclaredConstructor(String.class,int.class);///重点
        constructor.setAccessible(true);
        Enumsola instance2 = constructor.newInstance();
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

最后才出现了我们想看到的异常

Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects

21、CAS理解

知道什么是CAS?

CAS:比较当前值与内存空间是否是期望的值,如果不是就一直循环

public class CASdemo {
    //CAS 就是compareAndSet:比较并交换
    /**expectedValue  期望值   newValue 新的值
    *   如果当前值=expectedValue,就将它的值改为newValue;CAS是CPU的并发原语
     * */
    public static void main(String[] args) {
        AtomicInteger num=new AtomicInteger(2020);
        num.compareAndSet(2020, 2021);
        System.out.println(num.get());//num=2021
        //当前值变为2021,与2020比较,不相等
        num.getAndIncrement();//+1
        System.out.println(num.compareAndSet(2020, 2021));
        System.out.println(num.get());
    }
}

getAndIncrement()源码分析

public final int getAndIncrement() {
    return U.getAndAddInt(this, VALUE, 1);
}
//Unsafe类
/**offset: o的地址偏移值
*/
public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);//获取地址空间的值
    } while (!weakCompareAndSetInt(o, offset, v, v + delta));
    return v;        //如果o的地址偏移offset后的地址空间的值等于 v,就执行v + delta
}

java无法直接操作内存,需要调用c++,c++可以直接操作内存。

但是Unsafe类是java留的后门,可以直接操作内存,效率很高

CAS缺点:1、循环耗时 2、一次只能保证一个共享变量的原子性 3、ABA问题

22、原子引用

解决ABA问题:使用带版本号的原子类

public class ABAdemo {
    //初始值 1 ; 初始版本号 1
    static AtomicStampedReference<Integer> num=new AtomicStampedReference(1,1);
    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = num.getStamp();//获取初始版本号
            System.out.println(Thread.currentThread().getName()+"===>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //每更新一次,版本号加1,
            System.out.println("A=="+num.compareAndSet(1, 2,
                    num.getStamp(), num.getStamp() + 1));
            System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
            System.out.println("A=="+num.compareAndSet(2, 1,
                    num.getStamp(), num.getStamp() + 1));
            System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
        },"A").start();
        new Thread(() -> {
            int stamp = num.getStamp();//获取初始版本号
            System.out.println(Thread.currentThread().getName()+"===>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B=="+num.compareAndSet(1, 6,
                    num.getStamp(), num.getStamp() + 1));
            System.out.println(Thread.currentThread().getName()+"===>"+num.getStamp());
        },"B").start();

    }
}

23、可重入锁

回顾锁

公平锁:非公平锁:

读锁:写锁:

可重入锁(递归锁):就好比我拥有了家里大门的钥匙,就自动有了里面房间的钥匙

synchronized锁实现可重入

public class syncDemo {
    public static void main(String[] args) {
        Phone phone=new Phone();
        new Thread(()->{
            phone.send();
        },"A").start();
        new Thread(()->{
            phone.send();
        },"B").start();
    }

}
class Phone{
    public synchronized void send(){
        System.out.println(Thread.currentThread().getName()+"发短信");
        call();
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"打电话");
    }
}
结果:
A发短信
A打电话
B发短信
B打电话

lock锁实现可重入

public class lockDemo {
    public static void main(String[] args) {
        Phone1 phone=new Phone1();
        new Thread(()->{
            phone.send();
        },"A").start();
        new Thread(()->{
            phone.send();
        },"B").start();
    }

}
class Phone1{
    Lock lock=new ReentrantLock();
    public void send(){
        lock.lock();//注意lock锁必须成对使用 lock.lock()  lock.unlock();加几把锁,就要解几把锁
        try {
            System.out.println(Thread.currentThread().getName()+"发短信");
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public  void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"打电话");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

24、自旋锁

使用CAS自定义自旋锁

package com.company.returnlock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class spinLock {
    //Integer  0
    //Thread  null
    private AtomicReference<Thread> spin=new AtomicReference(null);
    public void mylock() throws InterruptedException {
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"进入自旋");
        while(!spin.compareAndSet(null,thread)){
            TimeUnit.SECONDS.sleep(1);
            System.out.println(Thread.currentThread().getName());
        }

    }
    public void myunlock(){
        Thread thread=Thread.currentThread();
        spin.compareAndSet(thread,null);
        System.out.println(Thread.currentThread().getName()+"结束自旋");
    }
}
class test{
    public static void main(String[] args) {
        spinLock lock=new spinLock();
        new Thread(()->{
            try {
                lock.mylock();
                TimeUnit.SECONDS.sleep(4);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myunlock();
            }
        },"A").start();
        new Thread(()->{
            try {
                lock.mylock();
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.myunlock();
            }
        },"B").start();
    }
}

25、死锁

什么是死锁:

因互相抢夺对方的资源,而相互等待

死锁产生的原因

1、对不可剥夺的资源的竞争,才有可能发生死锁。对可剥夺资源不会产生死锁

2、请求和释放资源的顺序不当

死锁案例

package com.company.returnlock;

import java.util.concurrent.TimeUnit;

public class Deadlock implements Runnable{
    private String lock1;
    private String lock2;
    public Deadlock(String i,String j){
        this.lock1=i;
        this.lock2=j;
    }
    @Override
    public void run() {
        synchronized (lock1){
            System.out.println(Thread.currentThread().getName()+"lock1==>"+lock2);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lock2){
               System.out.println(Thread.currentThread().getName()+"lock2==>"+lock1);
           }
        }
    }
}
class test2{
    static String lock1="lock1";
    static String lock2="lock2";

    public static void main(String[] args) {
        new Thread(new Deadlock(lock1,lock2),"A").start();
        new Thread(new Deadlock(lock2,lock1),"B").start();
    }

}

死锁排查

工作,面试中,如何排查死锁:

1、日志

2、查看堆栈信息

如何查看java堆栈信息

1、jps: jdk/bin 有jps工具 在ideal终端使用 jps -l 命令可以查看当前java程序运行的进程号

image

2、jstack 进程号

image

3、查看信息

image

posted @   阿落小世界  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示