[TOC]
第5章 安全发布对象
5-1 发布与溢出
- 发布对象:使一个对象能够被当前范围之外的代码所使用
- 对象溢出:一种错误的发布,当一个对象还没有构造完成时,就使它被其他线程所见
不安全的发布
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.machine.concurrency.example.publish;
import com.machine.concurrency.annotations.NotThreadSafe; import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
@Slf4j @NotThreadSafe public class UnsafePublish { private String[] states = {"a","b","c"};
public String[] getStates() { return states; }
public static void main(String[] args) { UnsafePublish unsafePublish = new UnsafePublish(); log.info("{}", Arrays.toString(unsafePublish.getStates()));
unsafePublish.getStates()[0] = "d"; log.info("{}", Arrays.toString(unsafePublish.getStates()));
} }
|
对象溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| package com.machine.concurrency.example.publish;
import com.machine.concurrency.annotations.NotRecommend; import com.machine.concurrency.annotations.NotThreadSafe; import lombok.extern.slf4j.Slf4j;
@Slf4j @NotThreadSafe @NotRecommend public class Escape {
private int thisCannBeEscape = 0;
public Escape(){ new InnerClass(); }
private class InnerClass{ public InnerClass(){ log.info("{}",Escape.this.thisCannBeEscape); } }
public static void main(String[] args) { new Escape(); } }
|
5-2 安全发布对象的4种方法
- 在静态初始化函数中初始化一个对象引用
- 将对象的引用保存到volatile类型域或者AtomicReference对象中
- 将对象的引用保存到某个正确构造对象的final类型域中
- 将对象的引用保存到一个由锁保护的域中
单例模式(懒汉、饿汉、枚举)
以下代码讲述一个懒汉模式逐步优化的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.NotThreadSafe;
@NotThreadSafe public class SingletonExample1 {
private SingletonExample1() {
}
private static SingletonExample1 instance = null;
public static SingletonExample1 getInstance() { if (instance == null) {
instance = new SingletonExample1(); } return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.NotRecommend; import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe @NotRecommend public class SingletonExample3 {
private SingletonExample3() {
} private static SingletonExample3 instance = null;
public static synchronized SingletonExample3 getInstance() { if (instance == null) { instance = new SingletonExample3(); } return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.NotThreadSafe;
@NotThreadSafe public class SingletonExample4 {
private SingletonExample4() {
}
private static SingletonExample4 instance = null;
public static SingletonExample4 getInstance() { if (instance == null) { synchronized (SingletonExample4.class) { if (instance == null) { instance = new SingletonExample4(); } } } return instance; } }
|
懒汉模式 最终解决方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe public class SingletonExample5 {
private SingletonExample5() {
}
private volatile static SingletonExample5 instance = null;
public static SingletonExample5 getInstance() { if (instance == null) { synchronized (SingletonExample5.class) { if (instance == null) { instance = new SingletonExample5(); } } } return instance; } }
|
以下代码讲述一个饿汉模式逐步优化的过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe public class SingletonExample2 {
private SingletonExample2() {
}
private static SingletonExample2 instance = new SingletonExample2();
public static SingletonExample2 getInstance() { return instance; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe public class SingletonExample6 {
private SingletonExample6() {
} private static SingletonExample6 instance = null; static { instance = new SingletonExample6(); }
public static SingletonExample6 getInstance() { return instance; }
public static void main(String[] args) { System.out.println(getInstance().hashCode()); System.out.println(getInstance().hashCode()); } }
|
枚举模式优化过程:最推荐
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.Recommend; import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe @Recommend public class SingletonExample7 {
private SingletonExample7() {
}
public static SingletonExample7 getInstance() { return Singleton.INSTANCE.getInstance(); }
private enum Singleton { INSTANCE;
private SingletonExample7 singleton;
Singleton() { singleton = new SingletonExample7(); }
public SingletonExample7 getInstance() { return singleton; } } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| package com.machine.concurrency.example.singleton;
import com.machine.concurrency.annotations.Recommend; import com.machine.concurrency.annotations.ThreadSafe; import lombok.Getter; import lombok.extern.slf4j.Slf4j;
@Slf4j @ThreadSafe @Recommend public class SingletonExample8 {
private SingletonExample8(){ }
public static SingletonExample8 getInstance(){ return Singleton.INSTANCE.getSingletonExample8(); }
@Getter private enum Singleton{ INSTANCE;
private SingletonExample8 singletonExample8;
Singleton(){ singletonExample8 = new SingletonExample8(); System.out.println("233"); }
}
public static void main(String[] args) {
SingletonExample8.getInstance(); SingletonExample8.getInstance(); } }
|
第6章 线程安全策略
6-1 不可变对象
不可变对象需要满足的条件
- 对象创建以后其状态就不能修改
- 对象所有域都是final类型
- 对象是正确创建的(在对象创建期间,this引用没有逸出)
创建不可变对象的方式(参考String类型)
- 将类声明成final类型,使其不可以被继承
- 将所有的成员设置成私有的,使其他的类和对象不能直接访问这些成员
- 对变量不提供set方法
- 将所有可变的成员声明为final,这样只能对他们赋值一次
- 通过构造器初始化所有成员,进行深度拷贝
- 在get方法中,不直接返回对象本身,而是克隆对象,返回对象的拷贝
final关键字:类、方法、变量
- 修饰类:不能被继承(final类中的所有方法都会被隐式的声明为final方法)
- 修饰方法:1、锁定方法不被继承类修改;2、提升效率(private方法被隐式修饰为final方法)
- 修饰变量:基本数据类型变量(初始化之后不能修改)、引用类型变量(初始化之后不能再修改其引用,但原对象内部的值可以被改变)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| package com.machine.concurrency.example.immutable;
import com.google.common.collect.Maps; import com.machine.concurrency.annotations.NotThreadSafe; import lombok.extern.slf4j.Slf4j;
import java.util.Map;
@Slf4j @NotThreadSafe public class ImmutableExample1 {
private final static Integer a = 1; private final static String b = "2"; private final static Map<Integer, Integer> map = Maps.newHashMap();
static { map.put(1, 2); map.put(3, 4); map.put(5, 6); }
public static void main(String[] args) {
map.put(1, 3); log.info("{}", map.get(1)); }
private void test(final int a) {
} }
|
其他的不可变对象的创建
- Collection.unmodifiableXXX:Clollection、List、Set、Map…
- Guava:ImmutableXXX:Collection、List、Set、Map…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| package com.machine.concurrency.example.immutable;
import com.google.common.collect.Maps; import com.machine.concurrency.annotations.ThreadSafe; import lombok.extern.slf4j.Slf4j; import java.util.Collections; import java.util.Map;
@Slf4j @ThreadSafe public class ImmutableExample2 {
private static Map<Integer, Integer> map = Maps.newHashMap();
static { map.put(1, 2); map.put(3, 4); map.put(5, 6);
map = Collections.unmodifiableMap(map); }
public static void main(String[] args) { map.put(1, 3);
log.info("{}", map.get(1)); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| package com.machine.concurrency.example.immutable;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.machine.concurrency.annotations.ThreadSafe;
@ThreadSafe public class ImmutableExample3 {
private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
private final static ImmutableSet set = ImmutableSet.copyOf(list);
private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);
private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder() .put(1, 2).put(3, 4).put(5, 6).build();
public static void main(String[] args) {
System.out.println(map2.get(3)); } }
|
6-2 线程封闭
把对象封装到一个线程里,只有这个线程能看到这个对象(所以不存在并发下的线程安全,因为只有一个线程操作该对象)
实现线程封闭
Ad-hoc 线程封闭:程序控制实现,最糟糕,忽略
堆栈封闭:局部变量,无并发问题(在方法内部定义局部变量)
ThreadLocal 线程封闭:特别好的封闭方法
ThreadLocal:主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不通的变量值完成操作的场景。
参考:https://www.jianshu.com/p/98b68c97df9b
6-3 线程不安全类与写法
常用的线程不安全类
StringBuilder 线程不安全,StringBuffer线程安全
原因:StringBuffer几乎所有的方法都加了synchronized关键字
为什么需要StringBuilder:由于StringBuffer 加了 synchronized 所以性能会下降很多,所以在堆栈封闭等线程安全的环境下应该首先选用StringBuilder
- SimpleDateFormat
SimpleDateFormat 在多线程共享使用的时候回抛出转换异常,应该才用堆栈封闭在每次调用方法的时候在方法里创建一个SimpleDateFormat
另一种方式是使用joda-time的DateTimeFormatter(推荐使用)
1 2 3
| private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd");
DateTime.parse("20180320",dateTimeFormatter).toDate();
|
ArrayList,HashMap,HashSet等Collections
ArrayList,HashMap,HashSet都不安全
线程不安全写法
先检查再执行
1 2 3 4
| if(condition(a)){ handle(a); }
|
6-4 同步容器
同一接口,不同实现的线程安全类
- ArrayList -> Vector、Stack
- HashMap -> HashTable(key、Value不能为null)
vector的所有方法都是有synchronized关键字保护的
stack继承了vector,并且提供了栈操作(先进后出)
hashtable也是由synchronized关键字保护
Collections.synchronizedXXX (list,set,map)
同步容器并不一定线程安全
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| package com.machine.concurrency.example.syncContainer;
import com.machine.concurrency.annotations.NotThreadSafe; import lombok.extern.slf4j.Slf4j;
import java.util.List; import java.util.Vector;
@Slf4j @NotThreadSafe public class VectorExample {
public static int clientTotal = 5000; public static int threadTotal = 50;
public static List<Integer> list = new Vector<>();
public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { list.add(i); } Thread thread1 = new Thread(() -> { for (int i = 0; i < 10; i++) { list.remove(i); } });
Thread thread2 = new Thread(() -> { for (int i = 0; i < 10; i++) { list.get(i); } });
thread1.start(); thread2.start(); } }
|
在foreach或迭代器遍历的过程中不要做删除操作,应该先标记,然后最后再统一删除
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| package com.machine.concurrency.example.syncContainer;
import java.util.Iterator; import java.util.Vector;
public class VectorExample2 { private static void test1(Vector<Integer> v1) { for(Integer i : v1) { if (i.equals(3)) { v1.remove(i); } } }
private static void test2(Vector<Integer> v1) { Iterator<Integer> iterator = v1.iterator(); while (iterator.hasNext()) { Integer i = iterator.next(); if (i.equals(3)) { v1.remove(i); } } }
private static void test3(Vector<Integer> v1) { for (int i = 0; i < v1.size(); i++) { if (v1.get(i).equals(3)) { v1.remove(i); } } }
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); test1(vector); }
}
|
同步容器也存在很多安全问题,因此java提供了并发容器J.U.C
6-5 并发容器
J.U.C体系:tools、locks、aotmic、collections、executor
6-6 安全共享对象策略-总结
线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它
线程安全对象:一个线程安全的对象或容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它
被守护对象:被守护对象只能通过获取特定的锁来访问