您的位置:首页 > 编程语言 > Java开发

Java 并发编程之对象的共享(二)

2014-08-20 15:41 232 查看

线程封闭

一般在单线程内访问数据就不需要同步,这种技术叫做线程封闭,在Swing中大量使用了线程封闭技术,Swing的可视人化组件和数据模型对象都不是线程安全的,Swing通过将它们封闭到Swing的事件分发线程中来实现线程安全性。

线程封闭的另一个常见应用是JDBC的Connection对象,JDBC规范并不要求Connection对象必须是线程安全的。应用程序服务器连接池是线程安全的,连接池通常会由多个线程同时访问,因此非线程安全的连接池是毫无意义的。

一个安全的线程池通常要实现下列接口

package mysql;
import java.sql.*;
import java.sql.ResultSet;
public interface Pool
{
public boolean start(String dbname,String user,String psw);  //启动数据库连接池服务
//以下start函数将允许用户设置最低空闲连接数,最高空闲连接数,最大连接数
public boolean start(int lows,int maxs,int maxc,String dbname,String user,String psw);
public Connection getConnection();  //得到连接器
public boolean freeConnection(Connection con);//将连接返回到连接池中
public boolean close(); //清除连接池,并且关闭它(使之变得不可用)
}


Ad-hoc线程封闭

 这个是指,维护线程封闭性的职责完全由程序实现来承担,Ad-hoc线程封闭是非常脆弱的。因为没有语言特性可以将对象封闭到目标线程上,事实上对线程封闭对象的引用通常保存在公有变量 中。

栈封闭

栈封闭是线程封闭的一种特例。在栈封闭中只能通过局部变量才能访问对象。

public int loadTheArk(Collection<Animal> candidates) {
SortedSet<Animal> animals;
int numpairs = 0;
Animal cnadidate = nul;
animals = new TreeSet<Animal>(new GenderComparator());
animals.addAll = (candidates);
for (Animal a : animals) {
if (candidates == null || !candidates.isPotentalMate(a)) {
cnadidate = a;
} else {
ark.load(new AnimalPair(candidates, a));
++numpairs;
candidates = null;
}
}
return numpairs;
}
例如上面程序的Numpairs对象。无论如何都不会破坏栈封闭性。由于任何方法 都 无法 获得对基本类型的引用。同时只有一个引用指向集体animals,这个引用被封闭在局部变量中,因为也被封闭在执行线程中。

ThreadLocal类

维持线程封闭性的一种更规范的方法是使用ThreadLocal。这个类能使线程中的某个值与保存值 的对象关联起来。它提花了get与set等 访问接口或方法,这个方法为每个使用该 变量的纯种都 存有一个独立 的副本,因此get总是返回由当前执行线程在调用set时设置 的最新值。

public static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(url);
}
};

public static Connection getConnection() {
return connectionHolder.get();
}


这个ThreadLocal相当于 一个单线程池。当某个频繁执行的操作需要一个临时对象 ,例如 一个缓冲 区,而同时又希望避免在每次执行时都重新分配该临时对象,就可以使用这项技术 。

它会降低可重用性,也会在类之间引入隐含的耦合性,所以在使用格外小心 。

为此还做了一个小实验证明其线程安全性。

public class unsafe {
public static ThreadLocal<Integer> connectionHolder = new ThreadLocal<Integer>() {

@Override
protected Integer initialValue() {
// TODO Auto-generated method stub
int a = 1;
return a;
}
};

public static void main(String[] Args) {
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
connectionHolder.set(2);
}
}).start();
System.out.println(connectionHolder.get());
connectionHolder.set(2);
System.out.println(connectionHolder.get());
}
}
输出结果 :

1

2

证明只可以在主线程中对其进行修改。

不变性

不可变的对象一定是线程安全的。但是不可变不等于将所有的域都声明为final类型,即使这么做了那么这个对象仍然是可变的,因为在final类型的域中可以保存对可变对象的引用 。

举个栗子

import java.util.ArrayList;

public class unsafe {
private final static java.util.List<Integer> unsafevalue = new ArrayList<Integer>();

public static void main(String[] agrs) {
unsafevalue.add(2);
System.out.println(unsafevalue.get(0));
}
}
如果这上面个类有个get方法可以得到unsafevalue,那么仍然可以对final声明的域进行修改。

当满足三个条件时,对象才是不可变的。上面final的声明只是条件之一

对象创建以后其状态不能被修改
对象所有域都是Final类型
对象 是正确创建的(没有this引用逸出)
再举个栗子

import java.util.ArrayList;

public final class unsafe {
private final static java.util.List<Integer> unsafevalue = new ArrayList<Integer>();

unsafe() {
unsafevalue.add(2);
unsafevalue.add(3);
unsafevalue.add(4);
}

public boolean isExist(int t) {
if (unsafevalue.contains(t)) {
return true;
}
return false;
}
}


一个好的编程习惯:

除非需要更高的可见性,否则都声明为私有域,除非需要变化,否则都声明为final类型。

安全发布的常用模式

在静态初始化函数中初始化一个对象引用

将对象的引用保存到volatile类型的域或者 AtomicReferance对象 中
将对象的引用保存到某个正确 的构造对象中Final类型域中
将对象 的引用保存到一个由锁保护的域中
java的线程安全库中提供了一下安全发布保证

通过将一个键或者值放入 Hashtable,synchronizedMap或者ConcurrentMap中,可以安全的将它发发布给任何从这个引起窗口中访问它的线程
通过将某个元素放入Vector,CopyOnWriteArrayList,CopyOnWriteArraySet.synchronizedList或synchronizedSet中,可以将元素安全的发布
通过 将某个元素放在BLockingQueue或者ConcurrentLinkedQueue中。可以将元素安全的发布
通常要发布一个静态构造的对象 ,最简单和最安全的方式就是使用静态的初始化容器

静态初始化器由JVM在类的初始化阶段执行,由于在jvm内部存在着同步机制,因为通过 这种方式初始化的任何对象 都可以安全的发布。换句话说,就是声明为静态类型的具有最高优先级。

参考资料《java 并发编程实战》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 多线程 并发