JUC并发编程

JUC 并发

普通业务的线程代码不方便Runnable 没有返回值、效率比可调concurrent相对低!
Java无法直接操作硬件
公平锁:不可以插队
非公平锁:可以插队

 //原始非公平锁
public ReentrantLock() { 
        sync = new NonfairSync(); 
    } 
    //重载方法:
public ReentrantLock(boolean fair) { 
        sync = fair ? new FairSync() : new NonfairSync(); // FairSync公平锁NonfairSync非
    }

synchronized和lock的区别

① Synchronized 是 Java 内置关键字,Lock 是一个 Java 类

② Synchronied无法判断取锁的状态,Lock 可以判断

③ Synchronied 会自动释放锁,Lock 必须要手动加锁和手动释放锁!可能会遇到死锁

④ Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

⑤ Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

⑥ Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

注: “广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。“

模拟生产者、消费者示例:

① Synchronized 是 Java 内置关键字,Lock 是一个 Java 类

② Synchronied无法判断取锁的状态,Lock 可以判断

③ Synchronied 会自动释放锁,Lock 必须要手动加锁和手动释放锁!可能会遇到死锁

④ Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

⑤ Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

⑥ Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。

代码示例

public static void main(String[] args) {

        Data data = new Data();

//        创建一个生产者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
//         创建一个消费者
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
    }

}
//这是一个缓冲类,生产和消费之间的仓库
class Data{
//    这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

//    +1,利用关键字加锁
    public synchronized void increment() throws InterruptedException {
//        首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁
        if(num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
//      通知其他线程 +1 执行完毕
        this.notifyAll();
    }

//    -1
    public synchronized void decrement() throws InterruptedException {
        //        首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
        if(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
//        通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

以上会存在虚假唤醒的问题!
就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后

防止虚假唤醒

//这是一个缓冲类,生产和消费之间的仓库
class Data{
    //    这是仓库的资源,生产者生产资源,消费者消费资源
    private int num = 0;

    //    +1,利用关键字加锁
    public synchronized void increment() throws InterruptedException {
//        首先查看仓库中的资源(num),如果资源不为0,就利用 wait 方法等待消费,释放锁

//        使用 if 存在虚假唤醒
        while (num!=0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
//      通知其他线程 +1 执行完毕
        this.notifyAll();
    }

    //    -1
    public synchronized void decrement() throws InterruptedException {
        //        首先查看仓库中的资源(num),如果资源为0,就利用 wait 方法等待生产,释放锁
        while(num==0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
//        通知其他线程 -1 执行完毕
        this.notifyAll();
    }
}

八锁现象

八锁链接: link.

集合安全问题

CopyOnWrite //写入时复制、 COW 计算机程序设计领域的一种优化策略

CopyOnWriteArrayList比Vector强:
Vector的add方法加了synchronized 加了synchronized关键字效率就会降低

CopyOnWriteArraySet

Map安全的集合:ConcurrentHashMap
Callable:
注意的细节问题:
1、有缓存
2、可能会阻塞
为什么使用Callable:
不论是直接继承Thread,还是实现Runnable接口都需要调用Thread类中的start方法去向操作系统请求io,cup等资源。
但是run方法没有返回值如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
但是Callable中的Call方法带有返回值!

代码示例

package com.Kari.callabledemo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author Kari
 * @date 2021年05月19日11:16 上午
 */
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);//适配类、因为Thread无法直接调用Callable
        new Thread(futureTask,"线程").start();
        new Thread(futureTask,"线程2").start();//结果会被缓存、效率高
        String o = String.valueOf(futureTask.get());//可能会产生阻塞,所以应该放在最后或者使用异步通信操作
        System.out.println(o);
    }
}
class MyThread implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("Good Job");
        return "yeah";
    }
}

常用的辅助类

CountDowmLatch:减法计数器

countDownLatch.countDown();//计数器减1
countDownLatch.await();//等待计数器归零才会执行后面的操作

代码示例
//等线程中的内容全部执行完才会执行 out 
public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(5);//总数为5
    for (int i = 0; i <5 ; i++) {
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"GG");
            countDownLatch.countDown();//计数器减1
        },String.valueOf(i)).start();
    }
    countDownLatch.await();//等待计数器归零才会执行后面的操作
    System.out.println("out");

}

CyclicBarrier:加法计数器

