3.多线程面试
1.线程的创建:
(1)继承Thread类:创建任务的同时创建线程
(2)实现Runnable接口:创建任务和创建线程分离
(3)实现Callable接口:可以用FutureTask捕获线程的执行结果
(4)线程池创建
1.步骤:(1)创建任务 (2)创建线程池 (3)把任务提交给线程池去执行
2.三种方法对比:
(1)实现Runnable接口和实现Callable接口可以避免单继承的局限性
(2)实现Runnable接口和实现Callable接口可以将创建任务与创建线程分类。可以让多个线程执行一个任务,而继承Thread类只能一个线程执行一个任务
3.线程的生命周期及状态转换
4.后台线程(守护线程):
(1)对于java程序来说,只要还有一个前台线程在运行,这个进程就不会结束;如果一个进程中只有后台线程在运行,这个进程就会结束
(2)即:进程中不存在前台线程时,整个进程就会结束
5.锁=对象锁=同步锁
6.多线程同步:死锁问题
7.多线程通信:生产者消费者模型
8.进程和线程的区别:进程是操作系统进行资源分配的基本单位;线程时操作系统进行调度的基本单位(即CPU分配时间的基本单位)
9.上下文切换:是指CPU从一个线程切换到另一个线程
10.wait()和sleep()的区别:
(1)wait释放锁,sleep不释放锁
字符串常量池;运行时常量池
11.线程同步就是:线程之间按照一定顺序执行
12.线程通信的方法:
(1)锁与同步
(2)等待/通知机制
(3)信号量
1.volatile:保证内存可见性(不保证原子性):如果用volatile关键字声明一个变量,在一个线程里面改变了这个变量的值,那么其他线程是立马可见更改后的值的
2.使用场景:多个线程(超过2个)需要合作的时候。此时使用锁和等待通知机制就不方便了
(4)管道
(5)其他通信相关
1.join,sleep,ThreadLocal
2.ThreadLocal用一个Map存储线程本地变量的副本。应用:数据库连接,session管理
13.在内存可见性上,volatile与锁具有相同的内存效果,volatile变量的读和锁的获取具有相同的内存语义;volatile变量的写和锁的释放具有相同的内存语义(volatile变量的读写与锁的获取释放具有相同的内存语义)
14.volatile提供一种比锁更轻量级的线程间的通信方式,volatile可以严格限制编译器和处理器对volatile变量与普通变量的重排序
15.在保证内存可见性上,volatile与锁具有相同的内存语义,所以可以作为一个轻量级锁来使用
在禁止指令重排序上,volatile也是非常有用的,比如单例模式,其中一种实现是:“双重锁检查”
16.几种锁:
(1)一个对象有四种锁状态:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态
17.锁可以从不同的角度分类,所以锁可以分为:乐观锁和悲观锁
(1)悲观锁就是我们常说的锁;乐观锁也称“无锁”,乐观锁总是认为对共享资源的访问没有冲突,线程无需加锁和等待。通常使用一种CAS的技术来保证线程执行的安全性。乐观锁天生免疫死锁。乐观锁用于“读多写少”的场景,悲观锁用于“写多读少”的场景
(2)CAS
1.CAS是原子操作
17.单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程
18.线程池
(1)池化技术:线程池,数据库连接池
(2)使用线程池的好处:1.降低资源消耗(创建线程和销毁线程需要耗费资源跟时间) 2.提高响应速度
(3)如何创建线程池:
1.通过ThreadPoolExecutor的构造方法来实现
2.通过Executor框架的工具类Executors来实现
(4)execute(task)方法和submit(task)方法的区别:execute用于提交没有返回值的任务,submit用于提交有返回值的任务
(5)ThreadPoolSize的构造方法中有几个比较重要的参数:corePoolSize,maximumPoolSize,keepAliveTime
1.corePoolSize:核心池大小(=线程池的大小=核心线程的个数)。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到任务缓存队列当中。当任务缓存队列满了之后,便创建新的线程。当线程个数达到maximumPoolSize时,就会开启拒绝策略,拒绝执行新提交的任务
2.keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
(6)通过Executors类中提供的几个静态方法来创建线程池:它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了
Executors.newCachedThreadPool();
//创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE(corePoolSize=0,maximumPoolSize=Integer.MAX_VALUE)
也就是说来了任务就创建线程去运行
Executors.newFixedThreadPool(
int
);
//创建固定容量大小的缓冲池(corePoolSize=maximumPoolSize)
Executors.newSingleThreadExecutor();
//创建容量为1的缓冲池(corePoolSize=maximumPoolSize=1)
19.死锁
(1)定义:两个线程在运行时都在等待对方的锁,这种现象称为死锁
(2)产生死锁必须具备以下4个条件:
1.互斥条件 2.请求与保持条件 3.不剥夺条件 4.循环等待条件
(3)如何避免死锁?只要破坏4个条件中的一个就可以了。
1.破幻互斥条件:没办法破坏 2.破坏请求与保持条件:一次性申请所有的资源
3.破坏不剥夺条件:占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它所占有的资源
4.破坏循环等待条件:靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。
20.线程间通信:生产者消费者模型
21.Atomic原子类
(1)原子操作是指一个操作是不可中断的。一系列操作要么全都执行,要么全不执行
(2)JUC中的原子类是哪4类?
1.基本类型 2.引用类型 3.数组类型 4.对象属性修改类型
22.多线程中的容器:普通容器,同步容器,并发容器。阻塞队列(在线程池中有使用到)
(1)同步容器:vector,stack,hashtable,Collections提供的静态工厂方法创建的类:Collections.syncronizedXxx()
(2)同步容器的缺陷:1.锁粒度大,并发执行效率低(锁粒度大,线程容易冲突,如读写冲突) 2.不一定线程安全
(3)并发容器:ConcurrentHashMap代替同步的Map,CopyOnWriteArrayList和CopyOnWriteArraySet分别代替List和Set
(4)ConcurrentHashMap可以做到读取数据不加锁,并且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个ConcurrentHashMap加锁
(5)CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
(6)阻塞队列:阻塞的是线程,队列存储的是任务。可以方便的实现生产者-消费者模型,不用额外的实现同步策略和线程间唤醒策略
(7)包括:ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue,DelayQueue。只要符合生产者-消费者模型的都可以使用阻塞队列
23.CountDownLatch、CyclicBarrier和Semaphore(一些辅助类)
(1)CountDownLatch:倒计数器。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了
(2)CyclicBarrier:循环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情,此时就可以利用CyclicBarrier了
(3)Semaphore翻译成字面意思为 信号量,Semaphore可以控制同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
(4)下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限
24.Interrupt
25.Java 线程同步的几种⽅方法?
(1)使⽤用Synchronized 关键字;
(2)wait 和 notify;
(3)使用特殊域变量 volatile 实现线程同步;
(4)使用可重入锁实现线程同步;
(5) 使用阻塞队列实现线程同步;
(6)使用信号量 Semaphore
25.syncronized和ReentrantLock都是“排他锁”,ReentrantReadWriteLock是“读写分离锁”
26.指令重排序和happens-before原则是难点,还得看看