线程安全
一、类的线程安全定义
(Doung Lee)如果多线程下使用这个类,不管多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。
类的线程安全表现为:
操作的原子性
内存的可见性
不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。
二、怎么才能做到类的线程安全
1.栈封闭
所有的变量都是在方法内部声明的,这些变量都处于栈封闭状态(这样的话,就是安全的,因为方法是在栈针里面的,栈针又是线程级别的)。
2.无状态
没有任何成员变量的类,就叫无状态的类。
3.让类不可变
让状态不可变,两种方式:
1). 加final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。
我们需要注意到的是,当成员变量被final修饰的时候,就只能有get()方法,没有set()方法;(例子:比如ConcurrentHashMap中的Node类)
看一个代码事例:
public class ImmutableFinalRef { private final int a; private final int b; private final User user;//这里,就不能保证线程安全啦 public ImmutableFinalRef(int a, int b) { super(); this.a = a; this.b = b; this.user = new User(); } public int getA() { return a; } public int getB() { return b; } public User getUser() { return user; } public static class User{ private int age; public User(int age) { super(); this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public static void main(String[] args) { ImmutableFinalRef ref = new ImmutableFinalRef(12,23); User u = ref.getUser(); //u.setAge(35); } }
看到上面的代码的时候,我们注意到在成员变量的对象,还是会被赋值的。只要下面的方式才行:
package com.xiangxue.ch7.safeclass; /** *@author Mark老师 享学课堂 https://enjoy.ke.qq.com * *类说明:看起来不可变的类,实际是可变的 */ public class ImmutableFinalRef { private final int a; private final int b; private final User user;//这里,就不能保证线程安全啦 public ImmutableFinalRef(int a, int b) { super(); this.a = a; this.b = b; this.user = new User(); } public int getA() { return a; } public int getB() { return b; } public User getUser() { return user; } public static class User{ private final int age; public User(int age) { super(); this.age = age; } public int getAge() { return age; } } public static void main(String[] args) { ImmutableFinalRef ref = new ImmutableFinalRef(12,23); User u = ref.getUser(); //u.setAge(35); } }
2). 根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值。
看例子。其实在成员变量加上final,是最好的
public class ImmutetableToo { private List<Integer> list = new ArrayList<>(3); public ImmutetableToo() { list.add(1); list.add(2); list.add(3); } public boolean isContains(int i) { return list.contains(i); } }
4.使用不可变的类
不可变类(Immutable Objects):当类的实例一经创建,其内容便不可改变,即无法修改其成员变量。
Java 中八个基本类型的包装类和 String 类都属于不可变类,而其他的大多数类都属于可变类。
5.volatile
保证类的可见性,最适合一个线程写,多个线程读的情景,(比如ConcurrentHashMap)
6.安全的发布
这个时候我们可以和 3 中的2 进行对比
public class UnsafePublish { //要么用线程的容器替换 //要么发布出去的时候,提供副本,深度拷贝 private List<Integer> list = new ArrayList<>(3); public UnsafePublish() { list.add(1); list.add(2); list.add(3); } //讲list不安全的发布出去了 public List<Integer> getList() { return list; } //也是安全的,加了锁-------------------------------- public synchronized int getList(int index) { return list.get(index); } public synchronized void set(int index,int val) { list.set(index,val); } }
7.使用TheadLocal