JUC、生产者消费者问题和集合不安全问题等等等

2023-05-16

1. 什么是JUC

JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。

2. 并发和并行

  • 并发(Concurrent):多个线程同时操作一个资源。
  • 并行(Parallel):多个线程可以同时执行。
//获取cpu的核数
Runtime.getRuntime().availableProcessors()

3. 线程有五个状态

public enum State(){
	NEW,                // 新生
	RUNNABLE            // 运行
	BLOCKED   		  // 阻塞
	WAITING      	   // 等待
	TIME_WAITING 	   // 超时等待
	TERMINATED          // 中止
}

4. wait和sleep的区别

  1. sleep在Thread类中,wait在Object类中;
  2. sleep不会释放锁,wait会释放锁;
  3. sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知;
  4. sleep可以在任何地方使用,wait方法只能在同步方法或同步块中使用;
  5. sleep必须要捕获异常,wait不需要捕获异常。

5. Lock

  1. lock常用方法
    lock() 获得锁。
    unlock() 释放锁。
    tryLock(long time, TimeUnit unit) 如果在给定的等待时间内是空闲的,并且当前的线程尚未得到 interrupted,则获取该锁。
  2. 实现类
    ReentrantLock(可重入锁)
    ReentrantReadWriteLock.ReadLock(读锁)
    ReentrantReadWriteLock.WriteLock (写锁)
  3. Sychronized和Lock区别
    • 存在层次:Sychronized是Java的关键字,Lock是一个接口;
    • 异常是否释放锁:Sychronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而Lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。
    • 是否响应中断:Lock等待锁过程中可以用interrupt来中断等待,而Synchronized只能等待锁的释放,不能响应中断;
    • 是否知道获取锁:Lock可以通过trylock来知道有没有获取锁,而synchronized不能;
    • 锁类型:Synchronized可重入、不可中断、非公平;Lock可重入、可判断、可公平(默认非公平)
    • 性能:Synchronized适合少量代码同步问题;Lock适合大量代码同步问题;

5. 生产者消费问题 (防止虚假唤醒 用while判断)

  1. Synchronized版本的生产者与消费者问题
    需要注意虚假唤醒即:线程可以唤醒,但不会被通知,中断或者超时。等待应该总是出现在循环中,所以要用while代替if。
  2. Lock锁版本的生产者与消费者的问题
  3. Condition:同步监视器,实现精准的通知和唤醒

6. 八锁现象

1、new this 调用的是这个对象,是一个具体的对象!
2、static class 唯一的一个模板!

7. 集合类不安全

常见异常:java.util.ConcurrentModificationException:并发修改异常

1. List不安全

public class ListTest {
    public static void main(String[] args) {
        //List list = new ArrayList(); 线程不安全
        /**
         * 解决方案:
         * 1. List list = new Vector();
         * 2. List list = Collections.synchronizedList(new ArrayList<>());
         * 3. List list = new CopyOnWriteArrayList();
         */
        List list = new CopyOnWriteArrayList();
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

CopyOnWrite写入时复制:在不同的线程访问同一个资源的时候,只有更新操作,才回去复制一份新的数据并进行更换,否则都是访问同一资源。

2. Set不安全

public class SetTest {
    public static void main(String[] args) {
        //Set<String> set = new HashSet<>();  线程不安全
        /**
         * 解决方法:
         * 1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
         * 2. Set<String> set = new CopyOnWriteArraySet<>();
         */
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            }).start();
        }
    }
}

3. Map不安全

