线程安全概念
如果多线程环境下运行的结果符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的
线程不安全
- 举例
客户端检查还有1张票,将票卖出,还没有执行更新数据库,客户端B检查了票数发现大于0于是又卖了一次票然后A将票数更新到数据库这就出现了同一张票卖了两次
- 原子性
提供互斥访问同一时刻只能有一个线程对数据进行操作
(多行指令(一行代码可能会分解为多条指令执行),如果指令前后有依赖关系,不能插入影响我执行结果的指令,如果能狗插入就是没有原子性不能插入就是有原子性)
举例:n++有三条指令操作
- 从内存把数据读到CPU
- 进行数据更新
- 把数据写会到CPU
举例:我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还
没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样
就保证了这段代码的原子性了。
有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
- 可见性
一个主内存的线程被修改可以及时被其他线程观察到
(主内存:线程共享;工作内存:线程私有内存+CPU高速缓存/寄存器;主存共享数据的操作:从主存读取到工作内存,工作内存修改,然后写回主存)
举例:为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线
程之间不能及时看到改变,这个就是可见性问题。
- 有序性
如果两个线程不能从happens-before原则观察出来。那么就不能观察他们的有序性,虚拟机可以随意对他们进行重排序,导致其观察结果杂乱无序
重排序:
代码是这样的:
- 去前台取下 U 盘
- 去教室写 10 分钟作业
- 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑一次前台。这种叫做指令重排序
代码重排序会给多线程带来什么问题
刚才那个例子中,单线程情况是没问题的,优化是正确的,但在多线程场景下就有问题了,什么问题呢。可能快递是
在你写作业的10分钟内被另一个线程放过来的,或者被人变过了,如果指令重排序了,代码就会是错误的。
线程不安全讲解
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
COUNT++;
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
预期是20000
实际:小于20000
线程不安全解决
synchronized关键字: 同步锁或重量级锁(JAVA SE1.6之后胡引入l偏向锁和轻量级锁),具有可重入性
作用: 对某段代码加锁,让某段代码满足原子性,可见性有序性
synchronized的原子性,有序性,可见性
原理: 多个线程间同步互斥(一段代码在任意一个时间点只有一个线程执行:加锁,释放锁)
锁的对象不同分为对象锁和类锁
- 对象锁:
- 对于普通的同步方法锁的就是当前实例的对象
- 对于同步方法块,如果synchronized括号里配置的是类的实例对象,则锁的是配置的对象
- 类锁:(class 对象锁)
- 对于静态同步方法,锁的是当前类
- 对于同步方法块,如果synchronized括号里配置的是类的Class对象,则锁的是当前类
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
//加锁后结果就是20000
synchronized (Test4.class){
COUNT++;
}
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
synchronized加锁后20个线程同步互斥执行,有一个线程加锁执行时,剩余19个线程处于阻塞状态,当前运行线程执行完毕后解锁,剩余线程竞争抢占执行。
synchronized只有对同一个对象加锁才会产生同步互斥的作用
可重入性:同一个对象可以对同一个对象锁多次申请,基于重入计数器来实现
- synchronized 传入类对象-类名.class获取类对象
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
//加锁后结果就是20000
synchronized (clazz){
COUNT++;
}
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- synchronized方法传入实例对象
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Test4 st=new Test4();
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (st){
COUNT++;
}
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- 静态同步方法
public class Test4 {
private static int COUNT = 0;
//静态同步方法
public synchronized static void decrement(){
COUNT++;
}
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
//静态同步方法
decrement();
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- 实例同步方法
public class Test4 {
private static int COUNT = 0;
//实例同步方法
public synchronized void decrement(){
COUNT++;
}
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Test4 st=new Test4();
Thread[] threads = new Thread[20];
for(int i=0; i<20; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
//静态同步方法
st.decrement();
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- 拆分写法同理-sychronized必须锁同一对象才能实现同步互斥
- 一部分加锁一部分未加锁
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Test4 st=new Test4();
Thread[] threads = new Thread[20];
for(int i=0; i<19; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (st){
COUNT++;
}
}
}
});
}
for(int i=0; i<1; i++){
threads[19+i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
COUNT++;
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- 加锁同一对象
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Test4 st=new Test4();
Thread[] threads = new Thread[20];
for(int i=0; i<19; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (st){
COUNT++;
}
}
}
});
}
for(int i=0; i<1; i++){
threads[19+i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (st){
COUNT++;
}
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
- 加锁不同对象
public class Test4 {
private static int COUNT = 0;
public static void main(String[] args) throws InterruptedException {
//有一个变量COUNT=0;同时启动20个线程,每个线程循环1000次,每次循环COUNT++
//等着20个子线程执行完毕之后,在main线程打印COUNT(预期20000)
Class clazz=Test4.class;
Test4 st=new Test4();
Thread[] threads = new Thread[20];
for(int i=0; i<19; i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (st){
COUNT++;
}
}
}
});
}
for(int i=0; i<1; i++){
threads[19+i] = new Thread(new Runnable() {
@Override
public void run() {
for(int j=0; j<1000; j++){
synchronized (clazz){
COUNT++;
}
}
}
});
}
for(Thread t : threads){
t.start();
}
//让main线程阻塞等待所有的20个子线程执行完毕
for(Thread t : threads){
t.join();
}
System.out.println(COUNT);
}
}
synchronized对象锁之间的关系
- Class t=Test.class
- Test t1=new Test()
- Test t2=new Test()