Java并发知识总结

1、什么是进程、线程?

  • 进程:进程是系统分配资源的最小单位,电脑中运行的一个程序就是一个进程,比如QQ打开后,就会有一个进程
  • 线程:线程是比进程更小的单位,是CPU调度的最小的单位,在一个进程中可以划分多个进程,这些进程,共享进程的堆区和方法区的共享资源,但他们都有各自的虚拟机栈,程序计数器,本地方法栈,这些线程之间的切换比进程之间的切换快很多,所以线程也叫轻量级的进程。

2、什么是线程安全和线程不安全?

  • 当有多个线程同时访问一个共享资源时就会出现线程安全的问题,如果同一时间只有一个线程能得到共享资源就是线程安全的,也就是其他的线程的执行不会让当前线程产生错误,比如有一个共享变量a初始值为0,有1000个线程对a进行加一操作,如果不加保护的话,结果可能会小于1000,这就是线程不安全的。

3、什么是自旋锁?

  • 当多个线程访问临界区是,线程这有拿到了监视器锁之后才能执行临界区的代码,但同一时间只有一个线程拿到锁,没到到锁的线程只有变为阻塞状态,自旋锁认为线程等待的时间是非常短的,所以没拿到所得时候,就执行一个循环等待,直到拿到锁,这样线程就不会进入阻塞状态,但自旋的缺点就是,在自选的过程中会消耗CPU资源,造成浪费,也有自适应的自旋锁,可以根据不同的情况来调整自旋的时间。

4、什么是Java内存模型?

  • Java内存模型试图屏蔽不同硬件不同操作系统内存访问的差异,以实现java在不同平台访问内存都达到同样的效果
  • CPU处理的速度和内存的速度是相差很大的,这样CPU就会受到内存速度的限制导致CPU利用率不高,为了解决这个问题就在CPU中加入了缓存,所以每个线程不仅有共享的主存还有自己的缓存,缓存中存放的是主存的副本,CPU操作的是cache中的副本,在适当的时间把cache中的数据写会主存。

5、什么是CAS?

  • CAS是指compare and swap,意识是指一个旧的预期值A,主内存的值是B,要修改的值C,当且仅当A==B的时候,A的值才会被修改成C,而且这个操作是原子性的,是一个非阻塞性的 乐观锁,比如在主存中有一个变量a = 1,线程的工作内存中有一个变量a的副本,现在线程要把a变成2,CAS就会用native本地方法比较工作内存中的值和主存中的值是否相等,如果相等则更新,防止了其他线程对数据的修改,当前线程修改了a后,其他线程工作内存中的缓存就会失效,会从新从主存中获取。

6、什么是乐观锁和悲观锁?

  • 乐观锁:对数据持一种乐观的态度,认为在并发过程中不会有别的线程修改当前线程用的数据,等到了真的用到数据的时候在检查数据有没有被别的线程修改过,比如CAS就是乐观锁
  • 悲观锁:对数据持一种悲观的态度,认为别的线程会修改当前线程用的数据,所以线程会加锁,用完了在解锁,比如容synchronize

7、什么是AQS?

8、什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?

  • 原子在化学中是不可再分的,一个原子操作通常包含几个操作,这几个操作都成功了这个原子操作才算成功,如果有一个失败了,其他的操作也会失败。

1.基本类型

  • AtomicInteger:整形原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类

2.数组类型

  • AtomicIntegerArray:整形数组原子类
  • AtomicLongArray:长整形数组原子类
  • AtomicReferenceArray:引用类型数组原子类

3.引用类型

  • AtomicReference:引用类型原子类
  • AtomicStampedRerence:原子更新引用类型里的字段原子
  • AtomicMarkableReference :原子更新带有标记位的引用类型

4.对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整形字段的更新器
  • AtomicLongFieldUpdater:原子更新长整形字段的更新器
  • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用CAS 进行原子更新时可能出现的 ABA 问题。

9、什么是Executors框架?

10、什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?

   BlockingQueue有两种实现方式

  1. FIFO队列:LinkBlockingQueue,ArrayBlockingQueue(固定长度)
  2. 优先级队列:PriorityBlockingQueue

BlockingQueue提供了take()和put()方法,当队列中是空的时候,take会阻塞知道队列中有东西,当队列满了以后,put()方法会阻塞,等到队列有位置后才可以入队

   BlockingQueue 实现生产者消费者问题

public class ProducerConsumer {

    private static BlockingQueue<String> queue = new ArrayBlockingQueue<>(5);

    private static class Producer extends Thread {
        @Override
        public void run() {
            try {
                queue.put("product");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("produce..");
        }
    }

    private static class Consumer extends Thread {

        @Override
        public void run() {
            try {
                String product = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print("consume..");
        }
    }
}

public static void main(String[] args) {
    for (int i = 0; i < 2; i++) {
        Producer producer = new Producer();
        producer.start();
    }
    for (int i = 0; i < 5; i++) {
        Consumer consumer = new Consumer();
        consumer.start();
    }
    for (int i = 0; i < 3; i++) {
        Producer producer = new Producer();
        producer.start();
    }
}

produce..produce..consume..consume..produce..consume..produce..consume..produce..consume..

11、什么是Callable和Future?

