多线程实现方式
- 继承Thread类重写run方法,并通过调用.start()方法,启动线程
- 实现Runnable接口(Callable接口)重写run方法,Runnable接口的方式解决了Java单继承的局限,Runnable接口实现多线程比继承Thread类更加能描述数据共享的概念
- 如果所在的进程中有多个线程在同时运行,而这些线程可能会同时运行,如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,那么就是线程安全的
- 多线程的三个核心:原子性、可见性、顺序性
Volatile原理,能否代替锁
- JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存
- 保存内存的可见性,所有线程都能看到共享内存的最新状态
- 防止指令重排:为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序
- 使用场景:a. 对变量的写操作不依赖于当前值,b. 该变量没有包含在具有其他变量的不变式中
- 不能代替锁,volatile不能保证原子性
- CAS: 三个参数(V,A,B),一个当前内存值V、旧的预期值A、即将更新的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做
- CAS+volatile的非阻塞乐观锁的方式来降低同步锁带来的阻塞性能的问题,实现对共享变量的访问
线程的生命周期
- 线程通过new方法创建,调用start()方法,线程进入就绪态,等待系统的调度(时间片轮转调度),当系统调度,进入运行状态,正常结束或者异常退出,进程进入死亡状态
- 处于运行状态的线程若遇到sleep()方法,则线程进入睡眠状态,不会让出资源锁,sleep()方法结束,线程转为就绪状态,等待系统重新调度
- 处于运行状态的线程可能在等待IO,也可能进入挂起状态,IO完成,转为就绪状态
- 处于运行状态的线程yield()方法,线程转为就绪状态(yield只让给权限比自己高的)
- 处于运行状态的线程遇到wait() 方法(object的方法),线程处于等待状态,需要notify()/notifyALL()来唤醒线程,唤醒后的线程处于锁定状态,获取了“同步锁”之后,线程才转为就绪状态
- 处于运行的线程加synchronized锁变成同步操作,处于锁定状态,获取了“同步锁”之后,线程才转为就绪状态
sleep和wait区别
- sleep()方法导致了程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法的过程中,线程不会释放对象锁。
- 当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后,本线程才进入对象锁定池准备
Lock和Synchronized的区别
- L是接口,S是关键字
- S是在JVM层面上实现的,可以监控S的锁定,并在代码执行时出现异常,JVM会自动释放锁定,L是通过代码实现的,要保证锁一定会被释放,就必须将unLock()放到finally{}中
- L可以当等待锁的线程响应中断,S不行,使用S时等待的线程会一直等待下去,不能响应中断
- L锁可以通过多种方法来尝试获取锁,S不可以
- L可以提高多个线程进行读写操作的效率
Synchronized的原理
- Java虚拟机中的同步(Synchronization)是基于进入和退出管程(Monitor)对象实现,无论是显式同步(有明确的monitorenter和monitorexit指令)还是隐式同步(依赖方法调用和返回指令实现的)都是如此
各种锁
在所有的锁都启用的情况下线程进入临界区时会先去获取偏向锁,如果已经存在偏向锁了,则会尝试获取轻量级锁,启用自旋锁,如果自旋也没有获取到锁,则使用重量级锁,没有获取到锁的线程阻塞挂起,直到持有锁的线程执行完同步块唤醒他们
- 重排序:通常是编译器或运行时环境为了优化程序性能而采取的对指令进行重新排序执行的一种手段,编译器重排序和运行期重排序,分别对应编译时和运行时环境
- 自旋锁:持有锁的线程能在很短时间内释放锁资源,那些等待竞争锁的线程就不需要进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁
- 偏向锁:Java6引入的一项多线程优化,在同步锁只有一个线程时,是不需要触发同步的,会给线程加一个偏向锁,遇到其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁
- 轻量级锁:轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁
- 可重入锁:同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁
- 公平锁:在获取锁之前会先判断等待队列是否为空或者自己是否位于队列头部,该条件通过才能继续获取锁
- 非公平锁:与公平锁的区别在于新获取锁的线程会有多次机会去抢占锁,但如果被加入了等待队列后则跟公平锁没有区别
- 乐观锁:认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为其他线程不会修改,写数据时先读出当前版本号,比较跟上一次的版本号,如果一样则更新,如果不一样则要重复读-比较-写的操作,通过CAS实现
- 悲观锁:认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,如Synchronized