Java多线程6-无状态对象

状态变量:实例变量、静态变量

如果一个类的同一个实例被多个线程共享并不会使这些线程存在共享状态,那么这个类及其任意一个实例就被称为无状态对象;反之,如果一个类的同一个实例被多个线程共享,会使这些线程存在共享状态,那么这个类及其任意一个实例就被称为有状态对象

无状态对象不包含任何实例变量,且不包含任何静态变量或者其包含的静态变量都是只读的(常量)

有状态对象分为:状态可变对象、状态不可变对象

无状态对象具有固有的线程安全性,它可以被多个线程共享,而这些线程在执行该对象的任何方法时都无须使用同步机制

即使一个类不包含任何实例变量或者静态变量,执行该类方法的多个线程也仍然可能存在共享状态;因为类方法中可能存在的实例对象中包含有静态变量或实例变量。此时就需要在方法中加锁来保障

托管类:实例的创建、初始化以及销毁的整个生命周期完全由别人控制

不可变对象:一经创建其状态就保持不变的对象,其具有固有的线程安全性,所以也可以像无状态对象那样被多个线程共享

一个严格意义上的不可变对象要同时满足以下所有条件:

a、类本身使用final来修饰:为防止通过创建子类来改变其定义的行为

b、所有字段都是用final修饰:被修饰的字段值不可变,保证初始化安全,对其他线程可见时,它必定是初始化完成的

c、对象在此初始化过程中没有逸出(Escape):防止其他类(如该类的内部匿名类)在对象初始化过程中修改其状态

d、任何字段,若引用了其他状态可变的对象(集合、数组等),则这些字段必须是private修饰的,若有相关方法要返回这些字段值,则应该进行防御性复制

线程特有对象

一个实例只能被一个线程访问的对象就被称为线程特有对象,相应的线程就被称为特有对象的持有线程 

优点:保障对非线程安全对象的访问的线程安全,又避免了锁的开销,有利于减少对象的创建次数

ThreadLocal相当于线程访问其线程特有对象的代理,即各个线程通过这个对象可以创建并访问各自线程特有对象,T指定了相应线程特有对象的类型

其多个线程间与ThreadLocal实例间的访问关系:

ThreadLocal有四个常用方法:get()、set(T value)、initialValue()、remove()InitialValue()方法的返回值成为当前线程的线程特有对象ThreadLocal实现线程安全示例代码:public class ThreadManager extends HttpServlet{final static ThreadLocalSDF = new ThreadLocal(){

@Override

protected SimpleDateFormat initialValue(){

return new SimpleDateFormat("yyyy-MM-dd");

}

};

@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp)

throws ServletException, IOException {

final SimpleDateFormat sdf = SDF.get();

String strExpiryDate = req.getParameter("expirtyDate");

try(PrintWriter pwr = resp.getWriter()){

sdf.parse(strExpiryDate);

} catch(ParseException e) {

e.printStackTrace();

}

}

}

线程局部变量通常是会被声明为某个类的静态变量;ThreadLocal实例通常会被作为某个类的静态字段使用

线程持有对象可能导致的问题及其规避:

a、 退化与数据错乱

b、 ThreadLocal可能内存泄露、伪内存泄露

线程与ThreadLocal、线程特有对象的引用关系

每个Thread实例内部会维护一个类似HashMap的对象:ThreadLocalMap; 每个ThreadLocalMap内部包含若干个Entry(条目,一个键Key,值Value) key是一个ThreadLocal实例,value是一个线程持有对象;Entry可被复用。

由于ThreadLocal可能导致内存泄露,伪内存泄露;需要清理Entry所属线程特有对象,通过在当前线程中调用ThreadLocal.remove()。

线程特有对象的典型应用场景

a、 需要使用非线程安全对象,但又不希望因此而引入锁

b、 使用线程安全对象,但希望避免其使用锁的开销和相关问题

c、 隐式参数传递

d、 特定于线程的单例模式

推荐阅读更多精彩内容