在之前的文章讨论了 数据可见性 问题,说的主要是一些理论上的东西,比较的抽象,下面来说一个简单的模型吧:

很多地方我们需要用一个容器来保存一些实体,如果这个容器内的实体经常的变化(添加或删除),那么要保证这个容器的线程安全将会变得比较棘手(如果更新相对比较少,且注重读取的性能,那么可以用CopyOnWriteArrayList); 其实往容器中添加信息倒还好,但删除就会比较麻烦,影子模型就是为了避免删除这一个动作的:

把实体放到一个封装类中(把这个封装类叫做实体的影子),在里面有一个标志位,表示这个被包装的实体是否有效,当要删除这个实体时,就把这个标志位设置为无效(并不实时从物理上将这个实体从容器中删除)(表示旧的影子已经失效了)(可以将用户的影子作为用户信息保存起来,这样就不用去遍历实体容器寻找旧的影子);关于如何保证该标志位的可见性问题,其实只要将其设置为volatile即可(在一些并不要求强可见性的场合,可以根据“并发闲聊 – 数据可见性”的延伸解读2或者3来尽量保证它的可见性)。

单单看影子模型部分的其实是一个非常简单的封装想法,这里就直接上代码吧:

影子接口:

package com.dc.util.shadow.shadow;
/** * 影子对象接口 * @author Daemon* @param <Entity> 影子的实体*/
public interface IShadow<Entity> {/*** 影子有效*/void valid();/*** 影子失效*/void invalid();/*** 影子是否失效* * @return 影子是否失效*/boolean isvalid();/*** 获得影子的实体* * @return 影子的实体*/Entity getEntity();
}

影子的标准实现类:

package com.dc.util.shadow.shadow.nomal;
import com.dc.util.shadow.shadow.IShadow;/** * 影子(通过将isvalid声明为volatile确保其可见性) * @author Daemon* @param <Entity> 影子的实体*/
public class Shadow<Entity> implements IShadow<Entity> {/*** 影子是否有效*/protected volatile boolean isvalid;/*** 影子的实体*/protected final Entity entity;public Shadow(Entity entity) {this.entity = entity;isvalid = true;}public Shadow(Entity entity, boolean isvalid) {this.entity = entity;this.isvalid = isvalid;}public void valid() {isvalid = true;}public void invalid() {isvalid = false;}public boolean isvalid() {return isvalid;}public Entity getEntity() {return entity;}}

影子的弱化版实现类:(可以根据“并发闲聊 – 数据可见性”的延伸解读2或者3来尽量保证它的可见性)

package com.dc.util.shadow.shadow.week;
import com.dc.util.shadow.shadow.IShadow;/** * 弱可见性的影子(属性isvalid可见性无保证) * @author Daemon* @param <Entity> 影子的实体*/
public class WeekShadow<Entity> implements IShadow<Entity> {/*** 影子是否有效*/protected boolean isvalid;/*** 影子的实体*/protected final Entity entity;public WeekShadow(Entity entity) {this.entity = entity;isvalid = true;}public WeekShadow(Entity entity, boolean isvalid) {this.entity = entity;this.isvalid = isvalid;}public void valid() {isvalid = true;}public void invalid() {isvalid = false;}public boolean isvalid() {return isvalid;}public Entity getEntity() {return entity;}}

