强制线程到共享内存中读取数据,而不是从线程的工作空间的读取数据,从而可以可以使变量在多线程间可见
volatile无法保证原子性,volatile属于轻量级的同步性能比synchronized强很多(不加锁),但只能保证变脸在线程间的可见性,不能代替synzhronized的同步功能,netty框架大量使用了volatile关键字
static是保证唯一性,不保证一致性,多个实例共享一个变量
比如: private static int a;
也就是说如果多个对象实例共享一个a属性,当一个对象实例改变了a属性值其他实例也就改变了
volatile保证一致性,不保证唯一性,多个实例多个volatile变量
比如: private volatile int a;
当多个类对象实例每个对象中a是不一样的,当其中一个改变其他的是不会改变的,volatile的唯一性是在并发线程是多个线程间使用一个对象的a变量的一致性
不能保证原子性是比如用volatile修饰的变量在自增的操作中i++中分三步,第一步读取i值,第二部赋值,第三部放回内存
- 比如有一个i值为10在自增操作
- 有线程A与线程B两个线程
- 在线程A执行i++语句时会想从内存中获取i的值,就在这时线程B也执行i++语句,B也从内存中获取值,
- 然后线程A执行执行++操作,B也执行++操作,同时返回内存中是,其实只是+1
1 public class ThreadDemo09 implements Runnable {
2
3 private static volatile int sum = 0;
4
5 public static void add() {
6 System.out.println(Thread.currentThread().getName() + "循环前sum值" + sum);
7 for (int i = 0; i < 10000; i++) {
8 sum++;
9 }
10 System.out.println(Thread.currentThread().getName() + "循环后sum值" + sum);
11 }
12
13 @Override
14 public void run() {
15 add();
16 }
17
18 public static void main(String[] args) {
19 ExecutorService es = Executors.newFixedThreadPool(10);
20 for (int i = 0; i < 10; i++) {
21 es.submit(new ThreadDemo09());
22 }
23 es.shutdown();
24 while (true) {
25 if (es.isTerminated()) { // 判断线程是否消亡
26 if (sum == 100000) {
27 System.out.println(sum + "=ok");
28 } else {
29 System.out.println(sum + "=no");
30 }
31 break;
32 }
33 }
34 }
35 }
使用AtomicInteger等原子类可以保证变量的原子性,但是不能保证成员方法的原子性Atomic类采用CAS这种非锁机制
1 private static AtomicInteger sum = new AtomicInteger(0);
2
3 public static void add() {
4 sum.addAndGet(1);
5 try {
6 Thread.sleep(1000);
7 } catch (InterruptedException e) {
8 e.printStackTrace();
9 }
10 sum.addAndGet(9);
11 System.out.println(Thread.currentThread().getName() + "---> sum=" + sum);
12 }
13
14 public static void main(String[] args) {
15 ExecutorService es = Executors.newFixedThreadPool(10);
16 for (int i = 0; i < 10; i++) {
17 es.submit(new ThreadDemo10());
18 }
19 es.shutdown();
20 }
21
22 @Override
23 public void run() {
24 add();
25 }
使用ThreadLocal维护变量时,ThreadLocal给每个使用该变量的线程一个变量副本,所以每一个线程都可以独立修改变量内容,但是不会影响其他线程使用该变量
final static ThreadLocal<Integer> local = new ThreadLocal<Integer>();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
local.set(100);
System.out.println(Thread.currentThread().getName() + " localSet=" + local.get());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " localGet=" + local.get());
}, "t1").start();
Thread.sleep(1000);
new Thread(() -> {
Integer integer = local.get();
System.out.println(Thread.currentThread().getName() + " localSet=" + integer);
local.set(200);
System.out.println(Thread.currentThread().getName() + " localGet=" + local.get());
}, "t2").start();
}
Vector,HashTable都是古老的并发容器,都是使用Collections.synchronizedXXX()工厂方法创建的,并发状态下只能有一个线程访问对象,性能极低,
collections可以将不是线程安全的集合变为线程安全的,就是在collections工具类中有每个集合的静态同步类,比如list,在collection有一个synchronizedList的静态内部类,该类实现了list接口同时继承synchronizedCollections类在这个,但是本质上还是使用的list的方法,只是在方法外包裹了一层synchronized锁
public static void main(String[] args) {
// final List<String> list = new ArrayList<>();
List<String> list = Collections.synchronizedList(new ArrayList<String>());
ExecutorService es = Executors.newFixedThreadPool(100);
for (int i = 0; i < 10000; i++) {
es.execute(()->{
list.add("5");
});
}
es.shutdown();
while(true) {
if(es.isTerminated()) {
if(list.size() >= 10000) {
System.out.println("线程安全");
}else {
System.out.println("线程不安全");
}
System.out.println(list.size());
break;
}
}
}
JDK5.0以后有很多并发类比如concurrentMap属于并发类容器,vector与hashtable属于同步类容器,是将整个容器加锁,而concurrentMap是将需要操作的一个区域加锁,这样性能就大大的提高了
ConcurrentHashMap代替HashMap,HashTable
ConcurrentSkipListMap 代替了 TreeMap
ConcurrentHashMap将hash表分为16个segment(段),每个segment单独进行锁控制,从而减小了锁的粒度,提升了性能
ConcurrentHashMap
ConcurrentHashMap替换了 HashMap 与 HashTable
HashMap是线程不安全的,性能高.
HashTable是线程安全的,性能低
ConcurrentHashMap 线程安全,性能比HashTable高
性能比较
HashMap > ConcurrentHashMap > HashTable
public static void TestMap() {
/**
* HashTable是同步类容器,ConcurrentHashMap并发类容器 同步类容器将整个容器加锁,性能低,
* 并发类容器是将操作那个位置,给那个位置加锁.
*/
// 性能最高,不安全
// HashMap<String,Integer> map = new HashMap<String, Integer>();
// 在安全的情况下,性能高
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
// 线程安全性能低
// Hashtable<String, Integer> map = new Hashtable<String, Integer>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
long start = System.currentTimeMillis();
for (int j = 0; j < 1000000; j++) {
map.put(("a" + j), j);
}
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "---------)" + (end - start));
}, "T" + i).start();
}
}
ConcurrentHashMap新方法
public static void TestMap1() {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<String, Integer>();
map.put("a", 1);
map.put("b", 1);
map.put("c", 1);
//key存在则替换
map.put("a", 2);
//key存在则不替换
map.putIfAbsent("b", 2);//putIfAbsent方法存在不替换,但是
System.out.println(map.toString());
}
ConcurrentSkipListMap'
ConcurrentSkipListMap替换与storedMap对比
public static void TestMap1() {
//性能低,线程安全
// SortedMap<String, Integer> map = Collections.synchronizedSortedMap(new TreeMap<String, Integer>());
//线程安全,性能高
ConcurrentSkipListMap<String,Integer> map = new ConcurrentSkipListMap<String,Integer>();
//线程不安全,性能高
// final SortedMap<String, Integer> map = new TreeMap<String, Integer>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
long start = System.currentTimeMillis();
for (int j = 0; j < 100000; j++) {
map.put("a" + j, j);
}
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "--->" + (end - start));
}, "T" + i).start();
}
}
ConcurrentSkipListMaP新方法
public static void TestSkipListMap1() {
ConcurrentSkipListMap<String,Integer> map = new ConcurrentSkipListMap<String,Integer>();
map.put("a", 1);
map.put("b1", 1);
map.put("c", 1);
//key存在则替换
map.put("a", 2);
//key存在则不替换
map.putIfAbsent("b", 2); //并进行了排序
System.out.println(map.toString());
}
COW并发类容器
Copry on Write 容纳简称COW;
写时复制容器,向容器中添加元素时,先将容器进行Copy出一个新容器,然后将元素添加到新容器中,再将原容器中的引用指向新容器,并发读的时候不需要锁定容器,因为原容器没有变化,使用读写分离的思想,由于每次更新数据都会创建一个新容器,所以数据量较大并且频繁更新则对内存消耗很高,建议在高并发读的场景下使用
CopyOnWriteArraySet是基于CopyOnWriteArrayLiet实现的,其唯一的不同是add时调用的时候,CopyOnWriterArrayList的addIfAbsent方法同样采用锁保护,并创建一个新的大小+1的Object数组,遍历当前数组,如果Object中有当前元素,直接返回不添加,如果没有这个元素就添加到末尾,并返回,但CopyOnWriteArraySet 需要每次add都需要循环遍历,所以效率没有CopyOnWriteArrayList高
public static void TestCOWArray() {
CopyOnWriteArrayList<String> cowArray = new CopyOnWriteArrayList<String>();
cowArray.add("1");
cowArray.add("2");
cowArray.add("3");
cowArray.add("4");
cowArray.add("4");
// 假如存在则不添加,不存在添加
cowArray.addIfAbsent("2");
cowArray.addIfAbsent("5");
System.out.println(cowArray.toString());
}
//源码
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray(); //获取当前数组
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* A version of addIfAbsent using the strong hint that given
* recent snapshot does not contain e.
*/
//添加一个数据需要查询数组是否有要添加的数据,有则不添加,没有则添加到末尾
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public static void TestCOWSet() {
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<String>();
set.add("1");
set.add("2");
set.add("3");
set.add("4");
System.out.println(set);
set.add("4");
// 假如存在则不添加,不存在添加
set.add("5");
System.out.println(set);
}
//CopyOnWriteArraySet源码
//CopyOnWriteArraySet 中使用的就是CopyOnWriteArrayList
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private static final long serialVersionUID = 5457747651344034263L;
private final CopyOnWriteArrayList<E> al;
//add方法时用的al的addIfAbsent方法每次添加都需要遍历
public boolean add(E e) {
return al.addIfAbsent(e);
}