  • 在jdk1.5之前,如果需要获取子线程执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦,在1.5之后提供了Callable和Future接口,用他们可以方便的获得线程的返回值,首先创建一个实现了Callable接口的类,然后创建一个FutureTask,把Callable当做参数,然后创建一个Thread来驱动FutureTask,可以用FutureTask的get方法获取返回值

12、什么是FutureTask?

  • FutureTask实现了RunableFuture接口,RunableFuture接口有继承了Future可Runable接口,通过FutureTask可以获得Callable的返回值

13、什么是同步容器和并发容器的实现?

  • 同步容器:同步容器可以简单的理解为加了synchronize锁的容器,比如Vector,Hashtable,以及Collections.synchronizedSet,synchronizedList等方法返回的容器。这些容器为了线程安全都加了synchronize锁,但这些容器几乎没有用,因为在并发中效率太低了
  • 并发容器:并发容器采用了一种颗粒更细的加锁方式,可以称为分段加锁,比如ConcurrentHashMap,在这种锁机制下,允许任意数量的读线程并发地访问map,并且执行读操作的线程和写操作的线程也可以并发的访问map,同时允许一定数量的写操作线程并发地修改map,所以它可以在并发环境下实现更高的吞吐量。

14、什么是多线程?优缺点?

多线程就是多个线程并发执行,一个进程要完成一个任务,把这个任务划分成更小的任务分给每一个线程,这几个线程并发执行,就是多线程

优点:

  1. 从计算机底层来说:线程可以看作是轻量级的进程,是CPU调度的最小的单位,线程之间的切换要比进程之间切换快的多,另外,多核 CPU 时代意味着多个线程可以同时运行,这减少了线程上下文切换的开销。
  2. 从当代的互联网来说:现在的系统动不动就要求千万级的并发,而多线程并发编程是并发系统的基础,利用好多线程可以大大提高系统的并发量
  3. 可以提高CPU的利用率:在单核时代,要先进行IO然后再进行计算,这样二者的利用率只有50%,多线程可以在CPU计算的时候进行IO操作这样CPU的利用率就提高了,在多核时代,假如有一个任务,把这个任务分配给不同的线程,这些线程在不同的CPU中跑,可以让每一个CPU充分利用。

缺点:

  • 并发编程的目的是提高程序的执行效率提高程序执行的速度,但也存在很多问题比如上下文之间的切换问题,死锁问题,以及受限于软件和硬件资源的问题。

15、什么是多线程的上下文切换?

  • 并发执行的每个线程都会分一个时间片,当线程的执行时间到了,就会让出CPU给别的线程执行,线程保存当前执行的状态,到下一个线程执行的过程就是上下文切换。上下文切换是非常频繁的,一秒钟要切换上百次,每一次切换都是纳秒级别的,所以上下文切换是非常消耗CPU的,有可能是操作系统消耗最大的操作,linux比其他系统有很多优点,其中一个就是上下文切换的速度很快。

16、ThreadLocal的设计理念与作用?

在并发编程中每一个线程都可以使用共享资源,所以我们要对这些共享资源做同步,但做同步会涉及到很多问题,所以为什么不让每个线程都拥有这个共享资源的副本呢,那每个线程对副本进行修改,就互不干扰了,所以ThreadLocal就是解决这个问题的,ThreadLocal可以绑定每个线程的数据,当线程访问该变量的时候就会拿到当前线程的变量。

  • 原理:在ThreadLocal类中有一个Map,Map以线程为Key值,所以线程可以拿到当前线程绑定的数据。

  • 存在的问题:Map中的key值是threadlocald的弱引用,当没有被外部强引用的时候会被清理,但value是强引用不会被清理,所以就存在Key为null的值,这样就会造成内存泄漏,但ThreadLocalMap已经解决了这个问题,在每次get()和set()的时候都会清理key为null的值

  • 应用举例:在数据库额连接池中要保证每个线程都拥有自己的connection,不然事务就会出现混乱,所以就可以把每个线程的connection绑定到当前前程中去,就可以保证同一个线程用同一个连接

17、ThreadPool(线程池)用法与优势?

  1. 降低资源消耗。 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  2. 提高响应速度。 当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

18、Concurrent包里的其他东西

  • ArrayBlockingQueue、CountDownLatch等等。

19、synchronized和ReentrantLock的区别?

  1. 都是可重入锁
  2. synchronized是基于jvm层面的ReentrantLock是基于jdk层面的
  3. ReentrantLock比synchronized的功能更多:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

20、Semaphore有什么作用?

Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。

public class SemaphoreExample {