代码示例
public static void main(String[] args) {
    CyclicBarrier barrier = new CyclicBarrier(8,()->{
        System.out.println("全部执行完成");
    });
    for (int i = 0; i <9 ; i++) {
        final int I = i;//new thread是用lambda简化的一个类所以无法直接去到i 需要通过一个final常量
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+":::"+I);
            try {
                barrier.await();//等待加法计数器达到指定值:8
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        },String.valueOf(i)).start();
    }
}

Smaphore:信号量

代码示例
/**
 * semaphore.acquire(); 获得,如果满了则一直等待到被释放为止
 * semaphore.release();释放; 会将当前到信号量释放加一,唤醒等待到线程
 * 作用:
 * 1、并发限流,控制最大的线程数
 * 2、多个共享资源互斥到使用
 */
public static void main(String[] args) {
    Semaphore semaphore = new Semaphore(1);
    new Thread(()->{
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"进入停车位");
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"离开停车位");
            semaphore.release();
        }
    },"线程1").start();

    new Thread(()->{
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"进入停车位");
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"离开停车位");
            semaphore.release();
        }
    },"线程2").start();
    new Thread(()->{
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName()+"进入停车位");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println(Thread.currentThread().getName()+"离开停车位");
            semaphore.release();
        }
    },"线程3").start();
}

读写锁:更加细粒度读控制

独占锁(写锁)一次只能被一个线程占有
共享锁(读锁) 一次可被多个线程同时执行
读-读 共存
读-写 不可共存
写-写 不可共存

代码示例

public static void main(String[] args) {
    MyCacheLock cacheLock = new MyCacheLock();
    for (int i = 1; i < 8; i++) {
        final int ii = i;
        new Thread(() -> {
            cacheLock.put(String.valueOf(ii), String.valueOf(ii + "sd"));
        }, String.valueOf(ii)).start();
    }
    for (int i = 1; i < 8; i++) {
        final int ii = i;
        new Thread(() -> {
            cacheLock.get(String.valueOf(ii));
        }, String.valueOf(ii)).start();
    }
}
}

class MyCacheLock {
private volatile Map<String, String> map = new HashMap<String, String>();
//读写锁 更加细粒度读控制
ReadWriteLock lock = new ReentrantReadWriteLock();

//写
public void put(String key, String value) {
    lock.writeLock().lock();
    try {
        System.out.println("写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完成!");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.writeLock().unlock();
    }
}

//读
public String get(String key) {
    lock.readLock().lock();
    try {
        System.out.println("读取" + key);
        map.get(key);
        System.out.println(Thread.currentThread().getName() +"读取完成!");

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.readLock().unlock();
        return map.get(key);
    }

}

阻塞队列

在这里插入图片描述
BLockingQueue是 Queue接口的实现类典型的阻塞队列
Queue是和Set、List同级的接口都继承了Collection

BLockingQueue的四组API:

方式抛出异常有返回值,不抛异常阻塞等待超时等待
添加add()offer()put()offer(, ,)
移除remove()poll ()take()poll(,)
检测队首元素element()peek()--
代码示例展示:
 //抛出异常
    public static void test1() {
        //创建并设置队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("c"));
        //出现异常:Queue full 队列满了
//        System.out.println(blockingQueue.add("d"));

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());

        System.out.println(blockingQueue.element());
        //异常:NoSuchElementException
//        System.out.println(blockingQueue.remove());
    }

    //有返回值,不抛出异常
    public static void test2() {
        //创建并设置队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("c"));
        //返回false 不出现异常
        //   System.out.println(blockingQueue.offer("d"));

        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //返回null 不出现异常
        //  System.out.println(blockingQueue.poll());

        System.out.println(blockingQueue.peek());


    }

    //阻塞等待
    public static void test3() throws InterruptedException {
        //创建并设置队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
       // blockingQueue.put("d");//死等
        System.out.println(blockingQueue);
        blockingQueue.take();
        blockingQueue.take();
        blockingQueue.take();
       // blockingQueue.take();//死等
        System.out.println(blockingQueue);

    }

    //超时等待
    public static void test4() throws InterruptedException {
        //创建并设置队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(3);

        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        blockingQueue.offer("d",3, TimeUnit.SECONDS);//等待3秒
        System.out.println(blockingQueue);
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll();
        blockingQueue.poll(3,TimeUnit.SECONDS);//等待3秒
        System.out.println(blockingQueue);

    }

SynchronousQueue(同步队列)

没有容量、不存储元素,put一个元素必须take取出来否则不能继续put

