Java并发多线程

1. 创建线程的三种方法:Runnable、Callable、Thread

  1. 实现Runnable接口:重写run方法,创建实现类实例,创建Thread对象,调用start()启动线程
  2. 实现Callable接口:重写call方法,设置返回值类型,创建实现类实例,创建FutureTask实例,创建Thread对象,调用start()启动线程,调用get获取执行结果
  3. 继承Thread类:重写run方法,创建Thread对象,调用start()启动线程
  4. 实现接口好,接口可以多继承

2. 线程生命周期:新建、可运行、运行、就绪、阻塞、等待、超时等待、终止

  1. new新建
  2. runnable可运行:调用start()后等待CPU调度
  3. block阻塞:进入synchronized同步块,线程获取锁之后回到ready就绪,ready就绪获得CPU调度后回到running状态
  4. waiting等待:调用wait(),释放锁,执行notify()会唤醒等待线程,回到ready就绪
  5. time_waiting超时等待:调用wait(time)或sleep(time),等待超时后回到ready就绪
  6. terminated终止

3. 守护线程

  1. 只要还有一个非守护线程在运行,守护线程就要全部工作,当非守护线程全部结束后,守护线程随着JVM进程的停止而停止。

4. wait、notify、sleep

  1. wait和notify是Object方法,调用wait会使线程进入waiting状态,会释放锁,执行notfiy时会唤醒等待的线程
  2. 在同一把锁上等待的线程才能被唤醒,Object类是所有类的父类,notify一定能唤醒等待的线程
  3. sleep是Thread方法,wait是Object方法,sleep()不会释放锁,等待时间超时后继续执行

4. 手写死锁

public class Main{
    //两个Object资源
    public static Object a = new Object();
    public static Object b = new Object();

    public static void main(String[] args) {
        //启动线程
        new Thread(new Lock1()).start();;
        new Thread(new Lock2()).start();
    }
}

class Lock1 implements Runnable{
    @Override
    public void run() {
        try{
            while (true){
                synchronized (Main.a){
                    System.out.println("锁住a");
                    //休眠1秒,让Lock2获取b
                    Thread.sleep(1000);
                    synchronized (Main.b){
                        System.out.println("锁住b");
                    }
                }
            }
        }catch (Exception e){
        }
    }
}

class Lock2 implements Runnable{
    @Override
    public void run() {
        try{
            while (true){
                synchronized (Main.b){
                    System.out.println("锁住b");
                    //休眠1秒,让Lock1获取a
                    Thread.sleep(1000);
                    synchronized (Main.a){
                        System.out.println("锁住a");
                    }
                }
            }
        }catch (Exception e){
        }
    }
}

5. 并发三大特性:原子性、可见性、有序性

  1. 原子性:多线程下非原子操作能确保结果正确。volatile不能实现原子性,synchronized通过加锁实现
  2. 可见性:内存可见性,线程间操作可见。volatile和synchronized会将线程在工作内存修改的值刷新到主内存
  3. 有序性:指令有序性。volatile通过内存屏障实现,synchronized通过加锁实现

6. synchronized锁作用域:类锁和对象锁

  1. 作用在非静态方法或者代码块锁定this,是对象锁,锁住当前调用对象
  2. 作用在静态方法或者代码块锁定class对象,是类锁,锁住整个类
  3. 类锁和对象锁互不干涉,可以交替执行

7. synchronized锁升级:无锁、偏向锁、轻量级锁、重量级锁

  1. 无锁
  2. 偏向锁:单线程下线程A多次获得同一把锁,可以不进行竞争,直接获取
    1. 对象头变化:将对象头复制到线程A的锁记录空间
    2. 升级:线程B也要参与争抢,偏向锁升级为轻量级锁
    3. 降级:线程A终止,先释放对象头,降级为无锁,然后线程B将对象头复制到锁记录空间
    4. 优点:不阻塞,加锁快
    5. 缺点:不适用于多线程
  3. 轻量级锁:线程A持有偏向锁,线程B也要争抢,线程B通过CAS方式尝试将对象头复制到锁记录空间,争抢失败,进行自旋
    1. 对象头变化:线程B使用CAS方式尝试将对象头复制到锁记录空间
    2. 升级:线程B在自旋时,线程C也要参与争抢,轻量级锁升级为重量级锁
    3. 优点:自旋代替阻塞,速度快
    4. 缺点:自旋导致CPU占用高
  4. 重量级锁:线程A持有偏向锁时,线程B在自旋时,线程C也要争抢,轻量级锁升级为重量级锁
    1. 对象头变化:线程B使用CAS方式尝试将对象头复制到锁记录空间时,线程C也要争抢
    2. 优点:阻塞代替自旋,CPU占用低
    3. 缺点:阻塞时间长

