文章目录
容器
- 容器的示意图
CopyOnWriteArrayList
-
下面我们来描述下List下的CopyOnWriteArrayList
-
简介
是一种用于程序设计中的优化策略,当某个人要修改这个内容的时候,才会真正把内容copy出去形成新的内容然后在改。
- 作用是什么
CopyOnWrite容器即写时复制容器。通俗的理解当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将容器进行Copy复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器并发的读,而不需要加锁。因为当前容器不会添加任何元素。
- 应用的场景
写的时候添加锁,读的时候不添加锁 读多写少的时候使用
- 案例:
public class CopyOnWriteList {
public static void main(String[] args) {
List<String> lists = new CopyOnWriteArrayList<String>();
Random r = new Random();
Thread[] threads = new Thread[100];
for(int i=0; i<threads.length; i++){
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i=0; i<1000;i++){
lists.add("a"+r.nextInt(10000));
}
}
};
threads[i]= new Thread(runnable);
}
runAndComputeTime(threads);
System.out.println(lists.size());
}
private static void runAndComputeTime(Thread[] threads) {
long start = System.currentTimeMillis();
Arrays.asList(threads).forEach(t->t.start());
Arrays.asList(threads).forEach(t-> {
try {
t.join();
} catch (InterruptedException e) {
e.getMessage();
}
});
long end = System.currentTimeMillis();
System.out.println("消耗的时间为:"+(end-start));
}
}
- 来说说Collecttion下的Queue
public interface Queue<E> extends Collection<E> {
boolean add(E var1);
boolean offer(E var1); //相当于list中的add,添加成功返回true
E remove();
E poll(); //取完值并且remove移除掉
E element();
E peek(); //取完值不会remove掉
}
LinkedBlockingQueue
- 知识点一
- LinkedBlockingQueue
内部由单链表实现,只能从头部获取元素,从尾部添加元素。添加元素和获取元素都有独立的锁,是读写分离的。采用可重入锁来保证并发的情况下线程安全。
- LinkedBlockingQueue一共由三个构造器,无参构造器,指定容量的构造器,可以传入容器的构造器,如果为无参的构造器可能会出现内存溢出的现象。
public LinkedBlockingQueue(); //设置容量为Integer.MAX
public LinkedBlockingQueue(int capacity); //设置指定容量
public LinkedBlockingQueue(Collection<? extends E> c); //穿入一个容器,如果调用该构造器,容量默认也是Integer.MAX_VALUE
-
取数据
take():首选。当队列为空时阻塞。
poll():弹出列顶元素,队列为空时,返回空。
peek():返回列顶元素,但元素不弹出。队列为空的时候,返回null。
remove():移除某个元素,队列为空的时候抛出异常,成功返回true。 -
添加元素
put():首选。队满是阻塞
offer():队满返回false -
案例
public class LinkedBlockingQueueDemo {
static BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>();
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<100;i++){
try {
blockingQueue.put("a"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"p1").start();
//起了五个线程
for (int i = 0; i < 5; i++) {
new Thread(() -> {
for (;;) {
try {
System.out.println(Thread.currentThread().getName() + " take -" + blockingQueue.take()); //如果空了,就会等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "c" + i).start();
}
}
}
- 执行结果
....
c3 take -a99
c1 take -a87
c0 take -a86
c2 take -a91
synchronizedMap与correntHashMap
-
接下来我们着重的来说说Map中的并发,Map中HashMap在处理并发的问题是不安全的,而HashTable虽然是安全的,但是效率偏低。而 synchronizedMap和correntHashMap是采用CAS锁机制结构,能大幅度提升性能
-
案例
public class SynchronizedHashMap {
//修改相应的Map类型即可
static Map<UUID, UUID> m = Collections.synchronizedMap(new HashMap<>());
static int count = 1000000;
static UUID[] keys = new UUID[count];
static UUID[] values = new UUID[count];
static final int THREAD_COUNT = 100;
static {
for (int i = 0; i < count; i++) {
keys[i] = UUID.randomUUID();
values[i] = UUID.randomUUID();
}
}
static class MyThread extends Thread {
int start;
int gap = count / THREAD_COUNT;
public MyThread(int start) {
this.start = start;
}
@Override
public void run() {
for (int i = start; i < start + gap; i++) {
m.put(keys[i], values[i]);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
Thread[] threads = new Thread[THREAD_COUNT];
for (int i = 0; i < threads.length; i++) {
threads[i] =new MyThread(i * (count / THREAD_COUNT));
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println(end - start);
System.out.println(m.size());
//-----------------------------------
start = System.currentTimeMillis();
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 10000000; j++) {
m.get(keys[10]);
}
});
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
end = System.currentTimeMillis();
System.out.println(end - start);
}
}
- 运行结果
490
1000000
53443
Executor
- Executor框架的成员及其关系可以用一下的关系图表示:
- 那么在我们开发中一般使用ThreadPoolExecutor类,下面带大家来认识下核心的几大参数
corePoolSize:核心线程
maxPoolSize:最大线程数
keepAliveTime:存活时间
TimeUnit:时间单位
BlockingQueen:任务队列
ThreadFactory:线程工厂
RejectStratege:拒绝策略
- Absort
- DisCard
- DisCardOld
- CallsRuns:谁提交的教给谁处理
ThreadPoolExecutor
-
接下来我们来简单的看看如何使用ThreadPoolExecutor
-
案例演示
public class ThreadExecutorPoolDemo {
//定义一个Runnable
static class Task implements Runnable {
private int i;
public Task(int i) {
this.i = i;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Task " + i);
try {
System.in.read();//阻塞
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "Task{" +
"i=" + i +
'}';
}
}
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(2,
4,
60,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i < 8; i++) {
//定义为8个的原因是,最大线程数为4 + 队列最大可以存4个 为八个 超过八个执行拒绝策略
poolExecutor.execute(new Task(i));
}
System.out.println("在队列中等待的线程数:"+poolExecutor.getQueue());
//测试 当队列都满了 这个教给main方法自己处理
poolExecutor.execute(new Task(100));
System.out.println("main在队列中等待的线程数:"+poolExecutor.getQueue());
boolean shutdown = poolExecutor.isShutdown();
System.out.println(shutdown);
poolExecutor.shutdown();
}
}
- 执行结果
在队列中等待的线程数:[Task{i=2}, Task{i=3}, Task{i=4}, Task{i=5}]
main在队列中等待的线程数:[Task{i=3}, Task{i=4}, Task{i=5}, Task{i=100}]
false
pool-1-thread-1 Task 0
pool-1-thread-2 Task 1
pool-1-thread-3 Task 6
pool-1-thread-4 Task 7
Futrue和FutrueTask
- Futrue接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行结果进行获取(get()),取消(cancle()),判断是否完成等操作。
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}
方法解析:
-
V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成
-
V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
-
boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true
-
boolean isCanceller() :如果任务完成前被取消,则返回true。
-
boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
-
总结
通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。
但是我们必须明白Futrue只是一个接口,我们无法直接创建对象,因此就需要其实现类FutureTask登场。
- 分析
FutureTask除了实现了Futrue还实现了Runnable接口(即可以通过Runnable接口实现线程,也可以通过Futrue获取线程执行后的结果)。因此Futrue也可以直接交给Exectors执行。
public class CallableDemo implements Callable<Integer> {
private int sum;
@Override
public Integer call() throws Exception {
System.out.println("Callable子线程开始计算啦!");
Thread.sleep(2000);
for (int i = 0; i < 5000; i++) {
sum = sum + i;
}
System.out.println("Callable子线程计算结束!");
return sum;
}
public static void main(String[] args) {
//创建线程池
ExecutorService es = Executors.newSingleThreadExecutor();
//创建Callable对象任务
CallableDemo calTask = new CallableDemo();
//使用FutureTask
FutureTask<Integer> futureTask = new FutureTask(calTask);
//提交任务并获取执行结果
//或者使用future方法
//Future<Integer> future = es.submit(calTask);
es.submit(futureTask);
//关闭线程池
es.shutdown();
try {
Thread.sleep(2000);
System.out.println("主线程在执行其他任务");
if (futureTask.get() != null) {
//输出获取到的结果
System.out.println("future.get()-->" + futureTask.get());
} else {
//输出获取到的结果
System.out.println("future.get()未获取到结果");
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("主线程在执行完成");
}
}