多线程相关知识点
进程是操作系统中资源分配的最小单位;线程是操作系统中任务分配的最小的单位。
1.Java中的多线程的实现方式
继承Thread
实现Runnable、Callable
线程池(推荐)
2.多线程常用操作方法
sleep():线程休眠,运行态到阻塞态,不会释放锁,立即交出CPU
yield():线程让步,运行态到就绪态,不会释放锁,交出CPU时间不确定,由系统调度,只会让拥有相同优先级的线程由获取CPU的机会。
join():当前线程等待另一线程执行完毕后再恢复执行,运行态到阻塞态(谁调用谁阻塞),会释放锁。
3.多线程的等待与唤醒极致
wait()[等待资源]/notify():synchronized
要使用wait/notify,必须在同步方法或同步代码块中使用
Object及其子类的每个对象都有两个队列
同步队列:
获取该对象锁失败的线程进入同步队列(先进先出)
等待队列:
调用wait()的线程进入等待队列(等待被notify【后置入同步队列】)
wait():运行态到阻塞态,会释放对象锁
notify():阻塞态到就绪态,会释放对象锁
4.守护线程
Java线程只有两类,用户线程和守护线程。创建的线程默认都是用户线程,包括主线程。守护线程:后台线程,只有当前JVM进程中的最后一个用户线程终止,守护线程会随着JVM一同停止。GC线程是一个典型的守护线程。
setDaemon将用户线程置为守护线程。
同步问题:保护对象是谁?锁是谁?搞明白几把锁?
加锁:任意一个时刻只有一个线程可以使用
5.JVM
JVM 内存模型(JMM):并发程序
描述共享变量(类的成员变量、静态变量)如何存储
工作内存(线程的内存):
变量在线程中的操作(读写)必须在工作内存中进行,工作内存中保存了所有变量的副本
主内存:
所有的变量必须在主内存中存储
6.线程安全三个特性:
原子性:
【事务的操作是整体】一组操作要么同时发生,要么一个都不发生。
【基本数据类型的读写操作都属于原子性操作】(只有一个读或只有一个写)
可见性:
【事务提交后从一个状态到另一状态】某一线程对于变量的修改对于其他的线程而言是立即可见的。可见性:synchronized(lock)、volatile、final
有序性:
在单线程场景下,代码执行顺序就是代码书写顺序;在多线程场景下,所有代码都是乱序的。
7.使用synchronized解决同步问题
同步代码块:synchronized(){对象}【保护对象代码块,锁的是对象】
对象有2种方式:(1)任意对象(2)类.class对象【类加载时反射对象,反射对象只有一个】
同步方法:(1)修饰成员方法【锁的是当前对象this】
(2)修饰类方法【锁的是当前类的反射对象】
8.synchronized底层实现:对象Monitor机制
任意Object及其子类对象的内部在JVM机制中都附加Monitor,获取一个对象锁,实际上就是获取该对象的Monitor。
当一个线程尝试获取该对象的Monitor时:
若此时Monitor的值为0,该对象未被任何线程获取,当前的线程获取为Monitor,将持有线程置为当前线程,Monitor值加1;
若此时Monitor的值不为0,此时Monitor已被该线程持有。
(1)若当前线程恰好是持有线程,Monitor值再次加1,当前线程继续进入同步块(锁的可重入)
(2)若持有线程不是当前线程,当前线程会进入阻塞状态(同步队列)【即就是获取锁失败】等待Monitor值减为0
加锁:Monitorenter+1
解锁:Monitorexit-1
任意时刻只有当Monitor的值为0表示无锁状态。
9.CAS:Compare And Swap 无锁保证线程安全
CAS(O、V、N)
V:主内存存放实际变量值
O:当前线程认为的变量值
N:希望将变量替换的值
例子:主内存 ticket=1
线程1: ticket=0;cas(1,1,0) O == V->认为此时没有线程修改主内存的值。
线程2: ticket=0;cas(1,0,0) O !=V
->认为此时已有线程修改主内存的值,替换失败,返回主内存的最新值再次重试。
线程3:ticket=1;
当O==V时,认为此时没有线程修改变量值,成功将N值替换回主内存。
当O!=V时,认为已有线程修改变量值,替换失败,返回主内存的最新值再次重试。
10.ABA问题:添加版本号
num=0;
线程1:cas(0,0,1)主内存为1 num.1
线程2:cas(1,1,0)主内存为0 num.2
线程3:cas(0,0,5) 主内存为5【线程2、线程3并行】 num.0
11.偏向锁->轻量级锁->重量级锁(JDK1.6之前,synchronized就是重量级锁)
重量级锁(悲观锁):
获取Monitor失败的线程进入同步队列,状态为阻塞态
【只要尝试获取锁,一定有其他线程抢】
偏向锁(乐观锁):
只有一个线程来回进入同步块,直接将加锁与解锁的过程省略,每次进入同步块之前,只是判断一下同步块线程是否为当前线程。
【只要尝试获取锁,一定没有其他线程抢】
轻量级锁:
不同时刻有不同的线程进入同步块,每次线程进入同步块时都需要加锁与解锁。重量级锁:同一时刻有不同的线程进入同步块【只有一个线程进入,其他阻塞】
随着竞争的不断升级,锁也会不断地升级,锁也不会降级。
自适应自旋:重量级锁的优化
获取锁失败的线程不会立即阻塞,而是在CPU空跑一段无用代码,若在此时间段成功获取锁,则下次再获取锁失败时,空跑时间适当延迟;否则下次空跑时间缩短。
锁粗化:
将多次连续的加减锁过程粗化为一次大的加锁与解锁的过程,减少无用的加减锁过程,提高效率。
public class Test{
static StringBuffer sb = new StringBuffer();
public static void main(String[] args){
sb.append("hello");
sb.append("word");
sb.append("bit");
}
}
锁消除:
当变量为线程私有变量时,将原先方法上的synchronized消除掉。
public class Test{
public static void main(String[] args){
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append("word");
sb.append("bit");
}
}