JUC基础之集合类不安全,Callable接口,CountDownLatch,CyclicBarrier,Semaphore常用辅助类,ReadWRiteLock读写锁

List

public class UnsafeList
{
    public static void main(String[] args)
    {
        List list = new ArrayList<>();
        for (int i = 1; i <= 10; i++)
        {
            new  Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },"Thread"+i).start();
        }
    }
}

运行结果:

[2fbaf]
[2fbaf, f9622, 08fdf, 1399d, ba2a9, f0b84]
[2fbaf, f9622, 08fdf, 1399d, ba2a9]
[2fbaf, f9622, 08fdf, 1399d, ba2a9, f0b84, 1b25a, efb3d, 32159]
[2fbaf, f9622, 08fdf, 1399d]
[2fbaf, f9622, 08fdf]
[2fbaf, f9622]
[2fbaf]
[2fbaf, f9622, 08fdf, 1399d, ba2a9, f0b84, 1b25a]
Exception in thread "Thread7" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
    at java.util.ArrayList$Itr.next(ArrayList.java:859)
    at java.util.AbstractCollection.toString(AbstractCollection.java:461)
    at java.lang.String.valueOf(String.java:2994)
    at java.io.PrintStream.println(PrintStream.java:821)
    at com.y1.list.UnsafeList.lambda$main$0(UnsafeList.java:27)
    at java.lang.Thread.run(Thread.java:748)

并发下ArrayList是不安全的
ConcurrentModificationException: 并发修改异常

解决方案一:Vector

//解决方案一
List list = new Vector<>();
for (int i = 1; i <= 10; i++)
{
    new  Thread(()->{
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    },"Thread"+i).start();
}

Vector在jdk 1.0就出来了,而ArrayList在jdk 1.2才出来的
所以:选用Vector代替ArrayList不是最好的方案

解决方案二:Collections.synchronizedList(new ArrayList<>())

//解决方案二
List list = Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i <= 10; i++)
{
        new  Thread(()->{
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    },"Thread"+i).start();
}
 
 

解决方案三:new CopyOnWriteArrayList<>();

写入时复制,这是一种COW思想,一种计算机设计优化策略
多线程调用的时候,List读取的时候是读取固定的,写入的时候复制一份,复制完给调用者,调用者写完再给他放回去,写入时避免覆盖,造成数据问题(一种读写分离的思想)

//解决方案三
List list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 10; i++)
{
    new  Thread(()->{
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    },"Thread"+i).start();
}
 
 

看一下 CopyOnWriteArrayList 的底层 注意这个 volatile

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

为什么要用CopyOnWriteArrayList?
先看一下Vector的源码

public synchronized void addElement(E obj) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = obj;
}

只要有Synchronized的方法效率会非常低,同理Collections.synchronizedList(new ArrayList<>())也是

看一下CopyOnWriteArrayList的源码

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);  //复制
        newElements[len] = e;
        setArray(newElements);  //写回
        return true;
    } finally {
        lock.unlock();
    }
}

用的是Lock锁
Lock和Synchronized的区别

Set

先看一下这个图,List,Set是同级的,是相似的,(还要注意一个同级的BlockingQueue 阻塞队列)

Set的多线程不安全问题以及解决方案

public class UnsafeSet
{
    //HashSet set = new HashSet<>(); 出现ConcurrentModificationException
    //解决方案一       Collections.synchronizedSet(new HashSet<>());
    //解决方案二     CopyOnWriteArraySet set = new CopyOnWriteArraySet();
    public static void main(String[] args)
    {
        HashSet set = new HashSet<>();
        for (int i = 0; i < 10; i++)
        {
            new Thread(()->
            {
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}
 
 

HashSet底层是什么?看源码

public HashSet() {
    map = new HashMap<>();
}

看一下HashSet的add方法

public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}

本质就是map的key,因为map的key是无法重复的
PRESENT:是一个不变的的值

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

HashMap

还是要先看看源码,简单了解一下HashMap

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
    this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

loadFactor:加载因子
initialCapacity:初始容量
HashMap默认


验证不安全:

public static void main(String[] args)
{
    HashMap hashMap = new HashMap<>();
    for (int i = 1; i <= 30; i++)
    {
        new Thread(()->{
            hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,2));
            System.out.println(hashMap);
            },String.valueOf(i)).start();
    }
}

报出并发修改异常 ConcurrentModificationException

解决方案一

Collections.synchronizedMap(new HashMap<>());

发现HashMap没有刚才的CopyOnWrite
看文档



发现了一个 ConcurrentHashMap

解决方案二

Map hashMap = new ConcurrentHashMap<>();

ConcurrentHashMap是什么?

Callable

public interface Callable
返回结果并可能引发异常的任务。 实现者定义一个没有参数的单一方法,称为call 。
Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,A Runnable不返回结果,也不能抛出被检查的异常。
1.可以有返回值
2.可以抛出异常
3.和Runnable方法不同: Runnable:run() Callable:call()

//Runnable
@Override
public void run()
{
}

//Callable
@Override
public Object call() throws Exception
{
    return null;
}

看一下Callable的源码

@FunctionalInterface
public interface Callable {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

范型的参数就是是方法的返回值

但是new Thread() 只能接收Runnable作为参数
所以怎么用?

public class TestCallable
{
    public static void main(String[] args)
    {
        new  Thread(new FutureTask(new MyThread())).start();  
    }
}


class MyThread implements Callable
{
    @Override
    public String call() throws Exception
    {
        return "null";
    }
}

为什么能这么用呢?



适配器思想,FutureTask当作一个适配类

public class TestCallable
{
    public static void main(String[] args) throws ExecutionException, InterruptedException
    {
        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread); //适配类
        new Thread(futureTask,"Y1").start();
        new Thread(futureTask,"Y2").start();    //结果会被缓存,效率高
        Object o = (String)futureTask.get();  //获取Callable返回结果,肯能会产生阻塞
        System.out.println(o);
    }
}


class MyThread implements Callable
{
    @Override
    public String call() throws Exception
    {
        System.out.println("Y1”);
        return "Callable";
    }
}

futureTask.get();
可能会产生阻塞,因为它需要等待return,如果你的业务方法耗时太久太会一直等,我们要使用异步通信来处理,或者放到代码最后面...
多线程下结果会被缓存,效率高

CountDownLatch 减法计数器

// 减法计数器
public class Demo
{
    public static void main(String[] args) throws InterruptedException
    {
        CountDownLatch countDownLatch = new CountDownLatch(5); //总数是5
        for (int i = 0; i <5; i++)
        {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"被淘汰");
                countDownLatch.countDown(); //  数量 -1
            },String.valueOf(i)).start();
        }
        countDownLatch.await();  //等待计数器归零 再向下执行 否则会一直等待
        System.out.println("所有人都被淘汰");
    }
}

