线程相关知识总结:
一、进程与线程之间的区别?
(I)进程是系统分配资源的最小单位,线程是系统调度的最小单位
(II)进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈
(III)由于统一进程的个线程之间共享内存和文件资源,可以不通过内核进行直接通信
(IV)线程的创建、切换及终止效率更高(线程的创建、切换、终止也比较耗时,这里只是相对于进程来说效率更高)
二、多线程的使用场景?(一般要考虑性能和安全 以提高性能和效率为目标)
(I)多个任务且任务量较大
(II)多个任务且会阻塞
三、多线程中的特性及状态转换?
(一)多线程的特性注意事项
- 线程的创建:申请系统创建一个线程,比较耗时 —>new Thread() > 线程的用户态和内核态切换 —>系统接口调用(如线程中的IO操作)
线程的时间片轮转调度—>系统调度- 线程数的确定
(1)计算公式:CPU核数/(1-阻塞系数) 某县城执行任务的总时间=执行任务的总阻塞时间+真正执行任务的时间(非阻塞) 阻塞系数=真正执行任务的时间/总时间
(2)计算密集型的任务:CPU核数/CPU核数+1 作为线程数 (阻塞系数=0)
(3)IO密集型的任务:使用计算公式(二)常见的API导致的状态转换
1.线程启动:start() ,系统调度CPU执行线程,注意run()只是任务的定义
- 线程阻塞:
(1)Thread.sleep(long) —>当前线程休眠
(2)t.join() / t.join(long) —>t线程加入当前线程,当前线程等待long或t线程执行完
(3)synchronized—>竞争所失败的线程,进入阻塞态 (竞争失败的线程不停的再阻塞态和运行态之间切换(被JVM唤醒再次竞争对象锁),如果竞争失败的线程数量很多,对系统的性能影响较大)
synchronized的使用前提:多线程对同一个共享变量的操作(更新)- 线程的中断:通过设置中断标志位让某个线程中断,而不是停止线程,是一个“建议“,是否中断由该线程代码决定
(三)线程状态转换
多线程案例 --单例模式 代码
class SingletonTest {
// //1.饿汉模式
// //一个类里提供一个类的实例对象
// private static SingletonTest instance = new SingletonTest();
// private SingletonTest(){}
// public static SingletonTest getInstance(){
// return instance;
// }
// //2.懒汉模式---单线程
// private static SingletonTest instance = null;
// private SingletonTest(){}
// public static SingletonTest getInstance(){
// if (instance == null) {
// instance = new SingletonTest();
// }
// return instance;
// }
// //3.懒汉模式---多线程,低性能
// private static SingletonTest instance = null;
// private SingletonTest(){}
// public synchronized static SingletonTest getInstance(){
// //第一次实例化之后,应该允许多线程并行并发执行获取同一个对象
// if (instance == null) {
// instance = new SingletonTest();
// } //第一次是写操作,之后全是读操作
// return instance;
// }
//4.懒汉模式---多线程,二次判断,高性能
private static volatile SingletonTest instance = null;
//volatile 保证可见性(工作内存和主存,每次操作都是在主存中操作),禁止指令重排序,不保证原子性(赋值操作本身不具备原子性)
//特别注意的非原子性代码:一行代码分解为多条指令(n++,n--,++n,--n,new对象【1.创建初始化内存空间2.new对象3.赋值给共享变量】,若是允许重排序)
//TODO volatile常见的使用场景:一般进行读写分离操作,提供性能
// 1.写操作不依赖共享变量,赋值是一个常量(依赖共享变量的赋值不是原子性操作)
// 2. 作用在读,写依赖于其他手段(加锁)
private SingletonTest(){}
public static SingletonTest getInstance(){
if (instance == null) {
synchronized (SingletonTest.class) { //竞争对象锁,多个线程使用同一个对象
if (instance == null) {
instance = new SingletonTest(); //不满足原子性
}
}
}
return instance;
}
}
分析:volatile不禁止指令重排序,会有什么问题?
我们将程序分解为如下步骤:
如果volatile不禁止指令重排序,则步骤4-2和4-3的顺序是无法确定的。假设有两个线程(线程A,线程B),A,B竞争对象锁,A竞争成功,进入第4步,并先执行了4-3而没有执行4-2,此时因为instance已经非null。这时候线程B执行到了4-1处,判断instance非null并将其返回使用,因为此时SingletonTest实际上还未初始化(引用指向了内存空间,保存的是还没有初始化完的无效对象),就会出错。
volatile不禁止指令重排序的作用:分解后的指令有volatile修饰的变量,那么这行(4-3,建立了内存屏障)禁止指令重排序
线程安全单例模式:双重校验锁
满足:
(1)性能:初始化对象操作完成之后(4-2完成),其他线程进入第一行(并行并发)
(2)线程安全:同时初始化操作(多个线程同时进入1)使用加锁操作再次校验是因为单例模式需要