8. CAS:ABA、自旋开销

  1. CAS是compareAndSwap比较并交换,有三个操作数,内存值,预期值,新值,当且仅当内存值等于预期值时,才会将内存值替换为新值,否则进行自旋重试,直到成功。
  2. ABA:如果有一个数原来是A,被修改成B,最后又被改回A,那么在比较时会认为它没有发生变化,可能导致更新出错。可以使用AtomicStampedReference在变量前加一个版本号,ABA变成1A2B3A
  3. CAS是CPU级别的操作,运算快但是如果长时间自旋会导致CPU占用高,可以设置一个自旋上限,JVM默认是10次

9. AQS抽象队列同步器

  1. 维护了一个volatile修饰的int类型锁状态,提供了getState()、setState()、compareAndSetState()方法来操作这个值。在ReentrantLock的公平锁和非公平锁中有用到
  2. 维护了一个双向队列来管理线程获取锁,在ReentrantLock实现公平锁时用到,只有队列头部的线程才能获取锁

10. ReentrantLock和synchronized:实现方式、性能、公平锁和非公平锁、精确唤醒、优先级

  1. 实现方式
    1. synchronized通过JVM加锁和解锁
    2. ReentrantLock通过jdk加锁和解锁
  2. 性能:synchronized在1.6版本经历锁升级后性能一致
  3. 公平锁和非公平锁
    1. synchronized是非公平锁,避免线程切换
    2. ReentrantLock可以实现公平锁和非公平锁
      1. 公平锁:同步队列的头节点线程才能获取锁
        1. 创建公平锁:new ReentrantLock(true)
        2. 判断公平还是非公平
        3. farisync:先获取当前线程,AQS的同步队列,AQS的锁状态,如果锁状态为1,锁已经被持有,将当前线程存入同步队列队尾。如果锁状态为0,再判断当前线程是否是同步队列的头节点线程,如果不是,将当前线程存入同步队列的队尾,获取锁失败。如果当前线程是头节点线程,CAS方式将锁状态设置为1来获取锁,成功后记录当前线程以便重入
      2. 非公平锁:无视同步队列,当前线程可以直接进行CAS获取锁
        1. nofairsync:获取当前线程,获取AQS的锁状态,如果锁状态为1,获取失败,如果锁状态为0,当前线程直接进行CAS方式将锁状态设置为1来获取锁,获取成功后记录当前线程以便重入
  4. 精确唤醒:ReentrantLock通过绑定多个condition实现
  5. 优先使用synchronized,因为synchronized是通过JVM实现的,不用手动释放锁,ReentrantLock需要手动释放锁

