JUC第六课——容器

并发容器

物理结构:只分数组(连续存储)和链表(不连续存储)
逻辑结构:如下
在这里插入图片描述
队列和list的区别是队列实现了很多高并发的方法
Deque:双端队列

HashTable、HashMap、SynchronizedHashMap、ConcurrentHashMap

Vector和HashTable:自带锁,现在基本不用
从HashTable发展到HashMap、SynchronizedHashMap、ConcurrentHashMap

测试HashTable

package character06;

/**
 * @author laimouren
 */
public class Constants {
    public static final int COUNT = 1000000;
    public static final int THREAD_COUNT = 100;
}
package character06;

import java.util.*;

/**
 * 有一个HashTable,100个线程负责1000000个数据,每个线程都往HashTable中插入数据,测试它的性能
 * @author laimouren
 */
public class T01_TestHashTable {

    static Hashtable<UUID, UUID> m=new Hashtable<>();

    static int count=Constants.COUNT;
    static UUID[] keys=new UUID[count];
    static UUID[] values=new UUID[count];
    static final int THREAD_COUNT=Constants.THREAD_COUNT;
	//先准备好测试用例,保证内容相同
    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) {
        System.out.println("插入数据性能测试");
        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());
        
        //-----------------------------
        //测试读数据
        System.out.println("读数据性能测试");
        start=System.currentTimeMillis();
        for(int i=0;i<threads.length;i++){
            threads[i]=new Thread(()->{
                for(int j=0;j<1000000;j++){
                    m.get(keys[10]);//读取keys[10]100000次
                }
            });
        }
        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);

    }
}

测试SynchronizedHashMap

给HashMap加锁

package character06;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author laimouren
 */
public class T03_TestSynchronizedHashMap {
    static Map<UUID, UUID> m= Collections.synchronizedMap(new HashMap<UUID,UUID>());
    static int count=Constants.COUNT;
    static UUID[] keys=new UUID[count];
    static UUID[] values=new UUID[count];
    static final int THREAD_COUNT=Constants.THREAD_COUNT;
    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) {
        System.out.println("插入数据性能测试");
        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());
        //--------------测试读数据
        System.out.println("读数据性能测试");
        start=System.currentTimeMillis();
        for(int i=0;i<threads.length;i++){
            threads[i]=new Thread(()->{
                for(int j=0;j<1000000;j++){
                    m.get(keys[10]);//读取keys[10]100000次
                }
            });
        }
        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);

    }
}

测试ConcurrentHashMap

package character06;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author laimouren
 */
public class T04_TestConcurrentHashMap {
    static Map<UUID,UUID> m=new ConcurrentHashMap<>();
    static int count=Constants.COUNT;
    static UUID[] keys=new UUID[count];
    static UUID[] values=new UUID[count];
    static final int THREAD_COUNT=Constants.THREAD_COUNT;
    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) {
        System.out.println("插入数据性能测试");
        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());
        //--------------测试读数据
        System.out.println("读数据性能测试");
        start=System.currentTimeMillis();
        for(int i=0;i<threads.length;i++){
            threads[i]=new Thread(()->{
                for(int j=0;j<1000000;j++){
                    m.get(keys[10]);//读取keys[10]100000次
                }
            });
        }
        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);

    }
}

结论:ConcurrentHashMap读效率比HashTable和HashMap要高,写效率反而低

Vector、List、Queue

购票问题

使用List
package character06;

import java.util.ArrayList;
import java.util.List;

/**
 * 有N张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 *
 * 分析下面的程序可能会出现哪些问题?
 * 重复销售?超量销售?
 * @author laimouren
 */
public class TicketSeller1 {
	static List<String> tickets = new ArrayList<>();
	static {
		for(int i=0; i<10000; i++){
			tickets.add("票编号:" + i);
		}

	}	
	public static void main(String[] args) {
		for(int i=0; i<10; i++) {
			new Thread(()->{
				while(tickets.size() > 0) {
					System.out.println("销售了--" + tickets.remove(0));
				}
			}).start();
		}
	}
}
使用Vector

使用Vector进行对比

package character06;

