Java并发

这篇博客详细介绍了Java并发编程的相关知识,包括进程与线程的区别、并发编程的关键问题、通信机制、Java内存模型(JMM)、并发控制手段如synchronized、CAS,以及JUC组件如CountDownLatch、Semaphore和线程池等。同时,文中还讨论了线程的启动方式、状态以及常用的操作方法。

并发

1.进程和线程的区别
①进程是资源分配的最小单位,线程是CPU调度的最小单位。
②一个进程可以有多个线程,也就是进程是线程的容器,每个进程都至少有一个线程。
2.并发编程模型中的两个关键问题
①线程之间如何通信
②线程之间如何同步
3.通信的两种机制
①共享内存
②消息传递
4.JMM(java内存模型)

  • Java的并发采用的是共享内存模型
  • 主内存和工作内存
    • 处理器上的寄存器的读写比内存快几个数量级,为了解决这种,加入了高速缓存
      • 带来的问题?
        • 缓存一致性问题
  • JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,它试图屏蔽各种操作系统的内存访问差异,以实现java程序在各种平台下都能达到一致的内存访问效果
  • 8总内存间交互操作
    • read 把一个变量的值从主内存传输到工作内存
    • load 在read之后执行,把read得到的值放入工作内存的变量副本中
    • use 把工作内存中一个变量的值传递给执行引擎。
    • assign 作用于工作内存的变量,它把一个从执行引擎收到的值赋值给工作内存的变量
    • store 作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作
    • write 作用于主内存的变量,它把store操作从工作内存中的一个变量的值传送到主内存的变量中
    • lock 作用于主内存的变量,把一个变量标识为一条线程独占状态
    • unlock 作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • happens-before
    • 从JDK5开始,java使用happens-before概念来阐述操作间的内存可见性。
    • 在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系 。
    • 两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前。
    • 规则
      • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作。
      • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
      • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读。
      • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
      • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作。
      • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
      • 线程中断规则:对线程interrupt方法的调用happens-before于被中断线程的代码检测到中断事件的发生。
  • java内存模型中的重排序
    • 为什么需要指令重排序?
      • 因为指令重排序能够提高性能
    • 指令重排序的种类
      • 编译器优化的重排序
      • 指令级并行的重排序
      • 内存系统的重排序
    • 规则
      • as-if-serial 不管如何重排序,都必须保证代码在单线程下的运行正确
    • 解决方案
      • 使用锁同步
        • 在临界区内的代码可以重排序,但是由于互斥特性,其他线程感受不到重排序带来的影响
      • 基于happens-Before规则编程
    • 可以使用内存屏障的方法禁止重排序,我们遵守happens-Before规则底层就是使用了内存屏障

5.实现线程的方式
①继承Thread类重写run方法
缺点:java中类只能支持单继承。
②实现Runnable接口
缺点:不能获取返回值,还不能抛出异常
③实现Callable接口以及call方法,通过FutureTask包装器来创建Thread线程
特点:可以获取线程的返回值,可以抛出异常,并且get方法在获取返回值的时候会被阻塞,而且结果还会被缓存
##对于Thread来说只能执行实现Runnable接口的类,所有实现Callable并不能直接执行,所有需要借助一个中间类FutureTask类来运行

