多线程三大特性
原子性,可见性,有序性
线程安全:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都表现出正确的行为,那么就称为这个类是线程安全的
原子性(加锁)
synchronized 依赖jvm
修饰代码块时,作用于调用的对象,范围是大括号的代码
修饰方法时,作用于调用的对象,范围是整个方法
修饰静态方法时,作用于所有对象,范围是整个静态方法
修饰类,作用于所有对象,范围是括号括起来的部分
synchornized是不可中断锁
Lock是可中断锁
可见性
导致共享变量在线程间不可见的原因:
- 线程交叉执行
- 重排序结合线程交叉执行
- 共享变量更新后没有在工作内存与主内存之间及时更新
volatile的可见性通过加入内存屏障和禁止重排序优化来实现的
- 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量刷新到主内存
- 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量
有序性
Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到并发执行的正确性
原子性:atomic包,CAS算法,synchronized,lock
可见性:synchornized,volatile
有序性: happens-before
CPU的多级缓存
意义:
- 时间局限性:如果某个数据被访问,那么不久将来他很可能会被再次访问
- 看见局限性:如果某个数据被访问,那么他相邻的数据很快也可能被访问
Java内存模型
- Lock 作用于主内存的变量,把一个变量标识为一条线程独占状态
- unlock :作用于主内存的变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read 作用于主内存的变量 把一个变量值从主内存传输到线程的主内存中,以便随后的load动作使用
- load 作用于主内存 的变量 他把read操作从主内存中得到的变量值放入工作内存的变量副本中
- use 作用于工作内存的变量 把工作内存中的一个变量值传递给执行引擎
- assign 作用于工作内存的变量 他把一个从执行引擎接收到的值赋给工作内存的变量
- store 作用于工作内存的变量 把工作内存中的一个变量的值传送到主内存中 以便以后的write操作
- write 作用于主内存的变量 他把store操作从工作内存中一个变量的值传送到主内存的变量中
死锁
争夺资源,互相等待
四个必要条件
- 互斥
- 请求和保持
- 环路等待
- 不可剥夺条件
demo:
package cn.learnbyheart.deadLock;
public class DeadLock implements Runnable{
public int flag = 1;
private static Object o1 = new Object(),o2 = new Object();
public static void main(String[] args) {
DeadLock lock1 = new DeadLock();
DeadLock lock2 = new DeadLock();
lock1.flag = 1;
lock2.flag = 0;
new Thread(lock1).start();
new Thread(lock2).start();
}
@Override
public void run() {
System.out.println(flag);
if(flag == 1){
synchronized (o1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){
System.out.println("1");
}
}
}
if(flag == 0){
synchronized (o2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
System.out.println("0");
}
}
}
}
}
多线程并发最佳实践
- 使用本地变量
- 使用不可变类
- 最小化锁的作用域范围
- 使用Executor而不是new Thread
- 少用wait和Notify
- 使用blockingQueue实现生产消费模式
- 使用并发集合,而不是加了锁的同步集合(Collections.synchornizedXXX)
- 使用semaphore创建有界的访问
- 宁可使用同步代码块,也不要用同步方法
- 避免使用静态变量(最好用final型或者只读集合)