具体来说一个场景吧:(并非实际已在使用的场景,实际场景要复杂些而且不具备通用性,所以这里就以一个比较通用的虚构场景作为例子吧,实际场景实现类似:http://git.oschina.net/daemon_c/DC-Util 下面的test目录下的DelayPushTest)

场景:

用户可以在前端界面操作用户账户进入一个或者多个房间,加入房间后用户就可以接收到房间内的消息推送,用户也可以随时退出房间,退出房间后用户就收不到房间内的消息推送。

就不多说了,直接上测试代码吧:

测试类:

package com.dc.util.shadow;import com.dc.util.array.ArraySnapshot;
import com.dc.util.array.SynSimpleArray;
import com.dc.util.hash.HashSegmentTools;
import com.dc.util.shadow.shadow.IShadow;
import com.dc.util.shadow.shadow.nomal.Shadow;/*** 房间测试类 * @author Daemon*/
public class RoomTest {public static void main(String[] args) {//创建房间Room room = new Room();//创建测试用户:daemon, dcUser user1 = new User("daemon");User user2 = new User("dc");//添加用户daemon, dc的影子到房间中Shadow<User> daemonShadow = new Shadow<User>(user1);room.addUser(daemonShadow);Shadow<User> dcShadow = new Shadow<User>(user2);room.addUser(dcShadow);//推送消息1room.pushMsg("test1 test1 test1");//用户daemon退出房间System.out.println("\n~~~~~~~daemonShadow\n");daemonShadow.invalid();//推送消息2room.pushMsg("test2 test2 test2");//清理无效的用户影子room.clearInValidShadow();System.out.println("\n~~~~~~~clearInValidShadow\n");//推送消息3room.pushMsg("test3 test3 test3");}
}

用户类:

/*** 用户 * @author Daemon*/
class User {
String userName;User(String userName) {this.userName = userName;}
}

房间类:

/*** 房间 * @author Daemon*/
class Room {/*** 用户影子容器*/ShadowContext<User> userContext = new ShadowContext<User>(4);/*** 用户加入房间 * @param userShadow 用户影子*/void addUser(IShadow<User> userShadow) {userContext.add(userShadow);}/*** 推送消息给房间内的所有用户 * @param msg 推送的消息*/@SuppressWarnings("unchecked")void pushMsg(String msg) {System.out.println("----pushMsg:" + msg);//循环容器内的所有对象for( ShadowArray<User> shadowArray : userContext.shadowArrays ) {//获得当前用户队列的一个快照ArraySnapshot arraySnapshot = shadowArray.getArraySnapshot();int size = arraySnapshot.size;Object[] elementData = arraySnapshot.elementData;//          System.out.println(size + " " + elementData.length);//发送给每个有效的用户for( int i=0; i<size; i++ ) {IShadow<User> userShadow = (IShadow<User>) elementData[i];if( userShadow.isvalid() ) {send(userShadow.getEntity(), msg);} else {//失效的影子不予处理,打印信息方便测试System.out.println( "*****invalid user:'" + userShadow.getEntity().userName + "'" );}}}System.out.println("----pushMsg over\n");}/*** 推送消息给有用户 * @param user 用户* @param msg 推送的消息*/void send(User user, String msg) {System.out.println( ">>>>>send to user:'" + user.userName );}/*** 清除掉失效的影子*/void clearInValidShadow() { userContext.clearInValidShadow();}
}

用户容器类:(将用户根据hashcode放入到不同的ShadowArray中,降低添加时候的锁竞争)

/*** 用户影子容器 * @author Daemon* @param <EntityType> 用户*/
class ShadowContext<EntityType> extends HashSegmentTools {/*** 将用户存放在不同的桶内,降低添加时候的锁竞争*/final ShadowArray<EntityType>[] shadowArrays;@SuppressWarnings("unchecked")ShadowContext(int concurrencyLevel) {super(concurrencyLevel);int ssize = segmentMask + 1;shadowArrays = new ShadowArray[ssize];for( int i=0; i<ssize; i++ ) {shadowArrays[i] = new ShadowArray<EntityType>();}}/*** 添加用户 * @param shadow 用户影子*/void add(IShadow<EntityType> shadow) {shadowArrays[getSegmentIndex(shadow)].add(shadow);}/*** 清除掉失效的影子*/void clearInValidShadow() {for( ShadowArray<EntityType> array : shadowArrays ) {array.clearInValidShadow();}}
}

用户数组:(继承SynSimpleArray,并提供清除失效影子的方法)

/*** 用户数组封装类,对外提供线程安全的方法 * @author Daemon* @param <EntityType> 用户*/
class ShadowArray<EntityType> extends SynSimpleArray<IShadow<EntityType>> {ShadowArray() {super(0);}/*** 清除掉失效的影子*/@SuppressWarnings("unchecked")synchronized void clearInValidShadow() {int size = this.size;Object[] array = this.elementData;//循环所有的元素,查找是否有存在失效的影子IShadow<EntityType> shadow;for( int i=0; i<size; i++ ) {shadow = (IShadow<EntityType>) array[i];if( ! shadow.isvalid() ) {//发现有失效的影子,更新队列信息并返回int newSize = 0;Object[] newArray = new Object[size+6];for( int j=0; j<i; j++ ) {newArray[newSize++] = (IShadow<EntityType>) array[j];}for( int j=i+1; j<size; j++ ) {shadow = (IShadow<EntityType>) array[j];if( shadow.isvalid() )newArray[newSize++] = shadow;}this.size = newSize;this.elementData = newArray;return;}}//没有发现失效的影子}
}

控制台输出效果:

----pushMsg:test1 test1 test1
>>>>>send to user:'daemon
>>>>>send to user:'dc
----pushMsg over~~~~~~~daemonShadow----pushMsg:test2 test2 test2
*****invalid user:'daemon'
>>>>>send to user:'dc
----pushMsg over~~~~~~~clearInValidShadow----pushMsg:test3 test3 test3
>>>>>send to user:'dc
----pushMsg over

详细的代码可以在Git@OSC:http://git.oschina.net/daemon_c/DC-Util 下面的src和test目录 找到

影子模型(Java篇)相关推荐

  1. BurpSuite插件开发指南之 Java 篇

    Her0in · 2016/05/27 16:53 此文接着 <BurpSuite插件开发指南之 API 下篇> .在此篇中将会介绍如何使用Java 开发 BurpSuite 的插件,重点 ...

  2. 明翰Java教学系列之认识Java篇V1.3(持续更新)

    文章目录 传送门 前言 什么是Java? Java之父 `Java的应用场景` Java部分特点 Java工作机制 JDK(Java Development Kit) JRE(Java Runtime ...

  3. 面试宝典Java篇(基础+高级+集合+线程+IO+JVM)

    一.Java篇 1.1 Java基础篇 1.请你讲讲&和&&的区别? &运算符有两种用法:(1)按位与:(2)逻辑与. &&运算符是短路与运算. 逻辑与 ...

  4. 未来学习规划 - java篇

    未来学习规划 - java篇 1. Java 1.1 JDK源码 1.1.1 HashMap HashMap的数据结构(1.7.1.8的区别) HashMap的实现原理 HashMap扩容为什么是2^ ...

  5. 游戏开发总结-java篇

    游戏开发总结-java篇 前言 网络通信 数据存储 逻辑开发 逻辑开发一般遇到的问题有: Java游戏服务器方面的开发要掌握的技术: java服务器目前主流框架技术 前言 Java语言,由于学习成本低 ...

  6. map根据value值排序_凯哥带你从零学大数据系列之Java篇---第十九章:集合(Map+Collections)...

    温馨提示:如果想学扎实,一定要从头开始看凯哥的一系列文章(凯哥带你从零学大数据系列),千万不要从中间的某个部分开始看,知识前后是有很大关联,否则学习效果会打折扣. 系列文章第一篇是拥抱大数据:凯哥带你 ...

  7. 【Android 内存优化】Java 内存模型 ( Java 虚拟机内存模型 | 线程私有区 | 共享数据区 | 内存回收算法 | 引用计数 | 可达性分析 )

    文章目录 一. Java 虚拟机内存模型 二. 程序计数器 ( 线程私有区 ) 三. 虚拟机栈 ( 线程私有区 ) 四. 本地方法栈 ( 线程私有区 ) 五. 方法区 ( 共享数据区 ) 1. 方法区 ...

  8. JSON总结(java篇)

    JSON总结(java篇一) JSON简介 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.它基于ECMAScript的一个子集. JSON采用完全独立于 ...

  9. ios 获取一个枚举的所有值_凯哥带你从零学大数据系列之Java篇---第十一章:枚举...

    温馨提示:如果想学扎实,一定要从头开始看凯哥的一系列文章(凯哥带你从零学大数据系列),千万不要从中间的某个部分开始看,知识前后是有很大关联,否则学习效果会打折扣. 系列文章第一篇是拥抱大数据:凯哥带你 ...

最新文章

  1. 解决Python模块报错:ModuleNotFoundError: No module name 'StringIO'
  2. TricycleGAN:基于形状先验的无监督图像合成和分割
  3. Micropython TPYBoard 智能温控小风扇资料分享
  4. java web start error_java web start——启动文件错误,没法启动指定应用程序这个问题如何解决?谢谢...
  5. Game On Serverless:SAE 助力广州小迈提升微服务研发效能
  6. JAVA对象在JVM中内存分配
  7. windows上运行MapReduce出错(Failed to set permissions of path)
  8. Apache Ignite,Hazelcast,Cassandra和Tarantool之间的主要区别
  9. 把照片存QQ相册会越来越模糊,你们会把照片存在哪里?
  10. C#开发WPF/Silverlight动画及游戏系列教程(Game Tutorial):(四十九) 落雷!治疗!陷阱!连锁闪电!多段群伤!魔法之终极五重奏②...
  11. TimesTen Classic 18c 卸载 (uninstall)全过程
  12. C++ sting字符串函数详解
  13. wex5使用java语言_WeX5学习笔记之调用后端服务
  14. h3c 云服务器操作系统,产品技术-H3C CloudOS云操作系统电信版-新华三集团-H3C
  15. shields 徽标_纽约公共图书馆的新徽标
  16. 股价破300迎新年开门红,投资者们中了苹果的毒?
  17. VBA小白的福音 如何在EXCEL中实现连续编号自动更新打印?
  18. 怎么写好一篇接口文档
  19. 【欧拉计划第 5 题】最小公倍数 Smallest multiple
  20. android x86占比,不输主流安卓机 Android x86系统体验分享(附性能对比)

热门文章

  1. HTML5吃豆人在画布移动制作,js+canvas实现动态吃豆人效果
  2. transmac装黑苹果_黑苹果安装LionGM版!!完全摆脱MacDriver、TransMac等工具
  3. 查看parquet文件工具parquet-tools
  4. 网安必备知识:常见的安全设备
  5. 图片中的文字打码,公章打码
  6. Linux HA集群——共享存储篇
  7. 122个常见问题收集整理(FLASH初学者参见)
  8. 微信v3app支付php,微信app支付
  9. 详细教你如何部署ICE服务(三)---IceBox框架 Ice Registry服务注册中心的联合使用
  10. Python数学建模系列(六):蒙特卡洛算法