6.线程的启动方式
①线程的启动方式只有一种,将实现接口或者被继承的类放入Thread类中通过start启动
②java的线程启动是依赖C或C++实现的,java本身不能够开启线程,java的start方法底层依赖start0方法启动,而start0方法是一个native方法
7.Java程序运行的时候至少有两个线程
①主线程
②GC线程
8.Java中线程的6个状态
①NEW 新生
②RUNNABLE 运行
③BLOCKED 阻塞
④WAITING 等待
⑤TIMED_WAITING 超时等待
⑥TERMINATED 终止
在java.lang.Thread类里面有个State的枚举类
9.线程操作的常用方法

  • new Thread().setDaemon(boolean on)
    • 设置为守护线程
    • 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
    • 必须在开启线程前使用setDaemon方法
  • Thread.sleep(long millis)
    • 休眠当前线程,单位为毫秒
    • sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回
    • sleep不会释放锁
    • 建议使用TimeUnit类代替sleep,因为TimeUnit能够更精细化控制
    • 如果线程睡眠时间到了,该线程也不会立即执行,只是从睡眠状态变成了可运行状态
  • new Thread().setPriority(int newPriority)
    • 设置线程的优先级
    • 优先级分为1~10,数值越大优先级越高,CPU优先执行优先级高的任务
    • 优先级高的任务并不一定先执行
  • Thread.yield()
    • 线程让步,当前线程会释放CPU的执行权,转入就绪状态。
    • yield不会释放锁
  • new Thread().join(long millis, int nanos)
    • 阻塞调用此方法的线程进入 TIMED_WAITING 状态,直到目标线程完成,此线程再继续;
  • inInterrsupted和interrupted区别
    • isInterrupted是非静态的
    • interrupted是静态的,而且本质上是调用了当前线程中的isInterrupted 方法,不过传入了一个参数true
    • interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
    • isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态
  • new Object()
    • wait
      • 让线程进入等待
      • 会释放锁,但是不会释放所有的锁,只会释放当前对象的锁
    • notify和notifyAll
      • notifyAll唤醒所有的线程
      • notify随机唤醒一个线程,如果被唤醒的线程之前放弃的锁被其他对象所有,那么这个线程会进入阻塞状态,他必须等待其他线程释放这个锁,才能开始执行。
   /**
 * 首先让线程0拿到object这个锁,然后让线程0进入阻塞状态.
 * 之后主线程唤醒线程0,然后但是不释放锁,发现这个时候线程0虽然被唤醒,但是它之前放弃的锁被主线程使用,所以他会进入阻塞状态,等待其他线程释放锁
 */
public class Thread004 {
    static Object object=new Object();
    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            synchronized (object){
                try {
                    System.out.println(Thread.currentThread().getName()+"获取到锁并且准备进入wait");
                    object.wait();
                    System.out.println(Thread.currentThread().getName()+"被唤醒");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        System.out.println("thread1运行中吗"+thread.getState());
        TimeUnit.SECONDS.sleep(1);
        System.out.println("thread1等待中吗"+thread.getState());
        synchronized (object){
            object.notify();
            System.out.println("唤醒了线程"+thread.getName());
            System.out.println("thread0被唤醒了吗"+thread.getState());
            System.out.println(Thread.currentThread().getName()+"准备获取到锁并且进入同步代码块");
            System.out.println(Thread.currentThread().getName()+"获取到锁");
            TimeUnit.SECONDS.sleep(10);
        }
    }
}
  • 使用notifyAll和wait实现一个生产者和消费者模型
    public class Thread001 {
     private static int resourceNum=0;//资源数
     private static int resourceMax=10;//最大生产数
     public static void main(String[] args) {
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    con();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者1").start();

        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    con();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"生产者2").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    pro();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者1").start();
        new Thread(()->{
            try {
                for (int i = 0; i < 10; i++) {
                    pro();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"消费者2").start();
    }

    /**
     * 生产者
     * @throws InterruptedException
     */
    public static synchronized void con() throws InterruptedException {
        while (resourceNum>=resourceMax){
            Thread001.class.wait();
        }
        System.out.println(Thread.currentThread().getName()+"生产了一件,剩余"+(++resourceNum));
        Thread001.class.notifyAll();
    }

    /**
     * 消费者
     * @throws InterruptedException
     */
    public static synchronized void pro() throws InterruptedException {
        while (resourceNum<=0){
            Thread001.class.wait();
        }
        System.out.println(Thread.currentThread().getName()+"消费了一件,剩余"+(--resourceNum));
        Thread001.class.notifyAll();
    }
}

10.synchronized

  • 锁升级过程
    • 无锁
      • 偏向锁
        • 轻量级锁
          • 重量级锁
  • 特性
    • 可重入性
      • 原理
        • 每个synchronized都和一个monitor关联,当获得锁的时候monitor中的成员变量recursions就+1
        • 可以避免死锁,如果没有可重入特性,那么线程第二次获得该锁的时候就会死锁
      • 不可中断特性
      • 异常会释放锁
  • 锁的对象
    • 在静态方法上synchronized锁的是当前类的class对象
    • 在普通方法上synchronized锁的是当前对象
    • 传入对象的时候synchronized锁的是传入对象

11.CAS(乐观锁)

  • 简介
    • Compare And Swap(比较相同再交换)。是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。
  • 作用
    • CAS可以将比较和交换转换为原子操作,这个原子操作直接由CPU保证。CAS可以保证共享变量赋值时的原子操作。CAS操作依赖3个值:内存中的值V,旧的预估值X,要修改的新值B,如果旧
      的预估值X等于内存中的值V,就将新的值B保存到内存中。
  • 使用CAS和阻塞队列实现一个锁
 /**
 * @author lixintong
 * @date 2021/9/9 下午 2:25
 */
public class CASDemo {
    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        new Thread(()->{
            myLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"获取到锁");
                TimeUnit.SECONDS.sleep(1);
                myLock.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"释放锁");
        }).start();
        new Thread(()->{
            myLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"获取到锁");
                TimeUnit.SECONDS.sleep(1);
                myLock.unlock();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"释放锁");
        }).start();
    }
}