    public static void main(String[] args) {
        final int clientCount = 3;
        final int totalRequestCount = 10;
        Semaphore semaphore = new Semaphore(clientCount);
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < totalRequestCount; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    System.out.print(semaphore.availablePermits() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        }
        executorService.shutdown();
    }
}
before..before..before..before..before..before..before..before..before..before..after..after..after..after..after..after..after..after..after..after..

21、Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?

Lock接口比同步方法和同步块提供了更具扩展性的锁操作。
他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。
它的优势有

  1. 可以使锁更公平
  2. 可以使线程在等待锁的时候响应中断
  3. 可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间 可以在不同的范围,以不同的顺序获取和释放锁

整体上来说Lock是synchronized的扩展版,Lock提供了无条件的、可轮询的(tryLock方法)、定时的(tryLock带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition方法)锁操作。另外Lock的实现类基本都支持非公平锁(默认)和公平锁,synchronized只支持非公平锁,当然,在大部分情况下,非公平锁是高效的选择。

22、Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?

23、ConcurrentHashMap的并发度是什么?

24、ReentrantReadWriteLock读写锁的使用?

25、CyclicBarrier和CountDownLatch的用法及区别?

26、LockSupport工具?

27、Condition接口及其实现原理?

28、Fork/Join框架的理解?

29、wait()和sleep()的区别?

  • wait()和sleep()都可以暂定现在的进程,但是wait()会释放当前的锁,但sleep不会释放锁,sleep在时间到了以后会自己苏醒,继续执行,而wait需要别的线程调用他的notify()或者notifyAll()方法。

30、线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)?

在这里插入图片描述

  • 当线程创建完成以后就是New(新建)状态,当调用star()之后就成为Runnable可运行状态,可运行状态其实包含就绪状态和运行状态,当线程得到时间片后就成为运状态,当线程没有获得排它锁的时候就会变成阻塞状态,调用了wait(),方法后就会成为waiting状态。

31、start()方法和run()方法的区别?

当创建了一个线程后,线程就会进入新建状态,当调用star()后就会进入就绪状态,一旦获得时间片后就会进入运行状态,会自动的执行线程中的run()方法,调用start()方法会启动一个线程,和当前线程是异步的,而调用run()方法只是调用了线程中的一个普通方法,和调用普通类的方法没什么区别,没有启动线程,和当前的线程是同步执行的。

32、Runnable接口和Callable接口的区别?

实现了Runnable和Callable接口的类只是一个任务,并不能算是一个线程,需要通过Thread来调用,可以说任务是通过线程驱动而执行的。二者的区别就是Callable接口可以有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

33、volatile关键字的作用?

volatile关键字可以实现多线程之间数据的可见性,但并不能保证在多线程下是线程安全的,而且可以防止指令重排

原理:

  • 由于CPU和内存之间速度相差很大,这样会导致CPU的利用率不高,所以在CPU中加了缓存,这样就可以解决这个问题,java内存模型中每次不是从主存中取数据,而是从每个线程自己的工作内存中取数据,但是这样就会造成一个问题就是在多线程中会出现缓存不一致的问题,比如有一个变量a在主存中的初始值是1,每个线程的工作内存中都有一个a的副本,当一个线程修改了a的值,这时候a变成了0,但是其他线程并不知道,其他线程自己的工作内存中还是原来的旧值,当加了volatile关键字后,就要遵循操作系统的缓存一致协议,当前线程修改了之后,会被强制刷回主存,每一个线程就会在总线上嗅探是否有值改变了,如果发现主存中的值变了,那工作内存中的值就会失效,也就是,当前线程对值的修改对其他线程是可见的。
  • 虽然可以实现数据的可见性,但并不能保证原子性,所以在多线程下并不能保证是线程安全的

34、Java中如何获取到线程dump文件?

35、线程和进程有什么区别?

  1. 线程是资源分配的最小单位,进程是CPU调度的最小单位
  2. 线程之间的切换比进程之间的切换快很多,线程可以看成是轻量级的进程
  3. 进程之间是独立的,线程之间是相互联系的,线程之间共享进程的共享资源

36、线程实现的方式有几种(四种)?

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 4.使用线程池(有返回值

37、高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

38、如果你提交任务时,线程池队列已满,这时会发生什么?

39、锁的等级:方法锁、对象锁、类锁?

40、如果同步块内的线程抛出异常会发生什么?

41、并发编程(concurrency)并行编程(parallellism)有什么区别?

42、如何保证多线程下 i++ 结果正确?

1. 可以用synchronize关键字
2. 用CAS+volatile

43、一个线程如果出现了运行时异常会怎么样?

44、如何在两个线程之间共享数据?

45、生产者消费者模型的作用是什么?

46、怎么唤醒一个阻塞的线程?

47、Java中用到的线程调度算法是什么

48、单例模式的线程安全性?

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

49、线程类的构造方法、静态块是被哪个线程调用的?

50、同步方法和同步块,哪个是更好的选择?

  • 应该尽量使用同步块而不是同步方法,这样可以缩小同步范围,从而减少锁争用

51、如何检测死锁?怎么预防死锁?

  死锁满足的条件

  1. 互斥资源
  2. 不可剥夺
  3. 请求保持
  4. 环路等待
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值