import java.util.Vector;
import java.util.concurrent.TimeUnit;

/**
 * @author laimouren
 */
public class TicketSeller2 {
	//Vector是同步容器
	static Vector<String> tickets = new Vector<>();
	static {
		for(int i=0; i<1000; i++) {
			tickets.add("票 编号:" + i);
		}
	}
	
	public static void main(String[] args) {
		for(int i=0; i<10; i++) {
			new Thread(()->{
			//tickets.size()与tickets.remove()都是原子性操作,但是tickets.size()与tickets.remove()之间的操作不是原子性的
				while(tickets.size() > 0) {
					try {
						TimeUnit.MILLISECONDS.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("销售了--" + tickets.remove(0));
				}
			}).start();
		}
	}
}

上述方法还会超卖,为此我们可以使用list并进行加锁

给List加锁
package character06;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author laimouren
 */
public class TicketSeller3 {
	static List<String> tickets = new LinkedList<>();
	
	
	static {
		for(int i=0; i<1000; i++) {
			tickets.add("票 编号:" + i);
		}
	}
	
	public static void main(String[] args) {
		for(int i=0; i<10; i++) {
			new Thread(()->{
				while(true) {
					synchronized(tickets) {
						//判断与销售加到一个原子操作里,所以不会出问题,但是效率不高
						if(tickets.size() <= 0) {
							break;
						}
						try {
							TimeUnit.MILLISECONDS.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						
						System.out.println("销售了--" + tickets.remove(0));
					}
				}
			}).start();
		}
	}
}
使用Queue

多线程时候多考虑Queue,少考虑List
ConcurrentLinkedQueue底层很多CAS操作

package character06;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

public class TicketSeller4 {
	static Queue<String> tickets = new ConcurrentLinkedQueue<>();
	//ConcurrentLinkedQueue是并发容器  这个程序效率比3高

	static {
		for(int i=0; i<1000; i++) {
			tickets.add("票 编号:" + i);
		}
	}
	public static void main(String[] args) {
		for(int i=0; i<10; i++) {
			new Thread(()->{
				while(true) {
					String s = tickets.poll();
					//拿到头位置的那个票,为空说明没拿到,poll()是有原子性的
					// 判断后没有修改队列操作,所以不需要加锁
					if(s == null){
						break;
					}
					else
					{
						System.out.println("销售了--" + s);
					}
				}
			}).start();
		}
	}
}

ConcurrentSkipListMap

为什么要有这个类?
java中hashmap是无序的,treemap是有序的,但是treemap实现高并发比较困难,因此诞生了折中方案——ConcurrentSkipListMap

特点:
高并发且排序
底层实现是跳表

什么是跳表?
在这里插入图片描述

CopyOnWriteList、CopyOnWriteSet

写时复制

当一组数据读的操作特别多,写的操作特别少,可以使用它来提高效率
为什么提高效率?
读的时候不加锁,写入的时候在原数组上复制一份并且加多一个空间给线程,操作完之后,把原数组的引用指向新的数组

读的时候是共享锁,写的时候是排它锁,类似于ReadWriteLock

Queue

Queue和List的区别?
Queue添加了很多对线程友好的api:offer、peek、poll
BlockingQueue添加了put、take,会使线程阻塞,天然的生产者消费者模型实现

BlockingQueue

为线程池做准备
作用:使线程实现自动阻塞
基本操作:offer(添加数据),peek(看第一个元素),pool(取出第一个元素)

LinkingBlockingQueue

链表实现,无界队列
阻塞方法:put、take
阻塞使用Condition类的await实现的,底层使用unsafe的park()

import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class LinkedBlockingQueue {

    static BlockingQueue<String> strs = new LinkedBlockingQueue<>();

    static Random r = new Random();

    public static void main(String[] args) {
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                try {
                    strs.put("a" + i); //如果满了,就会等待
                    TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
                } 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 -" + strs.take()); //如果空了,就会等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "c" + i).start();
        }
    }
}
ArrayBlockingQueue

有界队列
put()满了就等待,线程阻塞
add()满了会抛异常
offer()有返回值,满了就会返回false

DelayQueue

也是BlockingQueue
需要实现compareTo方法
需要指定等待时间
用来按时间进行任务调度
本质是一个PriorityQueue

import java.util.Calendar;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class T07_DelayQueue {

    static BlockingQueue<MyTask> tasks = new DelayQueue<>();

    static Random r = new Random();

    static class MyTask implements Delayed {
        String name;
        long runningTime;

        MyTask(String name, long rt) {
            this.name = name;
            this.runningTime = rt;
        }

        @Override
        public int compareTo(Delayed o) {
            if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))
                return -1;
            else if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS))
                return 1;
            else
                return 0;
        }

        @Override
        public long getDelay(TimeUnit unit) {

            return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }


        @Override
        public String toString() {
            return name + " " + runningTime;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        long now = System.currentTimeMillis();
        MyTask t1 = new MyTask("t1", now + 1000);
        MyTask t2 = new MyTask("t2", now + 2000);
        MyTask t3 = new MyTask("t3", now + 1500);
        MyTask t4 = new MyTask("t4", now + 2500);
        MyTask t5 = new MyTask("t5", now + 500);

        tasks.put(t1);
        tasks.put(t2);
        tasks.put(t3);
        tasks.put(t4);
        tasks.put(t5);

        System.out.println(tasks);

        for (int i = 0; i < 5; i++) {
            System.out.println(tasks.take());
        }
    }
}
PriorityQueue

