Java并发

1. 线程与进程的区别:进程由CPU调度,执行计算机程序。线程由进程调度,可独立运行。

2. 创建线程的方式,使用接口还是类:runnable接口、callable接口、thread类、线程池。使用接口好,接口开销小且可以实现多继承

  1. 实现runnable接口
    1. 重写run方法
    2. 创建实现类实例,通过实现类实例创建Thread对象,Thread对象调用start使线程就绪
  2. 实现callable接口,获取返回值
    1. 重写call方法,设定返回值类型。
    2. 创建实现类实例,通过实现类实例创建FutureTask实例,使用FutureTask实例创建Thread对象,Thread对象调用start使线程就绪,FutureTask实例可以调用get方法获取返回值
  3. 继承Thread类
    1. 重写run方法
    2. 创建子类实例,调用start使线程就绪
  4. 使用接口好,继承Thread类开销大,使用接口可以实现多继承

3. 线程生命周期、守护线程:new、runnable、running、blocking、dead

  1. new新建:new Thread
  2. runnable可运行:调用start方法后,等待cpu调度
  3. running运行:执行run方法。可以去到block、runnable、terminated
    1. 调用wait、sleep、join会进入block
    2. 调用stop会进入terminated
  4. block阻塞:可以去到runnable、terminated
    1. 同步阻塞:synchronized,等待获取锁,回到runnable
    2. 等待阻塞:wait、sleep,回到runnable
    3. 其他阻塞:interrupted,回到runnable
    4. 调用stop,进入terminated
  5. terminated终止
  6. 只要还有一个非守护线程未结束,守护线程就要全部工作。当非守护线程全部结束,守护线程随着JVM而停止。如GC线程就是守护线程。

4. 如何中断一个线程:interrupt和isInterrupted。如果已经调用了wait或sleep,会抛出异常

  1. 调用interrupt方法,可以通过isInterrupted判断是否进入中断状态
  2. 如果已经调用了wait或sleep再调用interrupt,会抛出异常

5. wait、notify、notifyAll,wait和sleep的区别:是Object方法,wait会释放锁,notify拿到锁执行唤醒。sleep是Thread方法,不会释放锁

  1. 都是Object方法,wait必须在synchronized下使用,wait会让线程进入等待状态,会释放锁。当其他线程执行到notify时,会唤醒等待的线程,使其重新进入runnable状态。
  2. 因为wait线程需要被notify唤醒,只有同一把锁上等待的线程才能实现唤醒,因此wait和notify都属于Object这个类锁就可以保证wait线程一定能够被notify唤醒。
  3. sleep是Thread方法,不会释放锁。wait是Object类方法,会释放锁

6. await、signal、signalAll:是JUC包下的Condition的方法,一个Lock可以绑定多个condition,实现精确唤醒

7. 线程间通信方式:synchronized、volatile、wait和notify、await和signal、join

  1. volatile:
    1. 读:将工作内存的变量副本设置为无效,要求线程去往主内存中读取最新的变量
    2. 写:将工作内存中修改后的变量刷新到主内存
  2. synchronized:
    1. 读:通过加锁来,保证只有一个线程的工作内存去往主内存中读取变量
    2. 写:释放锁时将修改后的变量刷新到主内存
  3. wait和notify
    1. 线程A调用wait进入等待,线程B执行notify唤醒线程A
  4. join
    1. 线程A调用join阻塞线程B,线程B等待线程A执行完毕才能继续执行

8. 死锁发生的条件、死锁预防机制、排除死锁:互斥、不剥夺、请求和保持、循环等待。一次性分配、有一个就不分配、可剥夺、资源有序分配。jps -l、jstack -l [deadlockId]

  1. 死锁发生条件
    1. 互斥条件:互相锁住对方需要的资源不释放
    2. 不可剥夺条件:资源未使用完不会释放
    3. 请求和保持条件:请求资源时,不释放持有的资源
    4. 循环等待条件:发生死锁时,进入循环等待
  2. 死锁预防
    1. 资源一次性分配:一次分配所有资源,不再请求。破坏请求条件
    2. 有一个资源就不分配:破坏了请求条件
    3. 可剥夺资源:先释放资源才能请求资源,破坏不可剥夺条件
    4. 资源有序分配:将资源标号,按需获取,破坏循环等待条件
  3. 死锁查找
    1. jps -l查看进程
    2. jstack -l [id],查看进程的堆栈情况,找到deadlock相关信息

9. 手写死锁

public class Main{

    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{
            System.out.println("Lock1");
            while (true){
                synchronized (Main.a){
                    System.out.println("锁住a");

                    Thread.sleep(2000);

                    synchronized (Main.b){
                        System.out.println("锁住b");
                    }
                }
            }
        }catch (Exception e){
        }
    }
}

class Lock2 implements Runnable{

