什么是并发?什么是并行?
- 并发:同一时间段,多个任务都在执行(单位时间内不一定能同时执行);
- 并行:单位时间内多个人任务同时执行;
为什么要使用多线程?
- 计算机原理来讲:我们知道线程是进程下的最小的执行单位,线程间的切换和调度成本要比进程小得多,另外现在的电脑都是多cpu配置,那么就可以同时运行多个线程,减少了线程间上下文的切换,从而降低了进程运行的开销
- 现在互联网的发展趋势,现在的系统需求的并发量在不断的提升,多线程这块对高并发系统来说说基础的一部分,处置好多线程可以大大提高系统的心能和整体的并发能力
- 从计算机底层来讲:
-
- 单核时代:但核心使用多线程主要是为了提高CPU和IO设备的综合利用率,Excemple:当只有一个线程时会导致CPU计算时,IO设置空闲,进行IO操作时,CPU空闲,这样导致CPU和IO设置的利用率只能达到50%;如果有两个线程同时运行,线程A在进行CPU计算时,线程B进行IO操作,这样设备的利用率可以达到较高的利用率;
- 多核时代:多线程可以提高CPU的利用率,在计算复杂任务时,可以让多个CPU同时利用,提高计算效率
- 多线程带来的问题
-
- 内存泄露、上下文切换、死锁、还有受限于硬件和软件的资源闲置问题
线程的生命周期和状态切换
- 线程的状态
名称 | 说明 |
NEW | 初始状态,新建线程,没有调用start()方法 |
RUNNABLE | 运行状态,java线程将操作系统中的就绪和运行两种状态统称为“运行中” |
BLOCKED | 阻塞状态,线程阻塞于锁 |
WAITING | 等待状态,线程进入等待状态,需要等待其他的线程通知或者中断。。 |
TIME_WAITING | 超时等待状态,不同于WAITING,是可以指定时间自行返回的 |
TERMINATED | 终止状态,当前线程运行结束。 |
什么是上下文切换?
- 多线程编程中线程数量>cpu核心数,而在同一时刻一个cpu只能处理一个线程的任务,为了让这些线程任务都能够有效执行,cpu的策略就是给每个线程都分配单位时间片来执行。当一个线程时间片用完后,就会处于就绪状态,转给其他线程使用,这就是一次上下文切换;
- 简言之:任务从保存到在加载的过程就是一次上下文切换;
什么是线程死锁?如何避免死锁?
认识死锁
- 多个线程阻塞时,线程中的一个或者多个都在等待某些资源被释放,线程被无限期的阻塞,程序舅不能正常终止了。
- 产生死锁的四个条件:
-
- 互斥条件:该资源任意一个时刻只有一个线程占用
- 请求与保持条件:一个进程因请求资源阻塞,对已获得的资源保持不放
- 不剥夺条件:线程获得的资源在未使用完之前不能被其他的线程强行剥夺,只有自己使用完毕后才能释放资源
- 循环等待条件:进程中的循环一直持有资源
避免死锁
针对产生死锁的条件,分析如何来避免产生死锁
- 破坏互斥条件:使用锁就是防止互斥的(临界资源需要互斥访问)
- 破坏请求与保持条件:一次清申请所有资源
- 破坏不剥夺条件:占有部分资源时去申请其他的资源,如果申请不到要主动释放资源
- 破坏循环等待条件:循环申请资源时,释放资源要反序;
sleep() 和 wait()方法的区别和共同点
- 主要区别:sleep不会释放锁,wait会释放锁
- 两者都可以暂停线程的执行
- wait通常用于线程间交互/通信,sleep通常用于暂停执行
- wait()方法被调用后,线程不会自动苏醒,需要别的线程用同一个对象上的notify()或者notifyAll()方法。sleep()方法执行完成后,线程会自动苏醒,或者使用wait(long)超时后自动苏醒。
start()方法会执行run()方法,为什么不直接调run()方法?
调用start()方法可启动线程并使线程进入就绪状态,而run()方法只是thread的一个普通方法调用,还是在主线程里执行。
synchronized关键字
简单了解
synchronized关键字解决了多个线程间访问资源的同步性,可以保证被修饰的方法或代码块可以再任意时刻只有一个线程执行。
synchronized属于重量级锁,效率较低,jdk1.6前线程是映射到操作系统的原生线程之上的;1.6后jvm对synchronized做了优化,如自旋锁,锁消除,锁粗化,偏向锁。。
使用方式
- 修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获取当前对象实例的锁
- 修饰静态方法:是对当前类加锁,会作用于所有该类的实例,因为静态成员不属于任何一个实例对象,而是类成员(所有类实例共享的)。线程A的一个实例对象的非静态synchronized方法,调用线程B的需要调用该实例的的 静态synchronized方法,是不会发生互斥的;【静态synchronized方法锁的是当前类,而非静态synchronized方法锁的当前实例】
- 修饰代码块:指定加锁对象,给定对象加锁,进入同步代码块前需要获取该对象的锁;
总体来说:synchronized关键字加到static静态方法或者synchroinzed(class)代码块上都是对类加锁;加到实例方法上面,是在对象实例上加锁,尽量不要用synchronized(String str) ,字符串常量池具有缓存功能;
- 双重校验实现单例模式
public class Singleton { private volatile static Singleton uniqueInstance; private Singleton() { } public synchronized static Singleton getUniqueInstance() { //先判断对象是否已经实例过,没有实例化过才进⼊加锁代码 if (uniqueInstance WX null) { //类对象加锁 synchronized (Singleton.class) { if (uniqueInstance WX null) { uniqueInstance = new Singleton(); } } } return uniqueInstance; } }
uniqueInstance采用了volatile关键字修饰:禁止指令重排;
synchronized关键字的原理
如果用javap解析带有synchronized字符的类,
- synchronized 同步代码块
就可以看到synchronized同步代码块的实现是使用了monitorenter/monitorexit指令的,标识同步代码块的开始、结束位置;当进入同步代码块时,监视器monitor对象的头中的计数器+1,代码快结束计数器-1=0,标识锁的释放;
- synchronized 修饰方法
修饰方法时并没有monitorenter/monitorexit,使用的是ACC_SYNCHRONIZED标识,标识这是一个同步方法,JVM通过通过该标识来判断是否声明为同步方法,从而执行相应的同步调用(JVM层控制)。
synchronized jdk1.6前后有哪些优化?
- jdk1.6后对锁进行了大量的优化,包括偏向锁,轻量级锁,自旋锁,适应性自旋锁,锁清除、锁粗化等技术来减少锁操作的开销。
- 锁存在的四种状态:无锁-> 偏向锁-> 轻量级锁-> 重量级锁,锁状态会随着竞争的激烈而逐渐升级,锁的升级是不可降级的
- 关于锁状态变化介绍,后面再补充:参考
synchronized和ReentrantLock的区别
- 两者都是可重入锁
- synchronized依赖于jvm,而reentrantLock依赖于API
- ReentrantLock相对于synchronized多了一些高级功能
volatile关键字
--未完待续 20200802