线程安全.

线程不安全的原因

如创建两个线程,进行++运算,使第一个线程自增500次,第二个线程也自增500次,预期的结果为1000,但实际自增的结果是无法预知的。这是为什么呢?

public class TestDemo {
    static class Count{
        public int count=0;
        void increase() {
            count++;
        }
    }


    public static void main(String[] args) throws InterruptedException {
        final Count counter=new Count();

        Thread t1=new Thread() {
            @Override
            public void run() {
                for (int i = 0; i <500 ; i++) {
                    counter.increase();
                }
            }
        };

        Thread t2=new Thread() {
            @Override
            public void run() {
                for (int i = 0; i <500 ; i++) {
                    counter.increase();
                }
            }
        };

        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(counter.count);
    }
}

在这里插入图片描述
像这种无法预知的结果是我们在写代码时最害怕的,那么为什么会出现这样的情况,大概率与并发执行有关,由于多线程是并发执行的,导致代码结果不准确,我们称这样的情况为“线程不安全”。
下面我们分析上边代码具体的执行过程:
count++,可以分为三步骤:

  1. 把内存中的值读到CPU中 LOAD
  2. 执行++操作 ADD
  3. 把CPU的值写回到内存中 SAVE

由于线程的调度是随机的,下面我们以其中一种情况为例:
在这里插入图片描述
像上图执行的过程就存在线程安全,执行了两次++预期结果为3但结果是2。
操作系统调度线程的时候是“抢占式执行”某个线程什么时候上CPU什么时候切换出CPU完全不确定。因此两个线程执行的具体的顺序是完全不可预测的。

线程之间抢占式执行

抢占式执行导致两个线程里面的操作的先后顺序无法确定,这样的随机性是导致线程安全的根本原因。

多个线程修改同一个变量

一个线程修改同一个变量,不存在线程安全问题。
多个线程读取同一个变量,不存在线程安全问题。
多个线程修改不同的变量,不存在线程安全问题。

为避免线程安全问题,就可以尝试变换代码组织形式。

原子性

像++这样的操作,分为三个步骤,就是一个“非原子”的操作。
像=操作,本质上是一个步骤,就是“原子”操作。

内存可见性

一个线程修改一个线程读取,由于编译器的优化会把中间一些SAVE和LOAD操作省略掉此时读的线程可能就是未修改的结果。

指令重排序

与编译器的优化有关

synchronized关键字

synchronized的特性

1.互斥
线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象synchronized就会阻塞等待。
进入synchronized修饰的代码块相对于加锁。
退出synchronized修饰的代码块相对于解锁。
synchronized需要指定一个具体的加锁对象。

  • 修饰非静态方法,加锁对象是this。
  • 修饰静态方法,加锁对象就是当前类对象
  • 修饰代码块,加锁对象通过()来指定

2.刷新内存,保证内存可见性
在synchronized内部如果要访问变量,就会保证一定能操作内存(禁止优化)
3.可重入性
synchronized对同一个线程可重复加锁,不会出现把自己锁死的问题。
如上边代码加上synchronized后就不存在线程安全,结果就是正确的。
在这里插入图片描述

volatile关键字

volatile修饰变量保证“内存可见性”,不保证“原子性”

wait和notify

wait:等待  notify:通知
用来协同多个线程之间的执行顺序。

wait

  • 让当前线程阻塞等待(让当前线程的PCB从就绪队列拿到等待队列中)并准备接受通知。
  • 释放当前锁。使用wait/notify,必须搭配synchronized。需要先获取到锁。
  • 满足一定的条件被唤醒时,重新尝试获取这个锁。

notify

  • 在synchronized中使用
  • notify一次唤醒一个线程(随机唤醒)
  • notifyAll一次唤醒所有线程

wait和sleep的区别

  • sleep操作是指一个固定的时间来阻塞等待。wait既可以指定时间也可以无线等待。
  • wait唤醒可以通过notify或者interrupt或者时间到唤醒。sleep只能时间到唤醒。
  • wait主要用途协调线程之间的先后顺序sleep不适合这个的场景,sleep只是单纯的让线程休眠不涉及多个线程配合。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值