线程安全

并发和并行

并发可以看作是假的并行。
CPU执行线程是一个个时间片执行的,看起来像是并行。

竞态

竞态的概念:程序运行的结果不固定,依赖相对时间顺序、线程的交替。
竞态的模式:1、read-modify-write

i++

2、check-then-act

if(condition){
    ……
}else{
   ……
}

线程安全的定义

如果一个程序,单线程运行正常,不做任何修改,多线程也运行正常,不出错,可以称为线程安全。

竞态---->非线程安全
线程安全----->非竞态

线程安全:原子性

某个线程对变量的操作,对于其他线程来说,要么已执行,要么未执行,不可分割。

Java中可以通过(1、锁。2、CAS)保证原子性。
JLS(Java Language Specification)保证对byte、boolean、short、char、float、int和引用类型变量的写操作都是原子的,long和double除外。volatile修饰long和double变量可以保证写操作的原子性,但是无法保证竞态的2种模式(read-modify-write和check-then-act)的原子性。
对所有类型的读都是原子性操作。

2个原子操作相加的复合操作,不一定是原子操作。

线程安全:可见性

可见性的定义:某个线程对变量的更新,其他线程是否能看见。
谈到可见性必须了解Java内存模型,参见:

https://www.hollischuang.com/archives/2550

不同线程是不能直接共享内存的,每个线程都有自己的工作内存。不同线程通过将本地内存中的变量和主内存来回刷新,来交互数据。

volatile关键字可以保证可见性。

package com.sss.concurrency.visibility;

/*
 * 一、volatile 关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
 *                    相较于 synchronized 是一种较为轻量级的同步策略。
 * 
 * 注意:
 * 1. volatile 不具备“互斥性”
 * 2. volatile 不能保证变量的“原子性”
 */
public class TestVolatile {
    
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        
        while(true){
            if(td.isFlag()){
                System.out.println("------------------");
                break;
            }else{
                /**
                 * 如果放开下面的IO输出,主线程可能获得休息时间,从主存中获得flag的最新值,程序停止。
                 * 如果没有下面的IO输出,那么主线程就没有机会从主存中刷新读取flag的值。
                 */
                //System.out.println("循环还在继续");
            }
        }
        
    }

}

class ThreadDemo implements Runnable {
    private /*volatile*/ boolean flag = false;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("子线程已经将flag置为" + flag);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

可见性问题与CPU的核数没关系,就算是单CPU,也会出现可见性问题。

JLS保证:父线程在启动子线程之前,对共享变量的更新对于子线程来说是可见的。示例:

package com.sss.concurrency.visibility;

import java.util.concurrent.TimeUnit;

public class VisibilityTest1 {
    //线程间的共享变量
    static int data = 0;
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(data);
        });
        //在子线程启动前更新data=1
        data = 1; //A
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //在子线程启动后更新data=2
        //data=2;//B
    }
}

如果注释掉B,无论运行多少次,一定输出1。
如果不注释B,运行结果可能是1,可能是2。

JLS保证:一个线程终止后,该线程对共享变量的更新对于调用该线程的join方法的线程而言,是可见的。示例:

package com.sss.concurrency.visibility;

import java.util.concurrent.TimeUnit;

public class VisibilityTest2 {
    // 线程间的共享变量
    static int data = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data = 1;
        });
        thread.start();
        thread.join();
        System.out.println(data);
    }
}

运行结果一定是1。

线程安全:有序性

重排序:编写的代码,经过CPU、编译器等优化,实际执行顺序和编写顺序不相同。

Object obj = new Object();

理想的执行顺序可能是:
1、给new Object()分配内存空间。
2、调用Object的构造方法。
3、obj变量指向刚刚划分好的内存空间。
实际的执行顺序可能是1–>3—>2

重排序也要遵循一定的规则:
1、as-if-serial(貌似串行语义)
只从单线程角度保证重排序后,不影响程序准确性,不保证多线程正确性。下面这些语句前后存在依赖关系,不会被重排序。

x=1;y=x+1;
y=x; x=1;
x=1;x=2;
if(x=1){
    y=2;
}

可以被重排序。

线程调度

线程调度有公平和非公平之分。非公平调度策略优点是吞吐率大,缺点是可能造成不同线程活跃度相差太大,导致线程饥饿现象。公平调度策略有点是不会导致饥饿现象,缺点是吞吐率较小。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值