强制线程到共享内存中读取数据,而不是从线程的工作空间的读取数据,从而可以可以使变量在多线程间可见volatile无法保证原子性,volatile属于轻量级的同步性能比synchronized强很多(不加锁),但只能保证变脸在线程间的可见性,不能代替synzhronized的同步功能,netty框架大量使用了volatile关键字
- volatile与static关键字的区别
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,HashTableConcurrentSkipListMap 代替了 TreeMapConcurrentHashMap将hash表分为16个segment(段),每个segment单独进行锁控制,从而减小了锁的粒度,提升了性能ConcurrentHashMapConcurrentHashMap替换了 HashMap 与 HashTableHashMap是线程不安全的,性能高.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高
- 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(); } }
- CopyOnWriteArraySet测试代码
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); }