优先级队列:内部进行了排序,底层是一个二叉树(小顶堆)的结构

package com.mashibing.juc.c_025;

import java.util.PriorityQueue;

public class T07_01_PriorityQueque {
    public static void main(String[] args) {
        PriorityQueue<String> q = new PriorityQueue<>();

        q.add("c");
        q.add("e");
        q.add("a");
        q.add("d");
        q.add("z");

        for (int i = 0; i < 5; i++) {
            System.out.println(q.poll());
        }
    }
}
输出:
a
c
d
e
z
SynchronusQueue

容量为0,只是单纯用来给其他线程传递任务
本质和Exchanger比较相似,也是需要两个线程同步对接,否则都会阻塞着
单人对单人的手递手

在线程池里面,线程之间进行任务调度的时候,经常会用到

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

public class T08_SynchronusQueue { //容量为0
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> strs = new SynchronousQueue<>();

        new Thread(() -> {
            try {
                System.out.println(strs.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        strs.put("aaa"); //阻塞等待消费者消费,但是只能一个线程手把手给另一个线程才会执行,容量为0,不能放在队列里
        //strs.put("bbb");
        //strs.add("aaa");
        System.out.println(strs.size());
    }
}
TransferQueue

装完,阻塞等着,有线程把它取走,再离开
要先开启消费者线程,再往里面transfer,要不然就阻塞了~
类似于多人对多人的手递手

场景1:要求某件任务有一个结果(比如一个订单等付款完成之后,确认有线程去处理它了,再给客户反馈)
场景2:确认收钱完成之后,才能把商品取走,比如面对面付款

package com.mashibing.juc.c_025;

import java.util.concurrent.LinkedTransferQueue;

public class T09_TransferQueue {
    public static void main(String[] args) throws InterruptedException {
        LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();

        new Thread(() -> {
            try {
                System.out.println(strs.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
		
		//和put的区别是装完数据就阻塞直到有人取走数据,才继续执行,而put只会当队列满了才会阻塞
        strs.transfer("aaa");

        //strs.put("aaa");

		/*new Thread(() -> {
			try {
				System.out.println(strs.take());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}).start();*/
    }
}

transfer():装完数据就堵塞等待其他线程取走数据

经典的交替打印面试题可以用 TransferQueue 实现
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.TransferQueue;

public class T13_TransferQueue {
    public static void main(String[] args) {
        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        TransferQueue<Character> queue = new LinkedTransferQueue<Character>();
        new Thread(() -> {
            try {
                for (char c : aI) {
                    System.out.print(queue.take());
                    queue.transfer(c);
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(() -> {
            try {
                for (char c : aC) {
                    queue.transfer(c);
                    System.out.print(queue.take());
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值