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
解决方案三:new CopyOnWriteArrayList<>();
写入时复制,这是一种COW思想,一种计算机设计优化策略
多线程调用的时候,List读取的时候是读取固定的,写入的时候复制一份,复制完给调用者,调用者写完再给他放回去,写入时避免覆盖,造成数据问题(一种读写分离的思想)
//解决方案三
List
看一下 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
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(); //解锁
}
}
}