原理:
countDownLatch.countDown();
countDownLatch.await();
每次有线程调用countDown时 数量-1。计数器变为0时countDownLatch.await(); 会被唤醒,执行之后的代码,否则等待唤醒

CyclicBarrier 加法计数器

//加法计数器
public class Demo
{
    public static void main(String[] args)
    {
        //消费十次免单
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{   // params:数量 , Runnable
            System.out.println("消费10次免单");
        });

        for (int i = 1; i <=10; i++)
        {
            final int temp = I;  //声明成员变量
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"消费了"+temp+"次");
                try
                {
                    cyclicBarrier.await();  //等待到指定数量时,会开启一个线程执行方法
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
                catch (BrokenBarrierException e)
                {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Semaphore 信号量

public class Demo
{
    public static void main(String[] args)
    {
        Semaphore semaphore = new Semaphore(3); //许可证数量


        for (int i = 1; i <7 ; i++)
        {
            new Thread(()->{
                try
                {
                    semaphore.acquire();  //acquire() 获得
                    System.out.println(Thread.currentThread().getName()+"抢到许可证");
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println(Thread.currentThread().getName()+"释放许可证");
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }finally
                {
                    semaphore.release();    //release() 释放
                }
            },String.valueOf(i)).start();
        }


    }
}

运行结果:

1抢到许可证
3抢到许可证
2抢到许可证
3释放许可证
1释放许可证
2释放许可证
5抢到许可证
4抢到许可证
6抢到许可证
5释放许可证
4释放许可证
6释放许可证

原理:
acquire() 获得许可证 ,如果许可证全部被拿走,等待到释放为止
release() 释放当前信号量,会使当前可用信号量+1,然后唤醒等待线程
作用:多个共享资源的互斥使用,并发限流,控制最大的线程数

ReadWRiteLock 读写锁(共享锁,独占锁)

读可以被多线程同时读,写的时候只能一个线程写,提高了效率

public class Demo
{
    public static void main(String[] args)
    {
        MyCache cache = new MyCache();


        //写入
        for (int i = 1; i <6 ; i++)
        {
            final int temp = i;
            new Thread(()->{
                cache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }


        //读取
        for (int i = 1; i <6 ; i++)
        {
            final int temp = i;
            new Thread(()->{
                cache.get(temp+"");
            },String.valueOf(i)).start();
        }


    }
}


/*
    自定义缓存
     */
class  MyCache
{
    private  volatile Map map = new HashMap<>();
    //读写锁,更加细粒度的控制
    private ReadWriteLock readWriteLock =  new ReentrantReadWriteLock();


    //存,写
    public  void  put(String key , Object value)
    {
        readWriteLock.writeLock().lock();  //加写锁
        try
        {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "写入OK");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            readWriteLock.writeLock().unlock();  //解锁
        }
    }


    //取,读
    public void get(String key)
    {
        readWriteLock.readLock().lock();  //加读锁 为了防止在写之前就读  也是为了读的时候不存在写
        try
        {
            System.out.println(Thread.currentThread().getName() + "读取" + key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取"+o+"ok");
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            readWriteLock.readLock().unlock();  //解锁
        }
    }
}

你可能感兴趣的:(JUC基础之集合类不安全,Callable接口,CountDownLatch,CyclicBarrier,Semaphore常用辅助类,ReadWRiteLock读写锁)