1.并发三要素:原子性、可见性、有序性;
出现线程安全问题的原因:
-
线程切换带来的原子性问题
-
缓存导致的可见性问题
-
编译优化带来的有序性问题
解决办法:
- JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
- synchronized、volatile、LOCK,可以解决可见性问题
- Happens-Before 规则可以解决有序性问题
任务从保存到再加载的过程就是一次上下文切换。
垃圾回收线程就是一个守护线程。
2.死锁产生的四个条件:
- 互斥、
- 请求与保持:一次性申请所有的资源
- 不剥夺:如果申请不到,可以主动释放它占有的资源。尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 循环等待:靠按序申请资源来预防
3.创建线程有四种方式:
- 继承 Thread 类;
- 实现 Runnable 接口;
- 实现 Callable 接口;
- 使用 Executors 工具类创建线程池
1.start和run方法的区别?
start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。
2.线程的生命周期及五种基本状态?
新建、就绪(可运行)、运行、阻塞、结束;
3.Java 中用到的线程调度算法是什么?
有两种调度模型:分时调度模型和抢占式调度模型。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃 CPU。
4.sleep() 和 wait() 有什么区别?
两者都可以暂停线程的执行
- 类的不同:sleep() 是 Thread线程类的静态方法,wait() 是 Object类的方法。
- 是否释放锁:sleep() 不释放锁;wait() 释放锁。
- 用途不同:Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
- 用法不同:wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
5.为什么线程通信的方法 wait(), notify()和 notifyAll()被定义在 Object 类里?
一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?
6.Thread 类中的 yield 方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
7.线程的 sleep()方法和 yield()方法有什么区别?
(1) sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
(2) 线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性,通常不建议使用yield()方法来控制并发线程的执行。
Java 中 interrupted 和 isInterrupted 方法的区别?
Java 如何实现多线程之间的通讯和协作?
可以通过中断 和 共享变量的方式实现线程间的通讯和协作
Java中线程通信协作的最常见的两种方式:
一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
线程间直接的数据交换:
三.通过管道进行线程间通信:1)字节流;2)字符流
8.什么是线程同步和线程互斥,有哪几种实现方式?
线程间的同步方法大体可分为两类:用户模式和内核模式。
用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
实现线程同步的方法
-
同步代码方法:sychronized 关键字修饰的方法
-
同步代码块:sychronized 关键字修饰的代码块
-
使用特殊变量域volatile实现线程同步:volatile关键字为域变量的访问提供了一种免锁机制
-
使用重入锁实现线程同步:reentrantlock类是可冲入、互斥、实现了lock接口的锁他与sychronized方法具有相同的基本行为和语义
9.在 Java 程序中怎么保证多线程的运行安全?
- 方法一:使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
- 方法二:使用自动锁 synchronized。
- 方法三:使用手动锁 Lock。
10.finalize()方法什么时候被调用?析构函数(finalization)的目的是什么?
1)垃圾回收器(garbage colector)决定回收某对象时,就会运行该对象的finalize()方法;
finalize是Object类的一个方法,该方法在Object类中的声明protected void finalize() throws Throwable { }
在垃圾回收器执行时会调用被回收对象的finalize()方法,可以覆盖此方法来实现对其资源的回收。注意:一旦垃圾回收器准备释放对象占用的内存,将首先调用该对象的finalize()方法,并且下一次垃圾回收动作发生时,才真正回收对象占用的内存空间
2)GC本来就是内存回收了,应用还需要在finalization做什么呢? 答案是大部分时候,什么都不用做(也就是不需要重载)。只有在某些很特殊的情况下,比如你调用了一些native的方法(一般是C写的),可以要在finaliztion里去调用C的释放函数。
11.
单例模式了解吗?来给我手写一下!给我解释一下双重检验锁方式实现单例模式的原理呗!”
双重校验锁实现对象单例(线程安全)
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;
}
}
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。
12.
什么是自旋?
既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更好的策略。
13.
synchronized、volatile、CAS 比较
(1)synchronized 是悲观锁,属于抢占式,会引起其他线程阻塞。
(2)volatile 提供多线程共享变量可见性和禁止指令重排序优化。
(3)CAS 是基于冲突检测的乐观锁(非阻塞)
- 首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;
- synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。
- synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。
- 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
14.
volatile 关键字的作用?
Java 提供了 volatile 关键字来保证可见性和禁止指令重排。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
从实践角度而言,volatile 的一个重要作用就是和 CAS 结合,保证了原子性,详细的可以参见 java.util.concurrent.atomic 包下的类,比如 AtomicInteger。
15.
synchronized 和 volatile 的区别是什么?
-
volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。
-
volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改可见性和原子性。
-
volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。
-
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
16.
synchronized是悲观的非公平的可重入锁。
17.
CAS会有ABA问题,解决办法就是加版本校验;从 Java1.5 开始 JDK 的 atomic包里提供了一个类 AtomicStampedReference 来解决 ABA 问题。
CAS原理就是一个乐观锁,从内存地址v中取出值与预期值A比较,如果相等,则将值修改为B,否则更新失败(或者循环等待)。
AQS原理就是当线程访问共享资源的时候,如果资源空闲则将资源交给线程,并把资源设置为锁定状态,如果共享资源被占用,则将线程加入一个CLH(虚拟双向队列中)(FIFO)等待资源释放。
private volatile int state;//共享变量,使用volatile修饰保证线程可见性。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。
不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。AQS底层使用了模板方法模式。
18.
饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
Java 中导致饥饿的原因:
1、高优先级线程吞噬所有的低优先级线程的 CPU 时间。
2、线程被永久堵塞在一个等待进入同步块的状态
3、线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的 wait 方法)
19.
锁共有4种状态,级别从低到高依次为:无状态锁,偏向锁,轻量级锁和重量级锁状态
20.
ThreadLocal与ThreadPool的区别?
Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。(但有内存泄漏的风险)ThreadLocalMap
中使用的 key 为 ThreadLocal
的弱引用,而 value 是强引用。(每次使用完ThreadLocal,都调用它的remove()方法,清除数据。)
使用 ThreadPoolExecutor 可以创建自定义线程池。
21.
线程池中 submit() 和 execute() 方法有什么区别?
接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有
异常处理:submit()方便Exception处理
22.
ThreadPoolExecutor构造函数重要参数分析
ThreadPoolExecutor
3 个最重要的参数:
corePoolSize
:核心线程数,线程数定义了最小可以同时运行的线程数量。maximumPoolSize
:线程池中允许存在的工作线程的最大数量workQueue
:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,任务就会被存放在队列中。
ThreadPoolExecutor
其他常见参数:
keepAliveTime
:线程池中的线程数量大于corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime
才会被回收销毁;unit
:keepAliveTime
参数的时间单位。threadFactory
:为线程池提供创建新线程的线程工厂handler
:线程池任务队列超过 maxinumPoolSize 之后的拒绝策略