实现多线程安全的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();
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

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());
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

运行结果:

线程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());
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

运行结果:

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


                        <li class="tool-item tool-active is-like "><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#csdnc-thumbsup"></use>
                        </svg><span class="name">点赞</span>
                        <span class="count">5</span>
                        </a></li>
                        <li class="tool-item tool-active is-collection "><a href="javascript:;" data-report-click="{&quot;mod&quot;:&quot;popu_824&quot;}"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-Collection-G"></use>
                        </svg><span class="name">收藏</span></a></li>
                        <li class="tool-item tool-active is-share"><a href="javascript:;"><svg class="icon" aria-hidden="true">
                            <use xlink:href="#icon-csdnc-fenxiang"></use>
                        </svg>分享</a></li>
                        <!--打赏开始-->
                                                <!--打赏结束-->
                                                <li class="tool-item tool-more">
                            <a>
                            <svg t="1575545411852" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5717" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M179.176 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5718"></path><path d="M509.684 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5719"></path><path d="M846.175 499.222m-113.245 0a113.245 113.245 0 1 0 226.49 0 113.245 113.245 0 1 0-226.49 0Z" p-id="5720"></path></svg>
                            </a>
                            <ul class="more-box">
                                <li class="item"><a class="article-report">文章举报</a></li>
                            </ul>
                        </li>
                                            </ul>
                </div>
                            </div>
            <div class="person-messagebox">
                <div class="left-message"><a href="https://blog.csdn.net/jinggod">
                    <img src="https://profile.csdnimg.cn/4/D/8/3_jinggod" class="avatar_pic" username="jinggod">
                                            <img src="https://g.csdnimg.cn/static/user-reg-year/1x/2.png" class="user-years">
                                    </a></div>
                <div class="middle-message">
                                        <div class="title"><span class="tit"><a href="https://blog.csdn.net/jinggod" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}" target="_blank">jinggod</a></span>
                                            </div>
                    <div class="text"><span>发布了25 篇原创文章</span> · <span>获赞 23</span> · <span>访问量 3万+</span></div>
                </div>
                                <div class="right-message">
                                            <a href="https://im.csdn.net/im/main.html?userName=jinggod" target="_blank" class="btn btn-sm btn-red-hollow bt-button personal-letter">私信
                        </a>
                                                            <a class="btn btn-sm  bt-button personal-watch" data-report-click="{&quot;mod&quot;:&quot;popu_379&quot;}">关注</a>
                                    </div>
                            </div>
        </div>
    
小屁孩大帅-杨一凡
发布了255 篇原创文章 · 获赞 149 · 访问量 48万+

本文链接http://element-ui.cn/news/show-934.aspx