/**
 * 使用CAS自定义一个锁
 */
class MyLock{
    private AtomicInteger state=new AtomicInteger(1);
    LinkedBlockingQueue<Thread> threads=new LinkedBlockingQueue<Thread>();//存放被park的线程,相当于自定义的AQS
    public void lock(){
        while (!state.compareAndSet(1, 0)){
            threads.add(Thread.currentThread());
            LockSupport.park();
        };
    }
    public void unlock() throws InterruptedException {
        while (!state.compareAndSet(0, 1)){};
        if(threads.size()!=0){
            Thread take = threads.take();
            LockSupport.unpark(take);
        }
    }
}

12.JUC

  • 什么是JUC
    • JUC是java.util.concurrent工具包的简称,他是并发大师Doug Lea的杰作
  • AQS(AbstractQueuedSynchronizer)队列同步器
    • 是JUC的核心
    • 原理
      • 围绕着一个同步队列和park还有自旋锁实现
    • 获取公平锁和非公平锁的区别
      • 公平锁线程在唤醒的时候不能插队,非公平锁可以插队
    • 详解(以公平锁为例)
      • lock加锁
        • lock方法真正调用的是acquire(1方法)
          • acquire方法
            public final void acquire(int arg) {
            // 尝试获取同步器tryAcquire false–> 入队addWaiter --> park阻塞该线程acquireQueued
            if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
            }
            • tryAcquire
              tryAcquire继承AQS,是获取锁的具体逻辑,如果锁的状态为0,那么获取成功返回true,如果锁的状态不为0,但是当独占的线程是本身,那么计数器加一,实现可重入锁,返回true,如果获取锁失败则返回false
              • 返回true
                • 加锁成功,不在执行后续方法
              • 返回false(说明当前锁对象正在被其他线程所持有)
                • 调用addWaiter
                  将当前请求锁的线程包装成Node并且放到等待队列中,并返回该Node。
                  在该方法中有一个enq方法,他循环体里面的代码因为不是线程安全的,所以会导致尾分叉问题,但是由于每次tail只会执行一个节点,那些尾分叉节点肯定会CAS失败,所以enq会多次自旋等待所有的节点都全部入队成功

                  • 调用AcquireQueued
                    拿到上一个方法返回的Node节点,如果该节点是的上一个节点是头节点,那么他不断自旋获取锁,如果不是则先自旋两次然后调用park方法阻塞当前线程。
                    上述的两个操作是一直被循环执行的,也就是说同步队列只有第一个节点才会尝试自旋,这个第一个节点指的是第一个非空节点。
      • unlock解锁
        • 真正调用的是sync.release方法
          • 该方法会唤醒第一个排队线程,并且把排队线程的上一个节点的ownerThread置为null,然后让head指向该节点
  • ReentrantLock
    • 可重入可中断锁,他是Lock最重要的实现类之一
    • FairSync()这个方法是公平锁,调用NonfairSync()方法是非公平锁
  • CountDownLatch(减法计数器)
    • 用法
      • 创建该对象的时候可以传入一个数值,每次调用countDown就会减一,计数器为零的时候await才不会阻塞
     public class Thread11 {
    // 计数器
    public static void main(String[] args) throws InterruptedException {
            // 总数是6,必须要执行任务的时候,再使用!
            CountDownLatch countDownLatch = new CountDownLatch(6);
            for (int i = 1; i <=6 ; i++) {
                new Thread(()->{
                    System.out.println(Thread.currentThread().getName()+" Go out");
                    countDownLatch.countDown(); // 数量-1
                },String.valueOf(i)).start();
            }
            countDownLatch.await(); // 等待计数器归零,然后再向下执行
            System.out.println("Close Door");
        }
}
  • CyclicBarrier(加法计数器)
    • 用法
      • 创建该对象的时候需要设置一个值,并且可以写一个实现Runnable的类,每次调用await就会加1,当加到设定的值后就会触发实现Runnable类的线程开启
    public class Thread11 {
    // 计数器
    public static void main(String[] args) throws InterruptedException {
        /**
         * 集齐7颗龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功!");
        });
        for (int i = 1; i <= 7; i++) {
            final int temp = i;// lambda能不能操作到i,需要借助final来控制

            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}
  • Semaphore(信号量)
    • 用法
      • 可以用来做多线程限流,创建该对象的时候可以传入一个资源数量,每次调用acquire就会减少一个资源,资源为零的时候在调用该方法会导致他的线程进入阻塞状态,除非调用release释放锁。
    public class Thread11 {
    // 计数器
    public static void main(String[] args) throws InterruptedException {
        // 线程数量:停车位! 限流!
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                // acquire() 得到
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢到车 位");
                            TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开车 位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // release() 释放
                }
            },String.valueOf(i)).start();
        }
    }
}
  • 读写锁ReentrantReadWriteLock
    • 读锁和写锁互斥
    • 写锁与写锁互斥
    • 读锁和读锁可以共存
    • 演示
/**
 * @author lixintong
 * @date 2021/9/9 下午 3:35
 *
 * 独占锁(写锁) 一次只能被一个线程占有
 * 共享锁(读锁) 多个线程可以同时占有
 * ReadWriteLock
 * 读-读 可以共存!
 * 读-写 不能共存!
 * 写-写 不能共存!
 *
 */
public class Thread11 {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }
        // 读取
        for (int i = 1; i <= 5 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}
// 加锁的
class MyCacheLock{
    private volatile Map<String,Object> map = new HashMap<>();
    // 读写锁: 更加细粒度的控制
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock lock = new ReentrantLock();
    // 存,写入的时候,只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    // 取,读,所有人都可以读!
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
/**
* 自定义缓存
*/
class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    // 存,写
    public void put(String key,Object value){
        System.out.println(Thread.currentThread().getName()+"写入"+key);
        map.put(key,value);
        System.out.println(Thread.currentThread().getName()+"写入OK");
    }
    // 取,读
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
} 
  • 阻塞队列
    • 详情参考容器中的BlockingQueue
    • SynchronousQueue
      • 容量为一的同步队列
  • 线程池
    • 阿里巴巴开发规范中不允许使用Executors去创建线程池,而是推荐使用ThreadPoolExecutor的方式创建
    • 三大方法
      • Executors.newSingleThreadExecutor();// 单个线程
      • Executors.newFixedThreadPool(5); // 创建一个固定的线程池的大小
      • Executors.newCachedThreadPool(); // 可伸缩
    • 七大参数
      • int corePoolSize, // 核心线程池大小
      • int maximumPoolSize, // 最大核心线程池大小
      • long keepAliveTime, // 超时了没有人调用就会释放
      • TimeUnit unit, // 超时单位
      • BlockingQueue workQueue, // 阻塞队列
      • ThreadFactory threadFactory, // 线程工厂:创建线程的,一般不用动
      • RejectedExecutionHandler handle // 拒绝策略
    • 4种拒绝策略
      • 假设现在有一个银行,银行的窗口就是线程,进来办理业务的人就是task
      • new ThreadPoolExecutor.AbortPolicy() // 满了,还有人进来,不处理这个人的,抛出异常
      • new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
      • new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
      • new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会抛出常!
  • ForkJoin
  • Future

如果本文对你有很大帮助,请点赞收藏一下,谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值