并发编程-Day2

并发编程

1.共享模型-内存

共享变量在多线程间的<可见性>问题与多条指令执行时的<有序性>问题

1.1Java内存模型

JMM它定义了主存、工作内存抽象概念,底层对应着CPU寄存器、缓存、硬件内存CPU指令优化等.

JMM体现在:

  • 原子性-保证指令不会受到线程上下文切换的影响
  • 可见性-保证指令不会受cpu缓存的影响
  • 有序性-保证指令不会受cpu指令并行优化的影响

1.2可见性

问题:通过变量控制while程序不能够停下来

@Slf4j
public class KeJianXingThreadD2 {

    static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (flag) {
            }
        });
        thread.start();

        Thread.sleep(3);
        log.debug("停止thread");
        flag = false;
    }
}

分析问题:

  • thread从内存读取了flag的值到工作内存中
  • thread线程频繁从主内存中读取flag的值,JIT编译器会将flag的值缓存到自己的工作内存中,减少对主内存中flaga的访问,提高效率
  • 3秒之后,main线程修改了flag的值,并同步到主内存中,而thread线程一直在读取自己的工作内存的值,一直是旧值,所以线程不会停下来

解决方案:

volatile(关键字)

可以修饰成员变量和静态成员变量,可以避免线程从自己的工作缓存中查找变量,必须到主内存获取它的值,线程操作volatile变量都是直接操作主

public class KeJianXingThreadD2 {

    volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (flag) {
            }
        });
        thread.start();

        Thread.sleep(3);
        log.debug("停止thread");
        flag = false;
    }
}

只能保证可见性 不能保证原子性

1.3有序性

程序执行的顺序按照代码的先后执顺序执行.

编译器和处理器为了提高执行的效率性能,会对执行进行重排序,指令重排序对单线程应用没有影响,但是在多线程环境下可能出现问题.<重排序的代码没有执行完>

解决:volatile关键字解决

1.4volatile原理

  • 读屏障
  • 写屏障

2.共享模型-无锁

  • CAS与volatile
  • 原子整数
  • 原子引用
  • 原子累加器
  • Unsafe

2.1CAS与volatile

/**
 * @program: springboot-demo-liuan
 * @description:
 * @author: Mr.Lh
 * @create: 2023-03-18 21:29
 **/
public class CasController {

    public static void main(String[] args) throws InterruptedException {
        Acout acout = new Acout(10000);
        acout.demo(acout);
    }
}

class Acout {

    public AtomicInteger balance;

    public Acout(int count) {
        this.balance = new AtomicInteger(count);
    }

    public void with(Integer amout) {
        while (true) {
            int i = balance.get();
            int b = i - amout;
            if (balance.compareAndSet(i, b)) {
                break;
            }
        }
    }

     void demo(Acout acout) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                acout.with(10);
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(acout.balance.get());
    }
}

关键点是compareAndSet,它的简称是CAS,它必须是原子性的操作

CAS特点

CAS和volatile可以实现无锁并发,适用于线程数小,多核CPU的场景下

  • CAS是基于乐观锁的思想
  • synchronized是基于悲观锁的思想
  • CAS体现的是无锁并发,无阻塞并发

CAS缺点

  • ABA问题:A值变成了B,然后又从B值变回了A,而使用CAS并不会感知到这个情况
  • 自旋时间过长:单次CAS并一定能够成功,CAS配合循环来使用

原子整数

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong

原子引用

  • AtomicReference(会有ABA的问题)
  • AtomicStampedReference(版本号解决ABA的问题)

3.线程池

1.线程池状态

ThreadPoolExecutor 使用 int 的高 3 位来表示线程池状态,低 29 位表示线程数量

状态名高 3 位接收新任务处理阻塞队列任务说明
RUNNING111YY
SHUTDOWN000NY不会接收新任务,但会处理阻塞队列剩余 任务
STOP001NN会中断正在执行的任务,并抛弃阻塞队列 任务
TIDYING010--任务全执行完毕,活动线程为 0 即将进入 终结
TERMINATED011--终结状态
2.构造方法
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 
  • corePoolSize 核心线程数
  • maximumPoolSize 最大线程数
  • keepAliveTime 生存时间
  • unit 生存时间单位
  • workQueue 当核心线程数满时,存放的任务队列
  • threadFactory 创建线程的工厂
  • handler 拒绝策略