代码示例:
 public static void main(String[] args) {
        SynchronousQueue<String> queue = new SynchronousQueue<String>();
        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"==>"+"put a");
                queue.put("a");
                System.out.println(Thread.currentThread().getName()+"==>"+"put b");
                queue.put("b");
                System.out.println(Thread.currentThread().getName()+"==>"+"put c");
                queue.put("c");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"Put").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
                TimeUnit.SECONDS.sleep(2);
                System.out.println(Thread.currentThread().getName()+"==>"+queue.take());
            } catch ( InterruptedException e )  {
                 e . 打印堆栈跟踪();} } , "取" ) 。开始( ) ; } /* 输出结果:Put==>put a Take==>a Put==>put b Take==>b Put==>put c Take==>c */

线程池

三大方法、七大参数、四种拒绝策略
程序的运行本质:占用系统资源、优化资源使用 =》池化技术
线程池、连接池、内存池、对象池; 创建和销毁十分的浪费资源
池化技术:事先准备好一些资源,有人用到就来拿,用完之后还回来
线程池的好处:
1、降低资源消耗
2、提高响应速度
3、方便管理
线程复用、可以控制最大并发数、管理线程

线程三大方法:

在这里插入图片描述

代码示例:
/**
 * @author Kari
 * @date 2021年05月21日3:33 下午
 */
//Executors  工具类
public class demo {
    public static void main(String[] args) {
        ExecutorService executorService=null;
        /**
         * 下面代码输出:
         * pool-1-thread-1ok
         * pool-1-thread-1ok
         * pool-1-thread-1ok
         * pool-1-thread-1ok
         * pool-1-thread-1ok
         */
        executorService = Executors.newSingleThreadExecutor();//单个线程
        /**
         *pool-2-thread-2ok
         * pool-2-thread-3ok
         * pool-2-thread-1ok
         * pool-2-thread-2ok
         * pool-2-thread-3ok
         */
        executorService = Executors.newFixedThreadPool(3);//创建一个固定的线程池大小
        /**
         * pool-3-thread-1ok
         * pool-3-thread-5ok
         * pool-3-thread-4ok
         * pool-3-thread-3ok
         * pool-3-thread-2ok
         */
        executorService = Executors.newCachedThreadPool();//可伸缩的
        try{
            for (int i = 0; i <5 ; i++) {
                executorService.execute(()->{
                    //使用线程池之后就用线程池来创建线程
                    System.out.println(Thread.currentThread().getName()+"ok");
                });
            }
        }catch (Exception e){

        }finally {
            executorService.shutdown();//关闭线程池
        }
    }
}

四种拒绝策略

在这里插入图片描述

new ThreadPoolExecutor.AbortPolicy());//银行满了还有人来,不处理这个人并且抛出异常
new ThreadPoolExecutor.CallerRunsPolicy());//哪里来的去哪里(会通过main线程去执行)
new ThreadPoolExecutor.DiscardPolicy());//队列满了,不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去替换最老的 也不会抛出异常
代码示例:
public static void main(String[] args) {
        //自定义线程池
        ExecutorService service = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
//                new ThreadPoolExecutor.AbortPolicy());//银行满了还有人来,不处理这个人并且抛出异常
//                new ThreadPoolExecutor.CallerRunsPolicy());//哪里来的去哪里(会通过main线程去执行)
//                new ThreadPoolExecutor.DiscardPolicy());//队列满了,不抛出异常
                new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去替换最老的 也不会抛出异常
        try {
            for (int i = 0; i < 100; i++) {
                service.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "jjj");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            service.shutdown();
        }

    }
ThreadPoolExecutor各个参数解析:

重点问题:最大线程数如何定义
1、CPU密集型:几核的电脑就设置为几,实现保存CPU最高效率运行

获取cpu核数
System.out.println(Runtime.getRuntime().availableProcessors());

2、IO密集型:判断程序中十分消耗io的线程,需要大于这个数量(最好设置为线程数量的两倍)

1)corePoolSize:核心线程数

核心线程会一直存活,及时没有任务需要执行

当线程数小于核心线程数时,即使有线程空闲,线程池也会优先创建新线程处理

设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭

2)queueCapacity:任务队列容量(阻塞队列)

当核心线程数达到最大时,新任务会放在队列中排队等待执行

3)maxPoolSize:最大线程数

当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务

当线程数=maxPoolSize,且任务队列已满时,线程池会拒绝处理任务而抛出异常

