java并发程序和共享对象实用策略
java并发程序和共享对象实用策略
在并发程序中使用和共享对象时,可以使用一些实用的策略,包括:
- 线程封闭
- 只读共享。共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象
- 线程安全共享。线程安全地对象在器内部实现同步。
- 保护对象。被保护的对象只能通过持有特定的锁来方访问。
线程封闭
当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭,它是实现线程安全性的最简单方式之一。
Ad-hoc线程封闭指的是,维护线程封闭性的职责完全由程序实现来承担。
当决定使用线程封闭技术时,通常是因为要将某个特定的子系统实现为一个单线程子系统。在volatile变量上存在一种特殊的线程封闭,只要你能确保只有单个线程对共享的volatile变量执行写入操作,那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”操作。在这种情况下,相当于将修改操作封闭在单个线程中以防止发生竞态条件,并且volatile变量的可见性保证还确保了其他线程能看到的最新值。
栈封闭是一种线程封闭的特例,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行线程中。它们位于执行线程的栈中,其他线程无法访问这个栈。在下面的例子中,由于任何方法都无法获得对基本类型的引用,因此Java语言的这种语义就确保了基本类型的局部变量始终封闭在线程内。
public int loadTheArk(Collection<Animal> candidates){
SortedSet<Animal> animals;
int numPirs = 0;
Animal candidate = null;
//animals被封闭在方法中,不要使它们逸出!
animals = new TreeSet<Animal>(new SpeciesGenderComparator());
animals.addAll(candidates);
for(Animal a : animals){
if(candidate == null || !candidate.isPotentialMate(a))
candidate =a;
else{
ark.load(new AnimalPair(candidate,a));
++numPairs;
candidate = null;
}
}
return numPairs;
}
在维持对象引用的栈封闭时,程序员需要多做一些工作以确保引用的对象不会逸出。
ThreadLocal类通常用于防止对可变的单实例变量(Singleton)或全局变量进行共享。例如通过将JDBC的连接保存到ThreaLocal对象中,每个线程都会拥有属于自己的连接
private static ThreadLocal<Connection> connectionHolder
=new ThreadLocal<Connection>(){
public Connection initialValue(){
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection(){
return connectionHolder.get();
}
当某个频繁执行的操作需要一个临时对象,例如一个缓冲区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以用这个技术。但是,ThreadLocal变量类似于全局变量,它能降低代码的可重用性,并在类之间引入隐含的耦合性,因此在使用时要格外小心。
不变性
满足同步需求的另一种方法是使用不可变对象。线程安全性是不可变对象的固有属性之一,它们的不变性条件是由构造函数创建的,只要它们的状态不改变,那么这些不变条件就能得以维持。另一方面,不可变对象不会像这样被恶意代码或者有问题的代码破坏,因此可以安全地共享和发布这些对象,而无须创建保护性的副本。
当满以下条件时,对象才是不可变的:
- 对象创建以后其状态就不能修改。
- 对象的所有域都是final类型。
- 对象是正确创建的(在对象的创建期间,this引用没有逸出)。
@Immutable
public final class ThreeStooges{
private final Set<String> stooges = new HashSet<String>();
public ThreeStooges(){
stooges.add("Moe");
stooges.add("Larry");
stooges.add("Curly");
}
public boolean isStooge(String name){
return stooges.contains(name);
}
}
使用Volatile类型来发布不可变对象
@Immutable
public class OneValueCache {
private final BigInteger lastNumber;
private final BigInteger[] lastFactors;
public OneValueCache(BigInteger lastNumber, BigInteger[] lastFactors) {
this.lastNumber = lastNumber;
this.lastFactors = Arrays.copyOf(lastFactors, lastFactors.length);
}
public BigInteger[] getFactors(BigInteger integer) {
if (lastNumber == null || !lastNumber.equals(integer)) {
return null;
} else {
return Arrays.copyOf(lastFactors, lastFactors.length);
}
}
}
使用指向不可变容器对象的volatile类型引用以缓存最新的结果
@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
private volatile OneValueCache cache = new OneValueCache(null, null);
/**
* 通过使用包含多个状态变量的容器对象来维持不变性的条件,并使用一个volatile类型的引用来确保可见性,
* 使得Volatile Cache Factorizer 在没有显式地使用锁的情况下仍然是线程安全的
* @param servletRequest
* @param servletResponse
* @throws ServletException
* @throws IOException
*/
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse)
throws ServletException, IOException {
BigInteger i = extractFromRequest(servletRequest);
BigInteger[] factors = cache.getFactors(i);
if (factors == null) {
factors = factor(i);
cache = new OneValueCache(i, factors);
}
encodeIntoResponse(servletRequest, servletResponse);
}
}
安全发布
要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全地发布:
- 在静态初始化函数中初始化一个对象引用。
- 将对象的引用保存到volatile类型的域或者AtomicReferance对象中。
- 将对象的引用保存到某个正确构造对象的final类型域中。
- 将对象的引用保存到一个由锁保护的域中
尽管javadoc在这个主题上没有给出很清晰的说明,但线程安全库中的容器类提供了以下的安全发布保证:
- 通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地将它发布给任何从这些容器中访问它的线程
- 通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以将该元素安全地发布到任何从这些容器中访问该元素的线程。
- 通过将某个元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以将该元素安全地发布到任何从这些队列中访问该元素的线程
通常,要发布一个静态构造的对象,最简单和最安全的方式是使用静态的初始化器:
public static Holder hodler= new Holder(42);
静态初始化器由JVM在类的初始化阶段执行。由于在JVM内部存在着同步机制,因此通过这种方式初始化的任何对象都可以被安全地发布。
事实不可变对象指的是如果对象从技术上看是可变的,但其状态在发布后不会再改变,那么把这种对象称为事实不可变对象。在这些对象发布后,程序只需要将它们视为不可变对象即可。
java并发程序和共享对象实用策略的更多相关文章
- 程序与程序之间共享对象:MarshalByRefObject
源自于:http://stackoverflow.com/questions/439173/message-pumps-and-appdomains/442316 程序与程序之间共享对象: Marsh ...
- Java并发编程实战 之 对象的共享
上一篇介绍了如何通过同步多个线程避免同一时刻访问相同数据,本篇介绍如何共享和发布对象,使它们被安全地由多个进程访问. 1.可见性 通常,我们无法保证执行读操作的线程能看到其他线程写入的值,因为每个线程 ...
- java并发:线程池、饱和策略、定制、扩展
一.序言 当我们需要使用线程的时候,我们可以新建一个线程,然后显式调用线程的start()方法,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创 ...
- java并发程序——并发容器
概述 java cocurrent包提供了很多并发容器,在提供并发控制的前提下,通过优化,提升性能.本文主要讨论常见的并发容器的实现机制和绝妙之处,但并不会对所有实现细节面面俱到. 为什么JUC需要提 ...
- java并发程序——BlockingQueue
概述 BlockingQueue顾名思义'阻塞的队列',是指在:队列的读取行为被阻塞直到队列不为空时,队列的写入行为被阻塞直到队列不满时.BlockingQueue是java.util.concurr ...
- JAVA并发编程学习笔记------对象的可见性及发布逸出
一.非原子的64位操作: 当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是一个随机值,这种安全性保证被称为最低安全性.最低安全性适用于绝大多数变量 ...
- 你的 Java 并发程序 Bug,100% 是这几个原因造成的
可见性问题 可见性是指一个线程对共享变量进行了修改,其他线程能够立马看到该共享变量更新后的值,这视乎是一个合情合理的要求,但是在多线程的情况下,可能就要让你失望了,由于每个 CPU 都有自己的缓存,每 ...
- Java并发编程笔记—基础知识—实用案例
如何正确停止一个线程 1)共享变量的使用 中断线程最好的,最受推荐的方式是,使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务.线程必须周期性的核查这一变量(尤其在 ...
- java并发程序——Excutor
概述 Excutor这个接口用的不多,但是ThreadPoolExcutor这个就用的比较多了,ThreadPoolExcutor是Excutor的一个实现.Excutor体系难点没有,大部分的关键点 ...
随机推荐
- linux命令积累
lsof -i: //根据端口号查相关信息 //杀进程 ps -ef|grep appName //根据进程名称查找相关信息 grep -r "关键词" 目录 //在制定目录下根据 ...
- iOS组件化开发一远程私有库的升级(三)
一.远程私有库的升级 1. 把新增的类 拖入到 classes 文件夹中 : 2. 修改 pod.spec 文件的 s.verson = ‘0.2.0': 二.更新远程仓库 1.cd 到本地仓库的位置 ...
- scrapy实战9动态设置ip代理从数据库中随机获取一个可用的ip:
在目录下创建tools(python package) 在tools中创建crawl_xici_ip.py文件写入代码如下: #coding=utf-8 import requests from sc ...
- scrapy基础知识之 parse()方法的工作机制思考:
1.因为使用的yield,而不是return.parse函数将会被当做一个生成器使用.scrapy会逐一获取parse方法中生成的结果,并判断该结果是一个什么样的类型: 2.如果是request则加入 ...
- Yii basic 模板支持连接多数据库
1.首先修改db配置文件,修改格式如下: return [ 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:hos ...
- Redis 使用C#程序操作Redis
一.安装操作所需Nuget包 二.写入 redis只是按Key值设置过期时间,不是对value内部的某些值设过期 2.1 string类型 /// <summary> /// 向Redis ...
- vue组件间通信六种方式(完整版)
本文总结了vue组件间通信的几种方式,如props. $emit/ $on.vuex. $parent / $children. $attrs/ $listeners和provide/inject,以 ...
- 使用gulp构建微信小程序工作流
前言 刚入门微信小程序的时候,一切都基于微信web开发者工具,没有使用其他框架,也没有工程化的概念.当时做的项目都比较简单,单单用微信web开发者工具倒也得心应手.学了些东西后,就按捺不住地想跳出原生 ...
- Java编程思想:文件加锁
import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; i ...
- 数据库触发器_trigger
部门表_删除: USE [test] GO /****** Object: Trigger [dbo].[部门_Delete] Script Date: 2015/8/31 16:41:46 **** ...