1、synchronized 和 volatile 的区别?

1)volatile只能变量修饰符,而synchronized则可以修改代码块或方法。
2)volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;而synchronized加锁,会导致线程阻塞(互斥性)
3)synchronized既能够保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
4)synchronized会执行编译优化(对字节码重新排序),编译优化有可能导致运行过程中异常。而volatile禁止字节码重新排序,不会引发编译优化导致的异常。

原子性:一个操作不能被打断,要么全部执行完毕,要么不执行
可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变化)。

2、synchronized 和 Lock 的区别?

1)语法不同:
synchronized 是Java的关键字或修饰符,在jvm层面上,修饰方法或代码块。
Lock不是修饰符,是一个接口(加锁的工具类,该类提供很多方法加锁、释放锁等)tryLock() unLock()
2)释放锁不同():
synchronized 以获取锁的线程执行完同步代码,释放锁,且线程执行发生异常,jvm会自动让线程释放锁(不管成功还是失败,都会自动释放锁)
Lock必须手动在finally中释放锁(必须手动释放锁)
3)死锁情况不同(
):
synchronized 在发生异常时候会自动释放占有的锁,因此不会出现死锁
Lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
4)锁判断不同:
synchronized 无法判断当前线程的上锁状态
Lock可以判断当前线程的上锁状态 tryLock unLock isLock
5)锁类型不同:
synchronized是可重入 不可判断 非公平
Lock:是可重入 可判断 可公平

公平性(线程等待时间长,优先获取锁)

3、什么是死锁?怎么防止死锁?

死锁是指多个线程因竞争共享资源而造成的一种互相等待的僵局。

死锁的四个必要条件:

1)互斥:一次只有一个进程可以使用一个资源。其他进程不能访问已分配给其他进程的资源。
2)占有且等待:当一个进程在等待分配得到其他资源时,其继续占有已分配得到的资源。
3)非抢占:不能强行抢占进程中已占有的资源。
4)循环等待:存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需要的一个资源。

如何预防死锁?
1)加锁顺序(线程按顺序加锁)
2)加锁时限 (线程请求所加上期限,超时就放弃,同时释放自己占有的锁) 死锁检测
redis作为锁+过期时间(50s) Reddison

4、JDK的四种线程池创建线程的方式?实际开发中用哪个?

ExecutorService接口,JDK线程池接口!!!

1)固定线程数的线程池(newFixedThreadPool)
ExecutorService es = Executors.newFixedThreadPool(5);

这种线程池里面的线程被设计成存放固定数量的线程,具体线程数可以考虑为CPU核数*N(N一般为2),这种线程池适合用在稳定且固定的并发场景。

2)缓存的线程池(newCachedThreadPool)
ExecutorService es = Executors.newCachedThreadPool();

这是一个可以无限扩大的线程池。
适合处理执行时间比较小的任务
线程空闲时间超过60s就会被杀死,所以长时间处于空闲状态的时候,这种线程池几乎不占用资源
使用该线程池是,一定要注意控制并发的任务数,否则创建大量的线程可能导致严重的性能问题

3)单个线程的线程池(newSingleThreadExecutor)
ExecutorService es = Executors.newSingleThreadExecutor();

该线程池可以保证所有任务按照指定顺序执行,所以这个比较适合那些需要按序执行任务的场景

4)固定个数的线程池(newScheduledThreadPool)(延迟执行线程)
Executors.newScheduledThreadPool(5);

但实际开发中,我们比较少用上面的线程池实现类创建线程池,因为比较不好控制线程数量,会导致内存溢出OOM。
建议是ThreadPoolExecutor创建线程池,并且指定7个线程池参数。