4)keepAliveTime:线程空闲时间

当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize

如果allowCoreThreadTimeout=true,则会直到线程数量=0

5)allowCoreThreadTimeout:允许核心线程超时

6)rejectedExecutionHandler:任务拒绝处理器

完整解析

四大函数式接口

  //Function 函数型接口 有一个输人参数和一个出
    @Test
    public void T0() {
        /**
         *
         */
//        Function function = new Function() {
//            @Override
//            public Object apply(Object o) {
//                return o;
//            }
//        };

       Function function  = a->{
          return a;
       };
        System.out.println(function.apply(299));//299
    }

    //Predicate 函数式接口 有一个输入参数和一个布尔类型的返回值
    @Test
    public void T1(){

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

    //Consumer 消费型接口,只有输入参数没有返回值。
    @Test
    public  void T2(){
        Consumer<String> consumer = sq->{
            System.out.println(sq);// ap
        };

        consumer.accept("ap");
    }

    //Supplier 供给型接口,只有返回值没有输入参数
    @Test
    public void T3(){
        Supplier<String> supplier =() ->{

          return   "I'm loser";
        };
        System.out.println(supplier.get()); //I'm loser
    }

Stream流式计算

大数据:存储+计算
例如Mysql、集合这些都是用来存储东西的
而计算都应该交给流来做

   public static void main(String[] args) {

        User u1 = new User(1, "a",16);
        User u2 = new User(2, "b",25);
        User u3 = new User(3, "c",67);
        User u4 = new User(4, "d",29);

        //创建集合
        List<User> list = Arrays.asList(u1, u2, u3, u4);
        //转化成流
        list.stream()
                .filter(u -> {
                    return u.getId() % 2 == 0;
                })
                .filter(u->{//filter 有参、返回值为布尔类型
                    return u.getAge()>23;
                })
                .map(u->{//map映射
                    return u.getName().toUpperCase();
                })
                .sorted((us1,us2)->{//sorted 排序
                    return  us2.compareTo(us1);//compareTo方法是比较并排序
                })
                .limit(1)//用于分页 当前使用目的是只输出一个用户名
                .forEach(System.out::println);
    }


/**
 * 定义实体类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
class User {
    private int id;
    private String name;
    private int age;
}

ForkJoin

关键字:并行执行任务、提高效率、提高数据量
本质:把大任务拆分成一个个都小任务
特点:工作窃取(当a、b两个线程同时执行任务 b率先完成任务后会窃取a未完成的任务 帮a执行完成)

代码示例:

//普通方法
    @org.junit.Test
    public void test1() {
        Long sum = 0L;
        Long sta = System.currentTimeMillis();
        for (int i = 1; i <= 10_0000_0000; i++) {
            sum += i;
        }
        Long sto = System.currentTimeMillis();
        System.out.println(sum + "时间:" + (sto - sta));

    }

    //使用ForkJoin
    @org.junit.Test
    public void test2() throws ExecutionException, InterruptedException {
        Long sta = System.currentTimeMillis();
        ForkJoinPool forkJoinPoll = new ForkJoinPool();
        ForkJoinTask<Long> demo = new demo(0L, 10_0000_0000L);
求和结果方法:
			1、
        forkJoinPoll.execute(demo);//执行任务 没有返回值
        System.out.println(demo.get());//获取值
			2ForkJoinTask<Long> submit = forkJoinPoll.submit(demo);//提交任务 有返回值
        Long sum = submit.get();
        Long sto = System.currentTimeMillis();
        System.out.println(sum + "时间:" + (sto - sta));
    }

    //并行流
    @org.junit.Test
    public void test3(){
        Long sta = System.currentTimeMillis();
        //Stream流
        Long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);//parallel并行计算  reduce求和
        Long sto = System.currentTimeMillis();
        System.out.println(sum + "时间:" + (sto - sta));
    }
    _____________________ForkJoin自定义_________________
    public class demo extends RecursiveTask<Long> {
    private Long sta;
    private Long sto;

    private Long len = 10000L;

    public demo(Long sta, Long sto) {
        this.sta = sta;//1
        this.sto = sto;//20000L
    }

    //计算方法
    @Override
    protected Long compute() {

        if ((sto - sta) < len) {
            Long sum = 0L;
            for (Long i = sta; i <= sto; i++) {
                sum += i;
            }
            return sum;
        } else {
            Long mm = (sto + sta) / 2;//获取中间值
            demo demo = new demo(sta, mm);
            demo.fork();//拆分任务把任务压入线程队列
            demo demo1 = new demo(mm + 1, sto);
            demo1.fork();
           return demo.join()+demo1.join();
        }
    }
}

JMM(内存模型)

并不存在相当于一种约定
同步约定:
1、线程解锁前: 必须把共享变量立刻刷回主存
2、线程加锁前: 必须读取主存中的最新值到工作内存中
3、加锁、解锁是同一把锁

在这里插入图片描述

八种操作:

在这里插入图片描述

Volatile

1、保证可见性
2、不保证原子性
3、禁止指令重排

处理器在执行指令重排会考虑数据之间的依赖性问题:

不会造成影响示例:
int x=1;//1
int y=2;//2
x=x+2;//3
y=x*y;//4
我们预期执行是1234,发送指令重排后可能是 2134、1324
但不会出现 4123 这是数据之间的依赖性导致的
造成影响的示例:
abcd四个值默认为0
线程A: || 线程B:
d=b; || c=a;
a=3; || b=1;
预期值:
d=0;c=0
发生指令重排可能会先执行a=3或者b=1
导致结果变成:
d=1;c=3;
这样就会对最后的结果造成影响

Volatile避免指令重排:
内存屏障、CPU指令。作用:
1、保证特定的操作的执行顺序
2、保证某些变量的内存可见性(利用这些特性就可以实现可见性)
在这里插入图片描述

Voliate的内存屏障在单例模式中使用最多!

单例模式

饿汉式

public class Hungry {

    /**
     * 很有可能会浪费空间
     */
    byte [] bytes1 = new byte[1024*1024];
    byte [] bytes2 = new byte[1024*1024];
    byte [] bytes3 = new byte[1024*1024];
    byte [] bytes4 = new byte[1024*1024];

    //私有构造器,别人无法new 保证只有一个对象
    private Hungry(){

    }
  private final static   Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

DCL懒汉式

//懒汉式
public class LanHan {
    private static boolean tmp = false;

    private LanHan() {
        System.out.println(Thread.currentThread().getName());
        synchronized (LanHan.class) {
            if (tmp) {
                tmp = true;
            } else {
                throw new RuntimeException("拒绝!");
            }
        }
    }

    //不加volatile会存在指令重排风险
    private volatile static LanHan LANHAN;

    //双重检测锁模式 懒汉式单利 DCL懒汉式
    private static LanHan getInstance() {
        if (LANHAN == null) {
            synchronized (LanHan.class) {
                if (LANHAN == null) {
                    LANHAN = new LanHan();//不是原子性操作,存在发生指令重排的可能
                    /**
                     * 如果不加volatile:
                     * new的这个过程:
                     * 1、分配内存空间
                     * 2、执行构造方法,初始化对象
                     * 3、把这个对象指向这个空间
                     *
                     *我们预期的执行顺序:123
                     *
                     * 比如现在A线程执行顺序是132
                     * 之后B线程进来之后此时的LANHAN还没有完成构造
                     */
                }
            }
        }
        return LANHAN;
    }
    ________________反射可以破坏懒汉式单例模式____
      public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        //反射会破坏单利模式
        Field tmp = LanHan.class.getDeclaredField("tmp");
        tmp.setAccessible(true);


        Constructor<LanHan> declaredConstructor = LanHan.class.getDeclaredConstructor(null);//空参 构造器
        declaredConstructor.setAccessible(true);//无视私有构造器
        LanHan l1 = declaredConstructor.newInstance();
        tmp.set(l1,false);
        LanHan l2 = declaredConstructor.newInstance();

        System.out.println(l1 + "\n" + l2);
    }
}

