Java高并发架构之并发编程-002

多线程并发编程 并发三大要素

一、原子性

原子,一个不可再被分割的颗粒。原子性,指的是一个或多个不能再被分割的操作。

int i = 1; // 原子操作
i++; // 非原子操作,从主内存读取 i 到线程工作内存,进行 +1,再把 i 写到主内存。

虽然读取和写入都是原子操作,但合起来就不属于原子操作,我们又叫这种为“复合操作”。

我们可以用synchronized 或 Lock 、原子变量类来把这个复合操作“变成”原子操作

案例演示:

public class MultiThread3Factor implements Runnable {
    // Atomicity
    /*    一、原子性

    原子,一个不可再被分割的颗粒。原子性,指的是一个或多个不能再被分割的操作。

    int i = 1; // 原子操作
    i++; // 非原子操作,从主内存读取 i 到线程工作内存,进行 +1,再把 i 写到主内存。

    虽然读取和写入都是原子操作,但合起来就不属于原子操作,我们又叫这种为“复合操作”。

    我们可以用synchronized 或 Lock 来把这个复合操作“变成”原子操作。
    */

    private int i = 0;

    private  void increase() {
        i++;
    }

    private synchronized void increase1() {
        i++;
    }


    Lock mLock = new ReentrantLock();

    private void increase2() {
        mLock.lock();
        try {
            i++;
        } finally {
            mLock.unlock();
        }
    }

    AtomicInteger mAtomicInteger = new AtomicInteger(0);

    private void increase3() {
       i = mAtomicInteger.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        MultiThread3Factor runnable = new MultiThread3Factor();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(runnable.i);
    }
    
    @Override
    public void run() {
        for (int i =0; i < 2000; i++) {
            increase3();
        }
    }

}

执行效果

二、可见性

当多线程访问某一个(同一个)变量时,其中一条线程对此变量作出修改,其他线程可以立刻读取到最新修改后的变量。

 public static void main(String[] args) throws InterruptedException {

        // 线程 1 执行
        Thread n = new Thread(new Runnable() {
            @Override
            public void run() {
                i++;
            }
        });
        n.start();

        // 线程 2 执行
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.print("线程2: i=" + i);
            }
        }).start();

    }

 

即使是在执行完线程里的 i++ 后再执行线程 2,线程 2 的输入结果也会有 2 个种情况,一个是 0 和 1。

因为 i++ 在线程 1(CPU1)中做完了运算,并没有立刻更新到主内存当中,而线程 2(CPU2)就去主内存当中读取并打印,此时打印的就是 0。

synchronized和Lock能够保证可见性。

另外volatile关键字也可以解决这个问题

案例2

static boolean stop = true;
    public static void main(String[] args) throws InterruptedException {

        // 线程 1 执行
        Thread n = new Thread(new Runnable() {
            @Override
            public void run() {
                i++;
            }
        });
        n.start();

        // 线程 2 执行
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (stop){
                    i++;
                }
                System.out.print("i=" + i);
            }
        }).start();
        TimeUnit.MILLISECONDS.sleep(1000);
        System.out.println("stop 线程执行....");
        stop = false;
    }


三、有序性

我们都知道处理器为了拥有更好的运算效率,会自动优化、排序执行我们写的代码,但会确保执行结果不变。

例子:

int a = 0; // 语句 1
int b = 0; // 语句 2
i++; // 语句 3
b++; // 语句 4

这一段代码的执行顺序很有可能不是按上面的 1、2、3、4 来依次执行,因为 1 和 2 没有数据依赖,3 和 4 没有数据依赖, 2、1、4、3 这样来执行可以吗?完全没问题,处理器会自动帮我们排序。(没有数据依赖的语句可能会被优化执行顺序

在单线程看来并没有什么问题,但在多线程则很容易出现问题。

再来个例子:

线程2需要依赖线程1初始化一些数据后才能正常执行。

// 线程 1
init();
inited = true;

// 线程 2
while(inited){
	work();
}

init(); 与 inited = true; 并没有数据的依赖,在单线程看来,如果把两句的代码调换好像也不会出现问题。

但此时处于一个多线程的环境,而处理器真的把这两句代码重新排序,那问题就出现了,若线程 1 先执行 inited = true; 此时,init() 并没有执行,线程 2 就已经开始调用 work() 方法,此时很可能造成一些奔溃或其他 BUG 的出现。

synchronized和Lock能确保原子性,能让多线程执行代码的时候依次按顺序执行,自然就具有有序性。

而volatile关键字也可以解决这个问题,volatile 关键字可以保证有序性,让处理器不会把这行代码进行优化排序。

总结

在进行多线程编程时要时刻保持线程安全的问题。主要关注这3个核心要素的处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Coder_Boy_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值