Java并发编程实战---线程安全性

什么叫做线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么称这个类是线程安全的。

那么什么叫做正确的行为?所见即所知We know it when we see it。

原子性

什么叫原子性?举个例子,假如现在有一个类中有一个计数器方法如下:

public class TestCount {
    private long count = 0;

    public long getCount() { 
        return count ;
    }

    public void addCount() {
        count++;
    }
}

每次执行addCount方法,就给私有的count字段+1,请问addCount具有原子性吗?答案是否定的。因为看似简单的一行代码count++,其实包含了三个动作:

1、取出count值;

2、count值+1;

3、写入count值。

显然这是三个有先后顺序的复合操作,在单线程下没有问题,但是多线程下就不一样了。运行以下代码,会发现经过100个用户并发对count+1后,最终打印的结果并不一定是100。造成这种情况的原因是竞态条件的存在:当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。

public static void main(String[] args) {
        //List<User> users = accountWalletService.initUsers();
        TestCount testCount = new TestCount();

        List<Integer> users = initUsers();
        //模拟并发
        users.parallelStream().forEach(b -> {
            testCount.addCount();
            //System.out.println(testCount.getCount());
            // accountWalletService.process(b);
        });
        System.out.println("sum:" + testCount.getCount());
        System.out.println("-------------");
    }

    private static List<Integer> initUsers() {
        List<Integer> res = new ArrayList<>();
        for (int i=0;i<100;i++) {
            res.add(i);
        }
        return res;
    }

如何确保单个变量的原子性?java原生提供了Atomic类型:

public class TestCount {
    private AtomicLong count = new AtomicLong(0);
    //private long count = 0;
    public AtomicLong getCount() {
        return count;
    }
//    public long getCount() {
//        return count;
//    }

    public void addCount() {
        count.incrementAndGet();
        //count++;
    }
}

再次运行代码,发现结果正确。

加锁机制

当需要确保一个以上状态变量的线程安全性时,就需要用到锁。java提供了一种内置锁机制:同步代码块。以下是一个未加锁的类,简单的将私有变量ticket减一来模拟抢票。

package com.example.demo.impl;

/**
 * @ClassName
 * @Description TODO
 * @Author 重装机器
 * @Date 2022/2/27 15:10
 **/
public class MyRunnable implements Runnable {

    private int ticket = 100;

    private Object object = new Object();

    public void run() {

        while (true) {

           

                if (ticket > 0) {

                    try {

                        Thread.sleep(100);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().getName() + ":出售第" + (101 - ticket) + "张票");

                    ticket -= 1;

                } else {
                    break;
                }
            

        }

    }

}

主函数如下。总共起了3个线程,模拟3个窗口同时抢票。

public class testMain {
        public static void main(String[] args) {

            //初始化一个对象
            MyRunnable myRunnable = new MyRunnable();

            //启动三个线程跑该对象的run方法
            Thread t1 = new Thread(myRunnable,"窗口1");

            Thread t2 = new Thread(myRunnable,"窗口2");

            Thread t3 = new Thread(myRunnable,"窗口3");

            t1.start();

            t2.start();

            t3.start();

        }

}

运行起来看看。当不加锁时,三个窗口出现了抢到同一张票的情况,这显然不符合实际。

我们加了同步代码块后。注意代码用synchronized包裹了起来。

public class MyRunnable implements Runnable {

    private int ticket = 100;

    private Object object = new Object();

    public void run() {

        while (true) {

            //加同步代码块
            synchronized (object) {

                if (ticket > 0) {

                    try {

                        Thread.sleep(100);

                    } catch (InterruptedException e) {

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().getName() + ":出售第" + (101 - ticket) + "张票");

                    ticket -= 1;

                } else {
                    break;
                }
            }

        }

    }

}

再次运行程序,发现结果正确:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值