反射无法破坏枚举单例模式

public enum  Enumdemo {

    INC;

    private Enumdemo getInc(){
        return INC;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Enumdemo enumdemo = Enumdemo.INC;
        Constructor<Enumdemo> declaredConstructor = Enumdemo.class.getDeclaredConstructor(null);//无参构造器
        declaredConstructor.setAccessible(true);//无视私有构造
        Enumdemo enumdemo1 = declaredConstructor.newInstance();
        System.out.println(enumdemo+"\n"+enumdemo1);
    }

}

深入了解CAS

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);

        //boolean compareAndSet(int expect, int update)
        //期望值、更新值
        //如果实际值 和 我的期望值相同,那么就更新
        //如果实际值 和 我的期望值不同,那么就不更新
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        //因为期望值是2020  实际值却变成了2021  所以会修改失败
        //CAS 是CPU的并发原语
        atomicInteger.getAndIncrement(); //++操作
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());
    }
}

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

缺点:

循环会耗时;
一次性只能保证一个共享变量的原子性;
它会存在ABA问题

CAS的ABA

ABA产生代码示例:
AtomicInteger atomicInteger = new AtomicInteger(2020);
        new Thread(()->{
            System.out.println(atomicInteger.compareAndSet(2020, 10000));
            System.out.println(atomicInteger.get());
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(atomicInteger.compareAndSet(10000, 2020));
                System.out.println(atomicInteger.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(atomicInteger.compareAndSet(2020, 2023));
                System.out.println(atomicInteger.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
ABA解决代码示例:
 public void abajiejue(){

        /**
         * 解决ABA问题 加上版本号 这样可以知道有人修改过数据
         * 注意此时参数都是Integer类型的 这个类的取值范围如果不是-128到127之间,就会是不同的对象!!!
         * 而此时比较的刚好是对象的内存地址!所以不在这个范围内会出现false
         */
        //比AtomicInteger多了个版本号的参数 类似乐观锁
        AtomicStampedReference<Integer> reference = new AtomicStampedReference(10,1);
        new Thread(()->{
            int ver = reference.getStamp();//获取版本号
            System.out.println("版本号ver"+ver);
            //期望值,更新值,当前版本号,版本号加一
            System.out.println(reference.compareAndSet(10,
                                                            100,
                                                                        reference.getStamp(),
                                                                reference.getStamp()+1));
            System.out.println(reference.getStamp());
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
                int ver = reference.getStamp();//获取版本号
                System.out.println("版本号ver:"+ver);
                //期望值,更新值,当前版本号,版本号加一
                System.out.println(reference.compareAndSet(100, 10,reference.getStamp(),reference.getStamp()+1));
                System.out.println(reference.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(4);
                int ver = reference.getStamp();//获取版本号
                System.out.println("版本号ver"+ver);
                //期望值,更新值,当前版本号,版本号加一
                System.out.println(reference.compareAndSet(10, 120,reference.getStamp(),reference.getStamp()+1));
                System.out.println(reference.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    } 

可重入锁

(套娃锁)
重入就是说某个主题已经获得而获得某个锁,可以再次获得死锁,可以多次获得相同的锁
在这里插入图片描述

synchronized示例:

public class SynchronizedDemo {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}
class Phone{
    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"=> sms");
        call();// 这里也有一把锁
    }
    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"=> call");
    }
}

Lock锁:

public class LockDemo {
    public static void main(String[] args) {
        Phone2 phone2  = new Phone2();
        new Thread(()->{
            phone2.sms();
        }).start();
        new Thread(()->{
            phone2.sms();
        }).start();
    }
}
class Phone2{
    Lock lock = new ReentrantLock();
    public void sms(){
        lock.lock();//细节:这个两把锁,两个钥匙
//        lock 锁必须配对,否则就是死锁在里面
        try {
            System.out.println(Thread.currentThread().getName()+"=>sms");
            call();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void call(){
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName()+"=>call");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

自旋锁

代码示例:

(设计自己的自旋锁)

//自旋锁
public class KariLock {

    AtomicReference<Thread> reference = new AtomicReference();

    public void mylock(){
        System.out.println(Thread.currentThread().getName()+"进入锁");
        while (!reference.compareAndSet(null,Thread.currentThread())){
        }
    }
    public void myUnlock(){
        System.out.println("\n"+Thread.currentThread().getName()+"解锁");
        reference.compareAndSet(Thread.currentThread(),null);
    }
}
public class TestLock {
    public static void main(String[] args) {
        KariLock lock = new KariLock();

        new Thread(()->{
            lock.mylock();
            try {
                TimeUnit.SECONDS.sleep(3);//模拟业务
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        }).start();
        new Thread(()->{
            lock.mylock();
            try {
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.myUnlock();
            }
        }).start();
    }
}

结论:线程0拿到锁后休眠,线程1会持续自旋,直到线程0解锁后,线程1才能解锁

死锁排查:

使用 jps 定位进程号,jdk 目录 bin 下 :有一个 jps
在IDea中的Terminal中输入 jsp-l
使用 jstack进程进程号找到死锁信息
jstack 9090

在工作中排查问题除了日志以外还可以查看堆栈信息

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值