1、先来了解一下:为什么多线程并发是不安全的?
??在操作系统中,线程是不拥有资源的,进程是拥有资源的。而线程是由进程创建的,一个进程可以创建多个线程,这些线程共享着进程中的资源。所以,当线程一起并发运行时,同时对一个数据进行修改,就可能会造成数据的不一致性,看下面的例子:
假设一个简单的int字段被定义和初始化:
int counter = 0;
该counter字段在两个线程A和B之间共享。假设线程A、线程B同时对counter进行计算,递增运算:
counter ++;
那么计算的结果应该是 2 。但是真实的结果却是 1 ,这是因为:线程A得到的运算结果是1,线程B的运算结果也是1,当线程A将结果写回到内存中的 count 后,线程B也将结果写回到内存中去,这就会把线程A的计算结果给覆盖了。
上面仅仅是一种简单的情况,还有更复杂的情况,本文不深入去了解。
2、多线程并发不安全的原因已经知道,那么针对这个种情况,java中有两种解决思路:
- 给共享的资源加把锁,保证每个资源变量每时每刻至多被一个线程占用。
- 让线程也拥有资源,不用去共享进程中的资源。
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(){@Overridepublic 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="{"mod":"popu_824"}"><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="{"mod":"popu_379"}" 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="{"mod":"popu_379"}">关注</a></div></div></div>