    @Override
    public void run() {
        try{
            System.out.println("Lock2");
            while (true){
                synchronized (Main.b){
                    System.out.println("锁住b");

                    Thread.sleep(2000);

                    synchronized (Main.a){
                        System.out.println("锁住a");
                    }
                }
            }
        }catch (Exception e){
        }
    }
}

10. 并发三大特性:原子性(synchronized)、内存可见性(volatile和synchronized)、指令有序性(volatile和synchronized)。因为volatile不能保证原子性,索引volatile并不是线程安全的

  1. 原子性:对于原子操作要能确保其结果正确,如i++就不是原子性的操作。读取i的值,i+1,i=i+1三步。synchronized可以实现原子性,volatile不能保证,因此volatile不是线程安全
  2. 可见性:一般指的是变量可见性,即一个线程对变量的修改其他线程可见。synchronized和volatile
  3. 有序性:一般指指令有序性,synchronized通过加锁,volatile通过内存屏障

11. synchronized锁作用域:

  1. 非静态方法对象锁,不同对象调用不同对象锁可以交替执行。
  2. 静态方法类锁,不同类对象调用不同类锁无法交替执行。
  3. 类锁和对象锁互不干扰

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

  1. 无锁
  2. 偏向锁:当一个线程多次获得同一把锁,那么它不需要参与争抢,可以通过重入锁机制实现多次持有(RentrantLock和synchronized都是可以重入锁。ReentrantLock会记录当前线程,以便重入)
  3. 轻量级锁:当线程A持有偏向锁,线程B也要参与争抢时,偏向锁升级为轻量级锁
  4. 重量级锁:当线程A持有偏向锁,线程B也要争抢这个锁时,线程B会通过CAS方式争抢,如果失败,进入自旋。当线程A处在偏向锁,线程B在进行自旋时,线程C也要争抢这个锁,那么轻量级锁会升级为重量级锁

13. 乐观锁和悲观锁:CAS乐观锁,不加锁执行。synchronized和ReentrantLock悲观锁,加锁执行。

  1. 乐观锁:认为不会发生冲突,不加锁,发生冲突,进行重试,直到成功
  2. 悲观锁:先加锁再操作。synchronized和ReentrantLock

14. CAS自旋锁,ABA问题,自旋消耗资源问题,哪些地方用到了CAS

  1. CAS是CompareAndSwap,比较再交换。CAS是一种无锁机制,有三个操作数,内存值、预期值、新值。当且仅当内存值等于预期值时,才会将内存值设置为新值。否则进行自旋
  2. CAS自旋消耗:CAS是CPU级别的操作,原子性操作,速度快,但是如果一直自旋,CPU占用高。可以设定一个自旋上限。
  3. ABA问题:如果一个值原本是A,后来被改为B,最后又被改回A。比较的时候会认为A没有变,对A进行交换。但实际上A已经被修改。可以通过原子类的版本号AtomicStampedReference在变量前加一个版本号,ABA变成1A2B3A
  4. synchronized的锁升级过程用到了CAS。hashMap1.8之后的put用到CAS。原子类的自增方法用到CAS。
  5. 可以通过Unsafe类调用CAS

15. ReentrantLock和synchronized的区别:可重入、实现方式、性能、等待可中断、公平锁和非公平锁、精确唤醒、使用优先级

  1. 可重入:synchronized和ReentrantLock都是可重入
    1. synchronized通过aqs维护的state锁状态实现,0代表可以重入,1代表不能重入
    2. ReentrantLock通过tryLock来实现
  2. 实现方式
    1. synchronized是通过JVM
    2. ReentrantLock是通过jdk
  3. 性能:synchronized在1.6版本经过一次锁升级优化,性能和ReentrantLock相同
  4. 等待可中断
    1. synchronized不行
    2. ReentrantLock可以,通过lockInterruptibly方法
  5. 公平锁和非公平锁
    1. ReentrantLock可以实现公平锁和非公平锁,默认实现非公平锁,避免线程切换,效率更高
    2. synchronized只能实现非公平锁
  6. 精确唤醒
    1. ReentrantLock可以绑定多个condition实现精确唤醒
    2. synchronized只能随机唤醒wait线程
  7. 优先级
    1. 如果不是需要使用ReentrantLock的高级功能,优先使用synchronized,因为synchronized是基于JVM实现的,不需要手动释放锁,而ReentrantLock要手动释放锁,否则会造成死锁

