并发编程-进阶

 
强制线程到共享内存中读取数据,而不是从线程的工作空间的读取数据,从而可以可以使变量在多线程间可见
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值,第二部赋值,第三部放回内存
  1. 比如有一个i值为10在自增操作
  2. 有线程A与线程B两个线程
  3. 在线程A执行i++语句时会想从内存中获取i的值,就在这时线程B也执行i++语句,B也从内存中获取值,
  4. 然后线程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高

 
  • 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);
    }

 

posted @ 2019-04-10 10:45  huxuekuo  阅读(247)  评论(0编辑  收藏  举报