实现多线程安全的3种方式

1、先来了解一下:为什么多线程并发是不安全的?

  在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。所以,当线程一起并发运行时,同时对一个数据进行修改,就可能会造成数据的不一致性,看下面的例子:

假设一个简单的int字段被定义和初始化:
int counter = 0;
该counter字段在两个线程A和B之间共享。假设线程A、线程B同时对counter进行计算,递增运算:
counter ++;
那么计算的结果应该是 2 。但是真实的结果却是 1 ,这是因为:线程A得到的运算结果是1,线程B的运算结果也是1,当线程A将结果写回到内存中的 count 后,线程B也将结果写回到内存中去,这就会把线程A的计算结果给覆盖了。

上面仅仅是一种简单的情况,还有更复杂的情况,本文不深入去了解。

2、多线程并发不安全的原因已经知道,那么针对这个种情况,java中有两种解决思路:
  1. 给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。
  2. 让线程也拥有资源,不用去共享进程中的资源。
3、基于上面的两种思路,下面便是3种实施方案:

1. 多实例、或者是多副本(ThreadLocal):对应着思路2,ThreadLocal可以为每个线程的维护一个私有的本地变量,可参考java线程副本–ThreadLocal
2. 使用锁机制 synchronize、lock方式:为资源加锁,可参考我写的一系列文章;
3. 使用 java.util.concurrent 下面的类库:有JDK提供的线程安全的集合类


可能说的还不太清楚,更新一下,以及给出一个线程安全模拟的例子:
上面说了,多线程之所以不安全,是因为共享着资源(如果没有资源变量共享,那么多线程一定是安全的)。比如,存在共享变量a,线程A在使用变量a时进行计算时,因为时间片的到来,导致线程不得不由运行中状态进入就绪状态,暂停运行。等该线程A又重新被调度,得以继续执行时,得到了最终的结果。但是此时内存中的变量a可能已经被其他线程改变了,但线程A的结果再写回到内存中时,就会覆盖了其他线程的计算结果,这就是多线程不安全的原理。

下面给出线程安全模拟的例子的思路:1、让三个线程瞬间同时并发(不得不用到锁,wait/notify机制,如果不懂,只要知道这是 等待/通知 便可,下面有注释);2、模拟3个线程共享着一个变量,使用变量进行计算的过程 与 将计算结果分成两次执行。
下面是没有进行同步,也就是线程不安全的情况:

CountMoney countMoney = new CountMoney();
String obj="";
    //创建启动3个线程
    for(int i=0;i<3;i++){
        Thread t1 = new Thread(){
            @Override
            public void run() {
            //用锁来让线程第一次运行时,进入等待状态,直到被通知来了才继续往下运行
                synchronized (obj) {
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //通知来了后,执行addMoney的方法
                countMoney .addMoney(1);
            }
        };
        //线程启动
        t1.start();
        //确保创建的线程的优先级一样
        t1.setPriority(Thread.NORM_PRIORITY);
    }
    try {
        //确保创建的3个线程已经运行了一次,进入等待状态
        Thread.sleep(200);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    synchronized (obj) {
        //瞬间唤醒3个线程
        obj.notifyAll();
    }

CountMoney 类

public class CountMoney {
    //线程共享着CountMoney对象中的money变量
    volatile long money = 0;

    public long getMoney() {
        return money;
    }

    public void setMoney(long money) {
        this.money = money;
    }

    public  void addMoney(long a) {//synchronized
        //1、取得变量money的值,计算出结果
        a = getMoney() + a;
        //线程完成第一步后,让出CPU;目的是:模拟1、2两行代码是分成两次执行的,不是一次性执行的
        Thread.yield();
        //2、将计算结果写回到变量money中
        setMoney(a);
        System.out.println("线程"+Thread.currentThread().getName()+"的计算结果"+getMoney());
    }

}

运行结果:

线程Thread-2的计算结果1
线程Thread-1的计算结果1
线程Thread-0的计算结果1

我们再来看一下,加锁后的 addMoney()方法,也就是进行同步后:

public synchronized void addMoney(long a) {//加了synchronized 修饰
        //1、取得变量money的值,计算出结果
        a = getMoney() + a;
        //线程完成第一步后,让出CPU;目的是:模拟1、2两行代码是分成两次执行的,不是一次性执行的
        Thread.yield();
        //2、将计算结果写回到变量money中
        setMoney(a);
        System.out.println("线程"+Thread.currentThread().getName()+"的计算结果"+getMoney());
    }

运行结果:

线程Thread-2的计算结果1
线程Thread-1的计算结果2
线程Thread-0的计算结果3


  • 8
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值