16. 手撕: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 int num = 1;
    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();

    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();
            //当num为1时,打印A
            if(num != 1){
                A.await();
            }

            System.out.print(Thread.currentThread().getName());
            //num改为2
            num = 2;
            //通知B打印
            B.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }

    public static void printB(){
        try{
            lock.lock();
            //当num为2时,打印B
            if(num != 2){
                B.await();
            }

            System.out.print(Thread.currentThread().getName());
            //num改为3
            num = 3;
            //通知C打印
            C.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }

    public static void printC(long loop){
        try{
            lock.lock();
            //当num为3时,打印C
            if(num != 3){
                C.await();
            }

            System.out.print(Thread.currentThread().getName());
            //第几轮
            System.out.println("["+ loop+"]");

            //num改为1
            num = 1;
            //通知A打印
            A.signal();
            //计数-1
            latch.countDown();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }
}

17. ReentrantLock实现公平锁和非公平锁:通过AQS维护的volatile锁状态和阻塞队列

  1. 公平锁:只有阻塞队列头部的线程才能获取锁。
    1. 创建公平锁,ReentrantLock(true)
    2. 判断是公平锁还是非公平锁
    3. fairSyn:先获取当前线程和AQS中维护的volatile锁状态和同步队列。如果锁状态为0,表明锁可以获取。可以通过CAS方式尝试将锁状态设置为1,同时也会判断当前线程是否是同步队列的头线程,如果不是,那么就算CAS成功也不能获取锁,返回false。如果线程是头线程,会记录当前线程,以便重入。
  2. 非公平锁:当前线程只需要完成CAS就可以获取锁
    1. 创建非公平锁:ReentrantLock()
    2. nofairSync:先获取当前线程和AQS中维护的volatile锁状态。如果锁状态为0,表明锁可以被获取,通过CAS方式尝试将状态设置为1,如果成功,记录当前线程,以便后续重入。

18. AQS是什么,哪些地方用到了AQS

  1. AQS是队列同步器,维护了一个volatile int锁状态和线程同步队列来管理线程。有三个主要方法,getState、setState、compareAndSetState
  2. 两种模式:独占和共享
  3. ReentrantLock的公平锁和非公平锁

19. volatile特性:不能保证原子性、内存可见性、指令有序性

20. volatile是线程安全的吗,volatile为什么不能保证原子性

  1. 因为volatile不能保证原子性,所以不是线程安全的
  2. 因为对于非原子性操作,如果i++,volatile无法保证其在多线程下的执行结果是准确的。如果有两个线程都读取了i++这个操作,本来i=5,进行两次i++应该返回i=7,但是两个线程分布执行一次i++,返回结果为i=6。因此无法保证原子性。而synchronized通过加锁确保线程只能有序执行操作,释放锁会将执行结果刷新到主内存

21. volatile内存可见性如何实现

  1. volatile读:JMM会将工作内存中的变量副本设置为无效,要求线程去往主内存中读取最新的变量
  2. volatile写:JMMV会将线程在工作内存中修改后的值刷新到主内存中

22. volatile指令有序性如何实现:内存屏障

  1. volatile写前:确保之前的写都已经刷新到主内存中
  2. volatile写后:禁止与后面的volatile操作重排
  3. volatile读前:禁止与后面的读重排
  4. volatile读后:禁止与后面的写重排

23. synchronized和volatile比较:线程安全、作用域、不能保证原子性、线程阻塞

  1. 作用域:volatile仅能作用在变量,synchronized可以作用在变量、方法、类
  2. 线程阻塞:synchronized会阻塞,volatile不会阻塞
  3. 线程安全:volatile不能保证原子性,不是线程安全。synchronized是线程安全

24. ThreadLocal底层实现:每个Thread都有一个ThreadLocalMap

  1. 底层实现:每个线程都有一个ThreadLocalMap,key为ThreadLocal对象,value为传入的对象
  2. 实现线程间数据隔离,父子线程间也无法通信
  3. set方法
    1. 先获取当前线程,判断ThreadLocalMap中是否存在当前线程,如果存在,更新。否则新建
  4. get方法
    1. 先获取当前线程,判断ThreadLocalMap中是否存在当前线程,如果存在,返回value。否则返回预设值
  5. remove方法
    1. 将当前线程从ThreadLocalMap中移除

25. ThreadLocal和synchronized的区别:都是解决高并发下访问变量问题

  1. ThreadLocal通过将线程和变量进行绑定来解决,采用空间换时间策略
  2. synchronized通过加锁来使线程有序访问变量,采用时间换空间策略
  3. ThreadLocal不是线程安全的,如果threadlocal.get()获取当前线程后使用到了其他线程,可能会出现多线程修改同一个变量问题。

26. ThreadLocal的内存泄漏问题,如何解决:

  1. 因为ThrealLocalMap的key是this指代的ThrealLocal实例,是弱引用,会被gc回收。而value是new创建的强引用,不会被gc回收。key被回收之后变为null,无法获取到value,造成浪费。
  2. 可以手动调用remove方法将当前线程从Map中移除

27. 为什么要用线程池:线程可复用,减少创建和销毁。大量创建线程可能导致OOM

28. 线程池创建方式、线程池三大类型、七大参数、四种拒绝策略

  1. Executors创建

  2. ThreadPoolExecutor自定义线程池参数

  3. SingleThreadExecutor单例线程池

  4. FixedThreadPool固定数量线程池

  5. CachedThreadPool可伸缩数量线程池

  6. ScheduledThreadPool固定数量的定时线程池

  7. corePoolSize:核心线程池大小,空闲时,队列满了,会超出

  8. maximumPoolSize:可同时活动的线程数量。超过该值,线程停止

  9. keepalivetime:最大存活时间

  10. uint:时间单位

  11. workqueue:同步队列,数组、链表、优先队列

  12. threadfactory:线程工厂

  13. handler:拒绝策略

  14. abortPolicy:超出承载,不接受后续,抛出异常

  15. callerRunPolicy:超出承载,使用main线程

  16. discardPolicy:超出承载,不接受后续,不抛异常

  17. discardOldest:超出承载,抛弃最老的线程

29. maximumPoolSize如何设置

  1. 使用CPU密集型:Runtime.getRuntime().avaliableProcessors

30. 为什么不能用Executors创建线程池:

  1. SingleThreadExecutors和FixedThreadPool的LinkedBlockingQueue的容量为Integer.MAX_VALUE,可能堆积大量线程,造成OOM。
  2. CachedThreadPool的maximumPoolSize为Integer.MAX_VALUE,可能创建大量线程,造成OOM

31. 手撕:消费者生产者模型:Lock和Condition实现、阻塞队列实现、两个线程打印1A2B3C4D5E6F

  1. 生产者消费者模型
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main{
    public static void main(String[] args) {

        Test test = new Test();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    test.producer();
                }
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    test.consumer();
                }
            }
        }).start();
    }
}

