线程安全

1.线程安全的概念:

想给出一个线程安全的确切定义是复杂的,但我们可以这样认为:

如果多线程环境下代码运行的结果是复核我们预期的,即在单线程环境应该得到的结果,则这个程序是线程安全的。

2.观察线程不安全:

创建一个静态变量COUNT,创建20个线程每个线程对COUNT加加一万次,预期结果是20万。

public class UnsafeThread {

    public static int COUNT;


    public static void main(String[] args) throws InterruptedException {
        //开启20个线程,每个线程对 COUNT++ 一万次
        //预期结果20万
        for (int i = 0; i < 20; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                            COUNT++;
                    }
                }
            }).start();
        }
        while (Thread.activeCount() > 1) {
            Thread.yield();
        }
        System.out.println(COUNT);
  	}
}

结果:低于20万,而且每次运行完得到的结果不同。
在这里插入图片描述

3.线程不安全的原因:

(1)原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还
没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
(2)可见性:
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线 程之间不能及时看到改变,这个就是可见性问题。
(3)有序性:
如果是在单线程情况下,JVM、CPU指令集会对代码执行顺序进行优化,以提高执行效率。这种做法叫做指令重排序。但是在多线程环境下,有可能上一个线程还没有把某个任务处理好,比如还没有给某个变量初始化就写回主内存了,而其他线程就直接用。这样就会产生问题了。

4.解决前面的不安全线程

public class SafetTest {
    private static int COUNT;

    //锁定的是 SafetTest 类对象
    public synchronized static void fun() {
        COUNT++;
    }

    public static void main(String[] args) {
        Object object = new Object();
        for (int i = 0; i < 20; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10000; i++) {
                        fun();
                        //COUNT++;
                        //this 指的是 Runnable 对象,每个线程都 new 一个。
                        synchronized (this) {
                            COUNT++;
                        }
                    }
                }
            });
            thread.start();
        }
        // > 1 使用 debug 方式;  > 2 使用 run 方法
        while (Thread.activeCount() > 2) {
            //线程让步,使得当前线程从运行态转变为就绪态。
            Thread.yield();
        }
        System.out.println(COUNT);
    }
}
synchronized的底层是使用操作系统的mutex lock实现的。
1.当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中 
2.当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内 存中读取共享变量
synchronized用的锁是存在Java对象头里的。
同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。

注意 synchronized 锁定的对象:
(1) synchronized 静态方法:锁定当前类对象
(2) synchronized 实例方法:锁定当前对象(this)
(3) synchronized(object):锁定object对象

5.volatile关键字

修饰的共享变量,可以保证可见性,部分保证有序性。
volatile不能保证原子性,对于变量的操作:
操作本身是原子性的,使用volatile就是线程安全的,否则是线程不安全。
volatile保证共享变量的可见性和有序性。
对于可见性,volatile修饰的变量直接从主内存中操作变量。从而保证共享变量的读取安全。
对于有序性,volatile修饰的变量禁止指令重排序。建立内存屏障,其他线程不可以中断volatile修饰变量的操作。

 class ThraedDemo {
    private volatile int n;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值