四种策略
  • CallerRunsPolicy 让调用者运行任务
  • DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
  • AbortPolicy 让调用者抛出异常 默认策略
  • DiscardPolicy 放弃本次任务
工厂创建线程
1.newFixedThreadPool

特点:

  • 核心线程数==最大线程数,无需超时时间
  • 阻塞队列是无界的,可以任意数量的任务
2.newCachedThreadPool

特点:

  • 核心线程数是0,最大线程数无限制,60s回收救急线程,救济线程可以无限的创建
  • 队列采用的SynchronousQueue 没有容量,没有线程来取是放不进去的
  • 没有上限的一个线程池
3.newSingleThreadExecutor

特点

  • 线程数固定为1,会放入无界队列中,任务执行完毕,这唯一的线程也不会被释放
任务调度线程池
//延时执行任务
public class TimeExcetor {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        scheduledExecutorService.schedule(() -> {
            System.out.println("aaaa");
        }, 2, TimeUnit.MILLISECONDS);

        scheduledExecutorService.schedule(() -> {
            System.out.println("bbbbbb");
        }, 1, TimeUnit.MILLISECONDS);
    }
}


//定时执行任务
@Slf4j
public class TimeExcetor {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        log.debug("start...");
        pool.scheduleAtFixedRate(() -> {
            log.debug("running...");
        }, 1, 1, TimeUnit.SECONDS);

    }
}
正确处理执行任务异常
  • 主动捕获异常

    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
        scheduledExecutorService.execute(() -> {
            try {
                int i = 10 / 0;
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            System.out.println("aaa");
        });
    }
    
  • Future

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        Future<Boolean> submit = executorService.submit(() -> {
            int i = 10 / 0;
            return true;
        });
        //如果任务执行成功则获取值,如果任务失败了 则会打印出异常信息
        System.out.println(submit.get());
    }
    

4.JUC

4.1 AQS原理

AbstractQueuedSynchronizer,是一个用于实现锁和同步器的基础框架

特点:

  • state state 表示同步状态(分独占锁和共享锁) 使用compareAndSetState机制实现加锁
  • Node表示队列元素,每个 Node 对象内都保存着当前线程的引用,这些 Node 构成了一个双向队列
4.2ReentrantReadWriteLock

ReentrantReadWriteLock 同样基于 AQS 实现了一套高效的同步机制。它维护着两个锁,一个读取锁和一个写入锁。读取锁允许多个线程同时共享读操作,而写入锁则只允许一个线程进行写入操作。如果有一个线程已经获取了写入锁,那么其他所有线程都无法获得读取或写入锁,它们只能等待写入锁被释放后才能进行读取或写入操作。

4.3Semaphore

是一种在并发编程中经常使用的同步原语。它可以用来管理一组共享的资源,防止共享资源的并发使用。Semaphore包含一个计数器和两个方法:acquire和release

@Slf4j
public class SemaphoreDemo1 {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(2);
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();
                    log.info("获取信号量");
                    Thread.sleep(10002);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    semaphore.release();
                }
            }).start();
        }
    }
}
4.4CountDownLatch

是一个同步工具,它可以让一个线程等待一组操作的完成。CountDownLatch包含一个计数器和两个方法:countDown和await

countDown方法将计数器的值减一,await方法阻塞线程,直到计数器值为零。每次调用countDown方法都会减少计数器的值,当计数器值为零时,await方法会解除所有等待线程的阻塞状态

@Slf4j
public class CountDownLatchDemo1 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(()->{
            try {
                Thread.sleep(1000);
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        new Thread(()->{
            try {
                Thread.sleep(2222);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            countDownLatch.countDown();
        }).start();
        new Thread(()->{
            try {
                Thread.sleep(3333);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            countDownLatch.countDown();
        }).start();
        //主线程阻塞
        countDownLatch.await();
    }
}
4.5CyclicBarrier

循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置『计数个数』,每个线程执 行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cb = new CyclicBarrier(2); // 个数为2时才会继续执行
        new Thread(()->{
            System.out.println("线程1开始.."+new Date());
            try {
                cb.await(); // 当个数不足时,等待
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程1继续向下运行..."+new Date());
        }).start();
        new Thread(()->{
            System.out.println("线程2开始.."+new Date());
            try { Thread.sleep(2000); } catch (InterruptedException e) { }
            try {
                cb.await(); // 2 秒后,线程个数够2,继续运行
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
            System.out.println("线程2继续向下运行..."+new Date());
        }).start();
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值