class Test{
    //使用Lock和Conditoon
    private Lock lock = new ReentrantLock();
    private Condition consumer = lock.newCondition();
    private Condition producer = lock.newCondition();

    //控制标志flag。true消费,停止生产。false生产,停止消费
    private boolean flag = false;

    public void producer(){
        try{
            lock.lock();
            //判断flag是否为true,如果为true,说明正在消费,停止生产
            if(flag == true){
                producer.await();
            }
            System.out.println("生产");
            Thread.sleep(1000);
            //flag改为true,通知消费
            flag = true;
            consumer.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }

    public void consumer(){
        try{
            lock.lock();
            //如果flag为false,说明正在生产,停止消费
            if(flag == false){
                consumer.await();
            }
            System.out.println("消费");
            Thread.sleep(1000);
            //flag改为false,通知生产
            flag = false;
            producer.signal();
        }catch (Exception e){
        }finally {
            lock.unlock();
        }
    }
}
  1. 阻塞队列实现生产者消费者
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Main{
    public static void main(String[] args) {

        //初始化一个阻塞队列,使用数组类型
        BlockingQueue queue = new ArrayBlockingQueue<>(10);

        new Thread(new producer(queue),"生产者").start();
        new Thread(new consumer(queue),"消费者").start();
    }
}

class producer implements Runnable{

    //创建一个阻塞队列,初始化为传入的queue
    BlockingQueue queue;
    producer(BlockingQueue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        int index = 0;
        while (true){
            //初始化资源
            String product = String.valueOf(index);

            //put放入队列
            try {
                queue.put(product);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //打印
            System.out.println(Thread.currentThread().getName()+"生产了"+product+",剩余商品数量:"+queue.size());

            //等待1秒,等消费者消费
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index++;
        }
    }
}

class consumer implements Runnable{

    //创建一个阻塞队列,初始化为传入的queue
    BlockingQueue queue;
    consumer(BlockingQueue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            //直接从队列中take
            try {
                System.out.println(Thread.currentThread().getName()+"消费"+queue.take()+",剩余商品数量:"+queue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //等待1秒,等生产者生产
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  1. 双线程打印1A2B3C

public class Main{
    public static void main(String[] args) {
        //数字字符数组
        char[] c1 = "123456".toCharArray();
        //字母字符数组
        char[] c2 = "ABCDEF".toCharArray();

        //创建一个Object对象
        Object object = new Object();

        //打印数字
        new Thread(() ->{
            synchronized (object){
                //遍历数字数字
                for(char c : c1){
                    System.out.print(c);

                    //唤醒另一个线程
                    object.notify();

                    //当前线程wait等待
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //打印完之后终止该线程
                object.notify();
            }
        }).start();

        //打印字母
        new Thread(() ->{
            synchronized (object){
                for(char c : c2){
                    System.out.print(c);

                    //唤醒另一个线程
                    object.notify();

                    //当前线程等待
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                //遍历完之后终止当前线程
                object.notify();
            }
        }).start();
    }
}

你可能感兴趣的:(Java并发)