推荐一个Java学习群523401738每天晚上在腾讯课堂都有一个Java技术学习课,会有老师分享干货,帮助大家分析解答问题,你愿意来学习吗
多线程间数据共享与隔离
什么场景下会用到数据共享?如何实现多线程之间的数据共享?如何确保多线程修改共享变量不会出现并发问题?
本篇博文为基础篇,努力做到让大家一目了然,同时欢迎各路大神的批评指正
主要内容集中在多线程环境下,如何实现安全高效的数据共享和数据隔离
I. 数据共享的几种方式
我们可以先抛开多线程这个前置条件,我们先看下在多个对象之间如何共享数据?
类的公开静态变量,多对象可以直接访问,可算是共享
interface中定义的属性,也可以直接访问,亦可以算是共享
外部配置(如文件中的值,远程的配置属性等),也可以算是都对象共享的资源
那么多线程之间的数据共享,又有什么区别呢?
首先上面的几种,当然也是合乎多线程的数据共享规则的,除此之外,也有一些与众不同的地方,下面就就结合不同的线程使用方式来说道说道
1. 一个Callable/Runnable任务,多个Thread执行
博文《Java并发学习之四种线程创建方式的实现与对比》介绍了线程创建的几种方式,我们知道利用Callbale/Runnable接口来创建线程时,最终都是要丢给Thread来启动线程的
先来个小例子,演示下上面这是个什么场景
主要将目标集中在线程1和线程2的创建上,实例中,只创建了一个task实例,但是起了两个线程,都是将task实例作为参数丢到线程中去调度执行
既然两个线程执行任务时,都是执行的同一个实例--task的run(),方法,那么task实例中的count变量理应就是两个线程共享的了
执行上面的,看下输出
如果数据不是共享的,则不可能出现2;至于为什么都输出2,则涉及到了线程同步的问题,后续再讲
所以,这种场景,就是一个有意思的线程间数据共享了
2. 一个Callable/Runnable对应一个Thread
上面是复用了task任务,如果我们不复用task任务呢,如何实现数据共享?
下面看下一种猥琐的实现方式
相比上面的一种方式,虽然创建了多个task任务,但是共享的数据,放在了一个多任务可以访问的对象内,输出如下,也证明确实属于共享了
3. 数据共享分析小结
上面两种是典型的数据共享方式,虽说表现形式不同,但揪其核心,就一点:
多线程的数据共享其本质依然是共同访问某一个实例的成员属性
为什么这么说?
多个线程,访问同一个数据(or资源),那么这个数据必然有一个载体,java的世界中一切都是对象,所以多个线程访问同一个实例的成员变量,也就能实现多线程之间的数据共享了
讲道理,弄清楚这个核心之后,再看上面的两种玩法
1. 一个Task任务,放在多个Thread中运行
因为只有一个task实例,被多个线程运行,那么task内部的成员,当然可以轻松的实现多线程的数据共享
2. 每个Thread独立运行
因为每个Thread都独立运行一个task任务,task之间的运行彼此独立,所以要实现数据共享,只能借助第三方的实例
即大家都访问一个第三方实例的成员属性,间接的实现多线程数据共享
再简单分析下线程池的使用场景(其实质和上面等同, 殊途同归罢了)
依然是有两种使用方式,公用task和独立的task
II. 数据隔离怎么玩
这个就有点意思了,有了数据共享就有了数据隔离; 有些数据我希望是多线程公用的;但是有些呢,我希望每个线程都是彼此独立互不影响的
上面说了数据共享的本质,反其道而行之,就能摸出数据隔离的本质,每个线程就访问线程内部的数据,不访问共通的数据,不就实现数据隔离了么
说说容易,实际做呢?
1. 一个task就对一个thread
这个就是最简单的方式了,每个thread就新创建一个task实例,那么task内部的成员全都是隔离的,对其他线程而言,都是访问不到的(正常情况下是访问不到的,当然不正常的情况下就说不准了)
2. ThreadLocal 的使用
重点来说一下这个,其实主要也是说如何使用 ThreadLocal 来实现数据隔离
使用 ThreadLocal 来保证变量在线程之间的隔离, 下面是一个简单的演示,两个线程都是在修改threadLocal中的值, 但是两个线程的修改,对彼此而言是独立的
某次输出截图如下,两个线程不会出现乱掉的情况
因为本篇的着力点是介绍数据共享和数据隔离的玩法,所以这里也就简单说下ThreadLocal的实现原理
使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
使用方式也比较简单,常用的三个方法
III. 注意事项&小结
1. 实例演示多线程数据共享和数据隔离
我们举一个小case,来分别演示下什么东西需要多线程之间的数据共享,什么东西又是需要多线程的数据隔离
诶,到点和女票一起去火锅店开胃了,这家店服务不错,对个用户,一进来就递上菜单,等着顾客勾选完比之后将菜单给后厨,开始准备食材
这个么过程就涉及到了多线程的数据共享和数据隔离
每一桌客户涮火锅的行为,我们比作开了一个线程执行某项任务;
那么有多少桌客户,就有多少个线程了
那么什么是多线程之间共享的呢?即什么东西是每桌客户都有的呢?
菜单
那么什么是多线程之间隔离的呢?即什么东西每桌客户都不一样,是由每桌的客户自己来控制的呢?
客户点的菜单
这么一看,诶,稍微清晰点了,但是又有个问题了,实际上呢,多线程共享的数据,可不仅仅是大家都可以查询这个数据,我们不尽可以查,我们还可以放心大胆的改呢,对比上面的,我一个客户来了菜单,新增一个菜,难不成其他桌的菜单上也新增了这个菜不成???
这个问题就有意思了,这个涉及的起始是另一个问题,多线程之间数据共享的安全性保障(即多线程之间的数据同步),主要表现为
一个线程修改共享数据,其他线程是否可以立马看到
一个线程修改共享数据,是否会被其他线程覆盖掉(而且这个覆盖的逻辑随机不可控)
那么这个问题怎么hold住呢? 精彩分析,欢迎关注下节分享(目前还没产出....)
2. 数据共享
多线程的数据共享其本质依然是共同访问某一个实例的成员属性
为了达到上面这个目标呢,常见的两种使用姿势如下
一个Task任务,被多个线程共同使用,那么Task任务内部的数据都是多线程共享的
一个公共的实例持有需要共享数据,每个线程都访问/修改这个实例的数据
3. 数据隔离
线程内部的数据都是隔离且安全的
借助ThreadLocal, 每个使用该变量的线程提供独立的变量副本
III. 其他
声明
尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见识有限,如有问题,请不吝指正,感激
欢迎关注java一灰灰
更多并发相关学习博文链接
Java并发之 volatile & synchronized 讲解
Java并发学习之ReentrantLock的工作原理及使用姿势