多线程进阶(CountDownLatch,死锁,线程安全集合类)

6:同步工具CountDownLatch

CountDownLatch:等待多个任务执行完毕

构造方法:给定计数

 	public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

主要方法:

	//减少锁存器的计数,如果到0释放所有等待进程
	public void countDown() {
        sync.releaseShared(1);
    }
    //等到当前锁存器计数到0
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

实例:

	private static CountDownLatch count=new CountDownLatch(5);
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(Thread.currentThread().getName()+"正在执行");
                        Thread.sleep(3000);
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        System.out.println(Thread.currentThread().getName()+"执行完毕");
                        count.countDown();
                    }
                }
            }).start();
        }
        try {
            //直到前面五个线程执行完毕,再执行下面的语句
            count.await();
            System.out.println("所有线程执行完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
Semaphore CountDownLatch
Seamphore资源可加可减 CountDownLatch只能减
适用于执行多次 适用于执行一次

7:死锁

双方都持有对方所需要的锁,双方无限期等待对方释放锁。

死锁产生条件:

  1. 互斥使用
  2. 不可抢占
  3. 保持和请求
  4. 循环等待

检测死锁:

方式1:jconsole检测死锁
多线程进阶(CountDownLatch,死锁,线程安全集合类)_第1张图片

方式二:JPS

首先使用Jps查看Java进程编号,然后使用Jstack查看进程信息,出现下述信息表示出现了死锁。Jstack会在最后给出进程的分析信息,表示出现了死锁。

多线程进阶(CountDownLatch,死锁,线程安全集合类)_第2张图片

8:线程安全集合类

线程不安全:

  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder

线程安全:

  • Vector (不推荐使用)
  • HashTable (不推荐使用)
  • ConcurrentHashMap
  • StringBuffer

不涉及修改的操作,线程安全的:

  • String

List线程安全相关:

  • 方式一:利用Collections的synchronizedXXX生成线程安全的集合类,内部使用synchroniezd关键字确保线程安全,效率不高

    • List<Object> objects = Collections.synchronizedList(new ArrayList<>());
      
  • 方式二:CopyOnWriteArrayList<>

    • CopyOnWrite容器即写时复制的容器,效率比同步的arrayList要高,但占用空间大

    • 添加/删除元素时复制新容器,向新容器里面添加/删除元素,添加/删除完毕后,将原容器的引用指向新的容器

    • copyonwrite容器是一种读写分离的思想,读和写在不同的容器

    • 当某个线程写的时候,其它线程可以从原容器中读,这样实现了无需锁的并发读操作

    • CopyOnWriteArrayList<Integer> arrayList=new CopyOnWriteArrayList<>();
      

Collections.synchronizedList实例:

 	private static List<Integer> arrayList=Collections.synchronizedList(new ArrayList<>());
    private static CountDownLatch count=new CountDownLatch(100);

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<100;i++){
            final int j=i;
			//100个线程并发写操作,Collections.synchronizedList保证数据都写入
            new Thread(new Runnable() {
                @Override
                public void run() {
                    arrayList.add(j);
                    count.countDown();
                }
            }).start();
        }

        count.await();
        System.out.println(arrayList.size());
    }

CopyOnWriteArrayList实例:

	private static CopyOnWriteArrayList<Integer> copyOnWriteArrayList=new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws InterruptedException {
        //10个线程并发读写操作
        for(int i=0;i<10;i++){
            final int j=i;

            new Thread(new Runnable() {
                @Override
                public void run() {
                    copyOnWriteArrayList.add(j);
                    System.out.println(Thread.currentThread().getName()+"修改后的List的哈希值为"+copyOnWriteArrayList.hashCode());
                    for(Integer integer:copyOnWriteArrayList){
                        System.out.println(integer);
                    }
                }
            }).start();

        }

        Thread.sleep(4000);
        System.out.println(Arrays.toString(copyOnWriteArrayList.toArray()));

    }

Queue线程安全相关:

方式1:基于数组的阻塞队列,需要指定队列大小

ArrayBlockingQueue<Integer> arrayBlockingQueue=new ArrayBlockingQueue<>(1);

方式2:基于链表的阻塞队列,不设置则是无限大小(可能发生内存溢出)

LinkedBlockingQueue<Integer> linkedBlockingQueue=new LinkedBlockingQueue<>();

方式3:基于堆的优先级阻塞队列

PriorityBlockingQueue<Integer> priorityBlockingQueue=new PriorityBlockingQueue<>();

方式4:只包含一个元素的阻塞队列

TransferQueue<Integer> transferQueue = new LinkedTransferQueue<>();
	//100个线程向队列写数据
	private  static ArrayBlockingQueue<Integer> arrayBlockingQueue=new ArrayBlockingQueue<>(100);
    private static CountDownLatch countDownLatch=new CountDownLatch(100);

    public static void main(String[] args) {
        for(int i=0 ;i <100 ; i++){
            final int j=i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    arrayBlockingQueue.add(j);
                    countDownLatch.countDown();
                }
            }).start();
        }

        try {
            countDownLatch.await();
            System.out.println(arrayBlockingQueue.size());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

哈希表线程安全相关:

方式1:Hashtable,数组+链表,特点是方法添加了synchronized保证线程安全,效率及其低下,无论读写都整个数组上锁。key不为null

private static Hashtable<String,String> hashtable=new Hashtable<>();

方式2:ConcurrentHashMap<>,并发的hashmap,效率高,线程安全,只锁Node节点。key不为null

ConcurrentHashMap和Node中属性使用volatile保证可见性,禁止指令重排序/建立内存屏障

private static ConcurrentHashMap<String,Integer> concurrentHashMap=new ConcurrentHashMap<>();

JDK1.8的ConcurrentHashMap底层解析:

底层结构:数组+链表+红黑树

读读操作:不加锁,可以并发

读写操作:不加锁,可以并发

写写操作:put(key,value)

  • 计算哈希值,确认索引位置,为当前索引位置的Node加锁
    • 如果Node无元素,使用CAS+自旋的方式插入元素(此位置无元素,线程冲突机率小)
    • 如果Node有元素,使用Synchronized加锁的方式插入元素
  • 扩容线程
    • 创建一个新的数组,同时将部分元素复制到新的数组
    • 扩容期间新老数组都存在,后续的每个操作线程都会复制一部分元素到新数组
    • 直到全部复制完成后删除老数组
    • 在扩容期间,查询元素从新老数组查询,插入元素只到新数组

JDK1.7的ConcurrentHashMap底层解析:

底层结构:数组+链表

使用锁分段技术(segment)

简单的说就是将若干个Node结合为一个Segment分段,使用ReentrantLock对每一个段加锁,目的为了降低竞争锁的概率,当线程访问的数据恰好在一个Segment分段上才进行竞争锁。

ConcurrentHashMap实例:

	private static ConcurrentHashMap<Integer,Integer> concurrentHashMap=new ConcurrentHashMap<>();
    public static void main(String[] args) throws InterruptedException {
        //100个线程的读写操作
        for(int i=0;i<100;i++){
            final int j=i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    concurrentHashMap.put(j,j);
                    Set<Map.Entry<Integer, Integer>> entries = concurrentHashMap.entrySet();
                    for(Map.Entry<Integer, Integer> entry:entries){
                        System.out.println(entry.getKey()+"  "+entry.getValue());
                    }
                }
            }).start();
        }

        Thread.sleep(5000);
        System.out.println(concurrentHashMap.size());
    }

你可能感兴趣的:(JavaSE高级,java,开发语言)