public class MapTest {
    public static void main(String[] args) {
        //Map<String,String> map = new HashMap<String,String>(16, 0.75f);  线程不安全
        /**
         * 解决方法:
         * 1. Map map = Collections.synchronizedMap(new HashMap<String,String>());
         * 2. Map<String,String> map = new ConcurrentHashMap<String,String>();
         */
        Map<String,String> map = new ConcurrentHashMap<String,String>();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

8. Callable

  1. 会有返回值;
  2. 可以抛出异常;
  3. 方法与Runnable不同,使用的是call()方法。
public class CallableTest {
    public static void main(String[] args) {
        MyThread thread = new MyThread();
        FutureTask task = new FutureTask(thread);// 适配类
        new Thread(task, "a").start();
        new Thread(task, "b").start();  // 结果会被缓存,效率高
        try {
            Integer o = (Integer) task.get(); //这个get 方法可能会产生阻塞!把他放到 后 
            System.out.println(o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("call()");
        return 1024;
    }
}

9. JUC中三个辅助类

  1. CountDownLatch(加法计数器):允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。
    CountDownLatch用给定的计数初始化。 await方法阻塞,直到由于countDown()方法的调用而导致当前计数达到零,之后所有等待线程被释放,并且任何后续的await 调用立即返回。
常用方法:
  • await() 导致当前线程等到锁存器计数到零,除非线程是 interrupted 。
  • countDown() 减少锁存器的计数,如果计数达到零,释放所有等待的线程。
  1. Semaphore(信号量):一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。
常用方法:
  • acquire() 从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。
  • release() 释放许可证,将其返回到信号量。
  1. CyclicBarrie(加法计数器):允许一组线程全部等待彼此达到共同屏障点的同步辅助。
常用方法:
  • await() 等待所有parties已经在这个障碍上调用了await。

10. 读写锁ReadWriteLock

ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有作者。 write lock是独家的。

常用方法:
  • readLock() 返回用于阅读的锁;
  • writeLock() 返回用于写入的锁。

11. 阻塞队列 BlockingQueue

Queue另外支持在检索元素时等待队列变为非空的操作,并且在存储元素时等待队列中的空间变得可用。

方式抛出异常有返回值不抛出异常阻塞等待超时等待
添加add(e)offer(e)put(e)offer(e,time,unit)
删除removepolltakepoll(time,unit)
队首元素elementpeek--

12. SynchronousQueue 同步队列

其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。

13. 线程池

  1. 线程池的好处
  • 降低资源的消耗
  • 提高响应的速度
  • 方便管理。
  1. 创建线程池的三大方法
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单一线程
ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池大小
ExecutorService threadPool = Executors.newCachedThreadPool(); //可伸缩的
  1. 7大参数
public ThreadPoolExecutor(int corePoolSize,   //核心线程池大小
                              int maximumPoolSize, //最大核心线程池大小
                              long keepAliveTime, //非核心线程的超时时长,当系统中非核心线程闲置时间超过keepAliveTime之后,则会被回收
                              TimeUnit unit,//第三个参数的单位,
                              BlockingQueue<Runnable> workQueue, //线程池中的任务队列
                              ThreadFactory threadFactory, //为线程池提供创建新线程的功能,这个我们一般使用默认即可
                              RejectedExecutionHandler handler) //拒绝策略 {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
public class ExecutorDemo2 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                2,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

  1. 四种拒绝策略
  • Class ThreadPoolExecutor.AbortPolicy:被拒绝的任务的处理程序,抛出一个 RejectedExecutionException 。
  • Class ThreadPoolExecutor.CallerRunsPolicy:一个被拒绝的任务的处理程序,直接在 execute方法的调用线程中运行被拒绝的任务,除非执行程序已被关闭,否则这个任务被丢弃。
  • Class ThreadPoolExecutor.DiscardOldestPolicy:被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试 execute ,除非执行程序被关闭,在这种情况下,任务被丢弃。
  • Class ThreadPoolExecutor.DiscardPolicy:被拒绝的任务的处理程序静默地丢弃被拒绝的任务。

14. 四大函数式接口

public class Function1 {
    public static void main(String[] args) {
        // 1. Function 函数型接口, 有一个输入参数,有一个输出
        Function<String, String> function = (str) -> {
            return str;
        };
        System.out.println(function.apply("aaaa"));
        // 2. 断定型接口:有一个输入参数,返回值只能是 布尔值!
        Predicate<String> predicate = (str)->{
          return str.isEmpty();
        };
        System.out.println(predicate.test("aaaa"));
        //3. Consumer 消费型接口: 只有输入,没有返回值
        Consumer<String> consumer = (str)->{
            System.out.println("正在消费 "+str);
        };
        consumer.accept("快餐");
        //4. 供给型接口:没有参数只有返回值
        Supplier<String> supplier = ()->{
           return "供给成功";
        };
        System.out.println(supplier.get());
    }
}

15. Stream流式计算

public class StreamTest {
    /**
     * 题目要求:一分钟内完成此题,只能用一行代码实现!
     * * 现在有5个用户!筛选:
     * * 1、ID 必须是偶数
     * * 2、年龄必须大于23岁
     * * 3、用户名转为大写字母
     * * 4、用户名字母倒着排序
     * * 5、只输出一个用户!
     */

    public static void main(String[] args) {
        User u1 = new User(1, "a", 21);
        User u2 = new User(2, "b", 22);
        User u3 = new User(3, "c", 23);
        User u4 = new User(4, "d", 24);
        User u5 = new User(6, "e", 25);
        List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
        list.stream()
                .filter(u -> {
                    return u.getId() % 2 == 0;
                })
                .filter(u -> {
                    return u.getAge() > 23;
                })
                .map(u -> {
                    return u.getName().toUpperCase();
                })
                .sorted((uu1, uu2) -> {
                    return uu2.compareTo(uu1);
                })
                .limit(1)
                .forEach(System.out::println);
    }
}

16. ForkJoin

在这里插入图片描述

  1. Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。
  2. ForkJoin的子类
    CountedCompleter
    RecursiveAction:递归事件没有返回值;
    RecursiveTask:递归任务有返回值。
  3. 常用方法
  • fork() 在当前任务正在运行的池中异步执行此任务(如果适用),或使用 ForkJoinPool.commonPool()(如果不是 inForkJoinPool())进行异步执行 。
  • isDone() 返回 true如果任务已完成。
  • join() 当 is done返回计算结果。

import java.util.concurrent.RecursiveTask;

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 {
            Long mid = (end + start) / 2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, mid);
            task1.fork();
            ForkJoinDemo task2 = new ForkJoinDemo(mid + 1, end);
            task2.fork();
            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;

public class ForkJoin1 {

    public static void main(String[] args) {
        sum1();
        sum2();
        sum3();
        sum4(0L, 1000_000_000L);
        sum5plus(1000_000_000L);
    }

    //for循环
    public static void sum1() {
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (int i = 0; i <= 1000_000_000L; i++) {
            sum += i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 时间 " + (end - start));
    }

    //ForkJoin
    public static void sum2() {
        long start = System.currentTimeMillis();
        ForkJoinDemo forkJoinDemo = new ForkJoinDemo(0L, 1000_000_000L);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo); // 提交任务
        Long sum = 0L;
        try {
            sum = submit.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 时间 " + (end - start));
    }

    // Stream并行流 ()  (]
    public static void sum3() {
        long start = System.currentTimeMillis();
        long sum = LongStream.rangeClosed(0L, 1000_000_000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 时间 " + (end - start));
    }

    //递归方法一
    public static void sum4(Long start, Long end) {
        long startTime = System.currentTimeMillis();
        Long temp = 250_000_000L;
        Long sum = 0L;
        if (end - start >= temp) {
            for (Long i = start; i <= end; i++) {
                sum += i;
            }
        } else {
            Long mid = (start + end) / 2;
            sum4(start, mid);
            sum4(mid + 1, end);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 时间 " + (endTime - startTime));
    }
    //递归方法二  数值太大会产生 java.lang.StackOverflowError
    public static void sum5plus(Long n) {
        long startTime = System.currentTimeMillis();
        Long sum = sum5(n);
        long endTime = System.currentTimeMillis();
        System.out.println("sum = " + sum + " 时间 " + (endTime - startTime));
    }

    public static Long sum5(Long n) {
        if (n == 1L) {
            return 1L;
        } else return n + sum5(n - 1);
    }
}

16. 异步回调

  1. 用Future可以明确地完成(设定其值和状态),并且可以被用作CompletionStage ,支持有关的功能和它的完成时触发动作。
  2. 常用方法:
  • runAsync(Runnable runnable) :返回一个新的CompletableFuture,它在运行给定操作后由运行在 ForkJoinPool.commonPool()中的任务 异步完成。
  • get() :等待这个未来完成的必要,然后返回结果。
  • whenComplete(BiConsumer<? super T,? super Throwable> action) :
    返回与此阶段相同的结果或异常的新的CompletionStage,当此阶段完成时,使用结果(或 null如果没有))和此阶段的异常(或 null如果没有))执行给定的操作。
  • exceptionally(Function<Throwable,? extends T> fn):
    返回一个新的CompletableFuture,当CompletableFuture完成时完成,结果是异常触发此CompletableFuture的完成特殊功能的给定功能; 否则,如果此CompletableFuture正常完成,则返回的CompletableFuture也会以相同的值正常完成。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;


public class CompletableFuture1 {
    public static void main(String[] args) {
        // 没有返回值的 runAsync 异步回调
        /*CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"=====> runAsync");
        });
        System.out.println("异步处理");
        try {
            completableFuture.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }*/
        // 有返回值的 supplyAsync 异步回调
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"=====> supplyAsync");
            int i = 1/0;
            return 1024;
        });
        completableFuture.whenComplete((t,u)->{
            System.out.println(t);// 正常的返回结果
            System.out.println(u); // 错误信息:
        }).exceptionally(e->{
            System.out.println(e.getMessage());
            return 2333; // 可以获取到错误的返回结果
        });
        try {
            System.out.println(completableFuture.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

17.Volatile

  1. 三大特性:1) 可见性 2) 不保证原子性 3) 禁止重排序
  2. 保证可见性
import java.util.concurrent.TimeUnit;

/**
 * @author bro
 * @date 2020/10/3 11:29
 * @desc
 */
public class Volatile1 {
    private volatile static int num = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            while (num == 0) {

            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

  1. 不保证原子性
public class Volatile2 {
    private volatile static int num = 0;

    public static void add() {
        /**
         * 不是原子性操作
         * javap -c xxx.class
         *
         * public static void add();
         *     Code:
         *        0: getstatic     #2                  // Field num:I
         *        3: iconst_1
         *        4: iadd
         *        5: putstatic     #2                  // Field num:I
         */
        num++;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount() > 2) Thread.yield();
        System.out.println(num);
    }

}

可以使用java.util.concurrent.atomic包下的原子类,解决原子性问题。

package com.peng;

import java.util.concurrent.atomic.AtomicInteger;
public class Volatile3 {
    private static AtomicInteger num = new AtomicInteger();

    public static void add() {
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2) Thread.yield();

        System.out.println(num);
    }
}

  1. 指令重排
  • 内存屏障(Mermory Barrier)又称内存栅栏,是一个CPU指令,它的作用有两个:
  1. 保证特定操作的顺序保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)由于编译器和处理器都能执行指令重排的优化,如果在指令键插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说,通过插入内存屏障前后的指令执行重排序优化。
  2. 内存屏障另外一个作用是刷新出各种CPU的缓存数,因此任何cpu上的线程都能读取到这些数据的最新版本
    也就是在Volatile的写和读的时候,加入屏障,防止出现指令重排

18. JMM(java memory model)

在这里插入图片描述

  1. Java的并发采用的是共享内存模型
  2. 作用:缓存一致性协议,用于定义数据读写的规则。
  3. JMM对这八种指令的使用,制定了如下规则:
    (1)不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write;
    (2)不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存;
    (3)不允许一个线程将没有assign的数据从工作内存同步回主内存;
    (4)一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作;
    (5)一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁;
    (6)如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值;
    (7)如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量;
    (8)对一个变量进行unlock操作之前,必须把此变量同步回主内存。

19.单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意

  • 单例类只能有一个实例。
  • 单例类必须自己创建自己的唯一实例。
  • 单例类必须给所有其他对象提供这一实例。

优点:

1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

单例的几种实现方式

  1. 懒汉式,线程不安全
public class LazyMan {
    private LazyMan(){

    }

    public static LazyMan lazyMan;

    public static LazyMan getInstance(){
        if (lazyMan == null){
            lazyMan = new LazyMan();
        }
        return lazyMan;
    }
}
  1. 饿汉式
//饿汉式
public class Hungry {
    private Hungry(){

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

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

  1. 双检锁/双重校验锁(DCL,即 double-checked locking)
//双检锁/双重校验锁
public class Singleton {
    private Singleton(){

    }

    public volatile static Singleton singleton;

    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static Singleton getInstance(){
        if (singleton == null){
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

  1. 枚举
public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

20. CAS

CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2020);
        // 期望、更新
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!
        // ============== 捣乱的线程 ==================
         System.out.println(atomicInteger.compareAndSet(2020, 2021));
         System.out.println(atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
        // ============== 期望的线程 ==================
         System.out.println(atomicInteger.compareAndSet(2020, 6666));
         System.out.println(atomicInteger.get());
    }
}

CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。

  1. 循环时间长开销很大。
  2. 只能保证一个变量的原子操作。
  3. ABA问题。

21. 原子引用

解决ABA 问题,引入原子引用! 对应的思想:乐观锁!

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo2 {
    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1 ==> " + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(1, 2, stamp, stamp + 1);
            System.out.println("a2 ==> " + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println("a3 ==> " + atomicStampedReference.getStamp());
        }).start();
    }
}

22. 索的理解

  1. 公平锁、非公平

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
  1. 可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

  1. 自旋锁
    自旋锁的定义:当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)
import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {
    AtomicReference atomicReference = new AtomicReference();

    public void lock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "===> lock");
        while (!atomicReference.compareAndSet(null, thread)) {
            
        }
    }

    public void unlock() {
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"=====> unlock");
        atomicReference.compareAndSet(thread,null);
    }
}

import java.util.concurrent.TimeUnit;

public class SpinLockTest {
    public static void main(String[] args) {
        SpinLockDemo spinLock = new SpinLockDemo();
        new Thread(()->{
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.unlock();
            }
        },"T1").start();
        new Thread(()->{
            spinLock.lock();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                spinLock.unlock();
            }
        },"T2").start();
    }
}

  1. 死锁

所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

死锁产生的4个必要条件?

  • 互斥条件:一个资源每次只能被一个进程使用;
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
  • 不剥夺条件: 进程已获得资源,未在用完之前不能强行剥夺;
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源的关系。

排除死锁:

  1. 使用 jps -l 定位进程号
  2. 使用 jstack 进程号 找到死锁问题
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JUC、生产者消费者问题和集合不安全问题等等等 的相关文章

随机推荐