10. 手撕:ReentrantLock精确唤醒轮番打印10次ABC(重点)

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class Main{

    //CountDownLatch
    private static final CountDownLatch latch = new CountDownLatch(10);

    //创建ReentrantLock,绑定3个condition
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition A = lock.newCondition();
    private static final Condition B = lock.newCondition();
    private static final Condition C = lock.newCondition();

    //控制标志符
    private static int flag = 1;

    public static void main(String[] args) throws InterruptedException {
        //初始化countdownlatch数量
        long loop = latch.getCount();

        //启动线程
        new Thread(() ->{
            for(int i=1;i<=loop;i++){
                try {
                    printA();
                }catch (Exception e){
                }
            }
        },"A").start();

        new Thread(() ->{
            for(int i=1;i<=loop;i++){
                try {
                    printB();
                }catch (Exception e){
                }
            }
        },"B").start();

        new Thread(() ->{
            for(int i=1;i<=loop;i++){
                try {
                    printC(i);
                }catch (Exception e){
                }
            }
        },"C").start();

        //当count计数为0时,终止
        latch.await();
    }

    public static void printA(){
        try{
            lock.lock();
            if(num != 1){
                A.await();
            }
            System.out.print(Thread.currentThread().getName());
            num = 2;
            B.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }

    public static void printB(){
        try{
            lock.lock();
            if(num != 2){
                B.await();
            }
            System.out.print(Thread.currentThread().getName());
            num = 3;
            C.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }

    public static void printC(long loop){
        try{
            lock.lock();
            if(num != 3){
                C.await();
            }
            System.out.print(Thread.currentThread().getName());
            num = 1;
            A.signal();
            latch.countDown();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }
}

11. volatile:不能实现原子性、内存可见性、指令有序性、与synchronized的区别

  1. 不能实现原子性:volatile对于非原子操作来说,多线程下不能保证其执行结果的正确性。比如i=5,i++,多线程下读取,可能会出现两个线程同时执行i++,最终结果为6
  2. 内存可见性:volatile读时会将JMM中工作内存的变量副本设置为无效,要求线程去往主内存中读取最新的值。volatile写时会将工作内存中修改的值刷新到主内存
  3. 指令有序:内存屏障
    1. volatile写前:确保之前的写操作都已经刷新到主内存
    2. volatile写后:禁止这个volatile写与后面的volatile操作重排
    3. volatile读前:禁止这个volatile读与后面的读操作重排
    4. volatile读后:禁止这个volatile读与后面的写操作重排
  4. volatile与synchronized的区别
    1. 作用域:synchronized可以用于类、方法、代码块,volatile只能作用在变量
    2. 线程阻塞:volatile是不加锁的机制,不阻塞。
    3. 线程安全:因为volatile不能实现原子性,所以不是线程安全

12. ThreadLocal:底层实现、内存泄漏、与synchronized的区别

  1. 底层实现:通过ThreadLocalMap封装,key为ThreadLocal的实例,value是传入的对象。每个线程都有一个ThreadLocal,使用ThreadLocal可以实现线程间数据隔离
  2. 内存泄漏:因为ThreadLocalMap的key是用this指代的ThreadLocal实例,是弱引用,而value是用new创建的强引用,弱引用在下一次GC时会被回收,key被回收后变为null,无法获取value,导致内存泄漏。虽然ThreadLocal的get和set都会对key和value进行判断空,但还是建议在使用完ThreadLocal后调用remove从Map中移除
  3. 与synchronized的区别
    1. 都是解决多线程下访问变量问题
    2. ThreadLocal采用空间换时间的策略,为每一个线程提供一个变量副本
    3. synchronized采用时间换空间的策略,加锁确保线程有序访问变量
    4. ThreadLocal不是线程安全的,如果get之后使用了其他线程对变量进行修改,会发生线程不安全

13. 线程池:优缺点、请求过程、创建方式、三种类型、七大参数、四种拒绝策略

  1. 优:线程池线程可以复用,可以减少重复创建和销毁线程的开销
  2. 缺:大量创建线程可能会导致OOM
  3. 请求过程:核心线程池、同步队列、最大线程池、拒绝策略
  4. 创建方式:Executors和new ThreadPoolExecutors
    1. 使用Executors创建三种已经设置好参数的线程池
    2. 使用new ThreadPoolExecutors创建自定义参数的线程池
  5. 三种类型
    1. SingleThreadExecutor单例线程池
    2. FixedThreadPool固定数量线程池
    3. CachedThreadPool可伸缩数量线程池
  6. 七大参数
    1. corePoolSize核心线程池线程数量
    2. maximumPoolSize最大线程池线程数量
    3. keepalive:线程存活时间
    4. uint:日期单位
    5. workQueue:同步队列
    6. ThreadFactory线程工厂
    7. handler拒绝策略
  7. 四种拒绝策略
    1. abortPolicy:不接受后续线程,抛出异常
    2. callerRunPolicy:使用提交任务的线程执行
    3. discardPolicy:不接受后续,不抛出异常
    4. discardOldest:抛弃最早的线程

14. 最大线程池数量如何设置:IO密集、CPU密集

  1. IO密集型:CPU利用率低,存在大量IO操作,maximumPoolSize为CPU核数的两倍:Runtime.getRuntime().avaliableProcessors()
  2. CPU密集型:CPU利用率高,不存在线程切换,maximumPoolSize为CPU核数:Runtime.getRuntime().avaliableProcessors()

15. 为什么不能使用Executors创建线程池

  1. Executors创建的SingleThreadExecutors和FixedThreadPool的workQueue的LinkedBlokcingQueue的容量为Integer.MAX_VALUE,可能会存放大量的请求,导致OOM
  2. Executors创建的CachedThreadPool的maximumPoolSize为Integer.MAX_VALUE,可能会创建大量的线程,导致OOM

16. 手撕:消费者生产者模型(ReentrantLock)、两个线程打印1A2B3C4D5E6F(synchronized)、三个线程打印1-100(volatile)

  1. 生产者消费者模型:ReentrantLock实现精确唤醒

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    public class LC{
    
     public static Lock lock = new ReentrantLock();
     public static Condition producer = lock.newCondition();
     public static Condition consumer = lock.newCondition();
    
     //控制标志符
     public static boolean flag = false;
    
     public static void main(String[] args) {
         new Thread(() ->{
             while (true){
                 producer();
             }
         }).start();
         new Thread(() ->{
             while (true){
                 consumer();
             }
         }).start();
     }
    
     public static void producer(){
         try{
             lock.lock();
             if(flag != false){
                 producer.await();
             }
             System.out.println("生产");
             Thread.sleep(1000);
             flag = true;
             consumer.signal();
         }catch (Exception e){
         }finally {
             lock.unlock();
         }
     }
    
     public static void consumer(){
         try{
             lock.lock();
             if(flag != true){
                 consumer.await();
             }
             System.out.println("消费");
             Thread.sleep(1000);
             flag = false;
             producer.signal();
         }catch (Exception e){
         }finally {
             lock.unlock();
         }
     }
    }
  2. 双线程打印1A2B3C:synchronized锁住object对象,notify唤醒等待的线程,当前线程wait等待

    public class LC{
     //通过一个对象锁实现
     public static Object object = new Object();
     //字符数组
     public static char[] nums = new char[]{'1','2','3'};
     public static char[] words = new char[]{'A','B','C'};
    
     public static void main(String[] args) {
         //启动两个线程
         new Thread(() ->{
             //获取锁
             synchronized (object){
                 for(char num : nums){
                     System.out.print(num);
                     //唤醒等待的线程
                     object.notify();
                     //当前线程进入等待
                     try {
                         object.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 //遍历完之后通知main线程
                 object.notify();
             }
         }).start();
         new Thread(() ->{
             //获取锁
             synchronized (object){
                 for(char word : words){
                     System.out.print(word);
                     System.out.println();
                     //唤醒等待的线程
                     object.notify();
                     //当前线程进入等待
                     try {
                         object.wait();
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
                 //遍历完之后通知main线程
                 object.notify();
             }
         }).start();
     }
    }
  3. 三个线程打印1-100:volatile实现线程间可见

    public class LC{
    
     public static volatile int num = 1;
     public static volatile int flag = 1;
    
     public static void main(String[] args) {
         //三个线程打印从1到100
         new Thread(() ->{
             while (num <= 100){
                 if(flag == 1){
                     System.out.println(Thread.currentThread().getName() + ":" + num);
                     num++;
                     flag = 2;
                 }
             }
         },"A").start();
    
         new Thread(() ->{
             while (num <= 100){
                 if(flag == 2){
                     System.out.println(Thread.currentThread().getName() + ":" + num);
                     num++;
                     flag = 3;
                 }
             }
         },"B").start();
    
         new Thread(() ->{
             while (num <= 100){
                 if(flag == 3){
                     System.out.println(Thread.currentThread().getName() + ":" + num);
                     num++;
                     flag = 1;
                 }
             }
         },"C").start();
     }
    }
全部评论

相关推荐

评论
1
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务