1,当中主要是多线程共享数据情境下的基本概念介绍
Java面向对象编程更多的是对共享对象的访问
一:对象的发布与逸出
发布:
使对象在当前使用域之外的地方可以使用该对象,可以将对象的引用保存到其他代码可以访问的地方(例如非私有变量/静态变量),使用非私有方法返回对象的引用–提供了一个可以访问对象的方法/修改对象可变状态的方法。
1-1
//例如
public class Test{
public Object obj;
public Object getObj(){
return new Test();
}
public void setObj(){
obj = new Test();
}
}
有时候我们不需要将对象发布出去,有时候我们又需要将对象发布,此时就需要一定的同步处理
逸出:
当对象不需要溢出,但是却发布时就叫做:逸出
常见的逸出:this 逸出
1-2
//1. 不安全的构造函数this 逸出
//2. 内部类,隐式this 逸出
public class Outer {
private int id;
public class Inter{
public void get(){ //在这个类中传递了外部类的this 对象,可以修改外部类的状态
return id;
}
}
}
当发布一个对象时,这个对象的所有非私有引用的多有对象都会被发布,当将一个对象传递给某个pubic 方法时,就相当于这个对象被发布,
二:封闭性
当访问可共享的可变数据时,通常需要同步。一种避免使用同步的方法就是不共享数据,仅仅单线程内访问数据。
线程封闭
常见的例子:JDBC 中的Connection对象,JDBC并不要求Connection时线程安全的,线程直接从连接池中获取一个connection 实例,使用完之后再将对象返回给连接池,在连接没有返回时,连接池不会给其他线程分配该连接对象,,这样就将Connection隐式的封装在了线程中,Java中支持线程封闭的机制:局部变量,ThreadLocal 类
栈封闭:
只能通过局部变量来访问对象,方法执行完成,出栈之后栈中的局部变量都会被回收。
1-3
public void test(){
//栈封闭,要保证栈中的对象不会逸出就行
Animal animal = new Animal();
}
ThreadLocal 类:
这是一个Java本身自带的类,这个类能使线程中的某个值和需要保存的值联系起来(值保存在线程中),每个线程操作自己的副本数据,相当于给当前线程创建的局部变量(栈封闭的局部变量是方法中的),线程结束相关数据就会被回收
Thread Local 一般用来防止单实例(一个类只有一个实例)变量的访问,例如:
JDBC中为了在调用每个方法时都要传递connection 对象,所以将connection 对象保存在线程中
1-4
public class TestDaoNew{
private static ThreadLocal<COnnection> connThread = new ThreadLocal<Connection>();
//将连接保存在线程中
public static Connection getConnection(){
if(connThread.getConnection()==null){
Connection conn = new Connection(...);
connThread.set(conn);
}
}
//释放连接资源
public void cleanConnection(){
getConnection().clean(); //直接返回线程中保存的连接
}
}
数据库操作中的事务的上下文也可以保存在线程中。
ThreadLocal 可以看作一个<Thread , E> 的结构,保存当前线程和对象。
三:不变性
数据是共享的但是数据无法被修改/它的状态只能由自己的构造函数决定,这也是线程安全的(不变对象一定是线程安全的)
不变对象的特点:
- 安全的构造,在构造的过程中不会出现逸出
- 所有的域都使用final 修饰
注意:不可变的对象和不可变的对象引用是有区别的
虽然不变性不存在修改出现的不安全,但是依然存在可见性问题,对象构造完成后其他的线程也可能无法看见对象的状态,所以还是需要Volatile 的修饰
public class Cache{
private final int a;
private final int b;
public Cache(int x,int y){
a = x;
b = y;
}
}
private volatile Cache cache = new Cache(0,0);
四:对象的安全发布
首先确保不是逸出
不安全发布的例子:
1-5
public Holder holder;
public void initialize(){
holder = new Holder(...);
}
为什么说这个holder 对象的发布是不安全的,首先他没有逸出。但是在多线程的情况下,存在可见性的问题,其他线程看见的Holder 的对象的状态是不定的,所以这样不正确的发布可能导致其他线程看见尚未成功创建的对象
所以在对象的发布中/构造中。要保证对象的安全发布
-
在类的静态初始化函数中发布该对象
-
将对象的引用保存到Volatile 修饰的域中
1-6 public volatile Holder holder; public void initialize(){ holder = new Holder(...); } //这样的发布就是正确的
-
这个对象为不可变对象,那么就算是1-5 的构造过程,发布也是安全的,这是因为JVM对不可变对象的特殊处理,保证了构造的安全
-
将对象的引用保存在一个由锁保护的域中。