多线程,高并发初步(四,并发容器)

线程安全的singleton(单例模式,线程安全)
单例模式部分内容,来源:https://www.cnblogs.com/xudong-bupt/p/3433643.html
1.多线程安全单例模式实例一(不使用同步锁)

public class Singleton {
private static Singleton sin=new Singleton();    ///直接初始化一个实例对象
 private Singleton(){    ///private类型的构造函数,保证其他类对象不能直接new一个该对象的实例
  }
 public static Singleton getSin(){    ///该类唯一的一个public方法    
   return sin;
 	}
}

上述代码中的一个缺点是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 。现在流行的设计都是讲“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象。所以这种适合在小系统。

2.多线程安全单例模式实例二(使用同步方法)

 public class Singleton {  
	 private static Singleton instance;  
	  private Singleton (){
 	   
	  }   
  public static synchronized Singleton getInstance(){    //对获取实例的方法进行同步
   if (instance == null)     
       instance = new Singleton(); 
   return instance;
  }
  }  

上述代码中的一次锁住了一个方法, 这个粒度有点大 ,改进就是只锁住其中的new语句就OK。就是所谓的“双重锁”机制。

3.多线程安全单例模式实例三(使用双重同步锁)

 public class Singleton {  
   private static Singleton instance;  
  private Singleton (){
 }   
  public static Singleton getInstance(){    //对获取实例的方法进行同步
    if (instance == null){
        synchronized(Singleton.class){
            if (instance == null)
               instance = new Singleton(); 
        }
    }
    return instance;
  }
}

并发容器
问题抛出:

/**
*10000张票出售的问题
*10线程去抢票
* @author zhouyi
*
*/
public class TicketSeller {

static List<String> ticketList = new ArrayList<String>();

static {
    for(int i = 0;i < 10000; i++)
        ticketList.add("票编号:" + i);
}

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

多次运行得到下面的结果:简单来说,票卖重了(原因List,ArrayList他们以及他们的方法都不是同步的,所以一定会出问题)
在这里插入图片描述
那么我们进一步,用具有原子性的方法(Vector以及他的方法都具有原子性)行不行呢?

public class TicketSeller {

static Vector<String> ticketList = new Vector();

static {
    for(int i = 0;i < 10000; i++)
        ticketList.add("票编号:" + i);
}

public static void main(String[] args) {
    for(int i = 0; i < 10; i++) {
        new Thread(()->{
            while (ticketList.size() > 0) {
            /**
            ***如果这一块有不具备原子性的操作,程序一样会挂掉。**比如我们加一块睡眠操作
            */
          	try {
                    TimeUnit.MICROSECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("销售了。。" + ticketList.remove(0));
            }
        }).start();
    }
}
}

得到运行结果如下:
在这里插入图片描述
那么我们加上锁,肯定是可以的(运行结果就不给出了)

public class TicketSeller {

static Vector<String> ticketList = new Vector();

static {
    for(int i = 0;i < 10000; i++)
        ticketList.add("票编号:" + i);
}

public static void main(String[] args) {
    for(int i = 0; i < 10; i++) {
        new Thread(()->{
            while(true)
            {
                synchronized (ticketList) {
                    if (ticketList.size() <= 0) break;
                    try {
                        TimeUnit.MICROSECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("销售了。。" + ticketList.remove(0));
                }
            }
        }).start();
    }
}
}

但是加上锁的效率是不高的。在java1.5后新增加的并发容器,可以很好解决这个问题。
并发容器–Queue(ConcurrentLinkedDeque,BlockingQueue等的实现)

public class TicketSeller {

//ConcurrentLinkedDeque :支持并发多线程队列
static Queue<String> ticketList = new ConcurrentLinkedDeque<String>();

static {
    for(int i = 0;i < 10000; i++)
        ticketList.add("票编号:" + i);
}

public static void main(String[] args) {
    for(int i = 0; i < 10; i++) {
        new Thread(()->{
            while(true)
            {
                String string = ticketList.poll(); // 同步往外拿出一个数据
                if(string == null) break; //这一步也不是原子性的,但是后面没有再对Queue进行操作,所以不会有问题
                System.out.println("销售了。。" + string);
            }
        }).start();
    }
}
}

并发容器–ConcurrentMap<K, V>

public class ConcurrentMap {

public static void main(String[] args) {
    // 对应得还有set,这里就不具体列举
    // Map<String, String> map = new ConcurrentHashMap<String, String>();// 并发量很高的情况下,使用
    //Map<String, String> map = new ConcurrentSkipListMap<String, String>(); //并发量很高,且需要排序,使用, 默认排好顺序
    Map<String, String> map = new Hashtable<String, String>(); // 所有的都带锁,但是效率低,现在基本不用
    // Map<String, String> map = new HashMap<String, String>(); // 不加锁
    // Map<String, String> map = new TreeMap<String, String>(); // 默认排好顺序 ,不加锁
    Random random = new Random();
    Thread[] ths = new Thread[100]; // 线程数组
    CountDownLatch latch = new CountDownLatch(ths.length);
    long start = System.currentTimeMillis();
    for(int i = 0; i < ths.length; i++) {
        ths[i] = new Thread(()->{
            for(int j = 0; j < 10000; j++) map.put("a" + random.nextInt(10000), "a" + random.nextInt(10000));
            latch.countDown(); // 门闩减一
        });
    }

    Arrays.asList(ths).forEach(t->t.start());
    try {
        latch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    long end = System.currentTimeMillis();
    System.out.println(end - start); //执行时间
}
}

并发容器–CopyOnWriteArrayList (读操作并发量大时使用)

public class CopyOnWrite {

public static void main(String[] args) {
    // 如果用ArrayList和Vector都会出现并发问题
    // 该容器的特点:写时复制,读的效率非常高,但写的效率就非常低
    List<String> list= new CopyOnWriteArrayList<String>(); // 效率很慢
    Random random = new Random();
    Thread[] ths = new Thread[100]; // 线程数组

    for(int i = 0; i < ths.length; i++) {
        Runnable task = new Runnable() {

            @Override
            public void run() {
                for(int j = 0; j < 1000; j++) list.add("a" + random.nextInt(10000));
            }
        };
        ths[i] = new Thread(task);
    }
    runAndComputeTime(ths);
    System.out.println(list.size());
    }

static void runAndComputeTime(Thread[] ths) {
    long start = System.currentTimeMillis();
    Arrays.asList(ths).forEach(t->t.start());
    Arrays.asList(ths).forEach(t->{
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    long end = System.currentTimeMillis();
    System.out.println(end - start); //执行时间
}
}

并发容器–Queue

public class Queue1 {

public static void main(String[] args) {
    // 这是单向队列,如果又需要可以使用双向队列
    // 高并发时,队列主要使用ConcurrentLinkedQueue,BlockingQueue
   Queue<String> queue = new ConcurrentLinkedQueue<String>();
   
   for(int i = 0;i < 10; i++) {
       queue.offer("a" + i); // 相当于add
   }

   System.out.println(queue);
   System.out.println(queue.size());

   System.out.println(queue.poll()); // 从头哪走一个,并删除
   System.out.println(queue.size());

   System.out.println(queue.peek()); // 从头拿走一个,不删除
   System.out.println(queue.size());
}
}

并发容器–BlockingQueue(用的非常多),阻塞队列
生产者和消费者完善。
LinkedBlockingDeque(无界队列)来实现

public class Queue2 {

// 无界队列
static BlockingQueue<String> queue = new LinkedBlockingDeque<String>();

static Random random = new Random();

public static void main(String[] args) {
    new Thread(()->{
        for(int i = 0; i < 100; i++) {
            try {
                queue.add("a" + i); // 如果填满了就会自动进入等待
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    },"th1").start();

    for(int i = 0; i < 5; i++) {
        new Thread(()->{
            for(;;) { // 无限循环
                try {
                    System.out.println(Thread.currentThread().getName() + "take" + queue.take()); // 如果空了 就继续
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"th2" + i).start();
    }
}
}

ArrayBlockingQueue(有界队列)来实现

public class Queue2 {

//有界队列
static BlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);

static Random random = new Random();

public static void main(String[] args) {
    for(int i = 0; i < 5; i++) {
        queue.add("a" + i);
    }
    queue.offer("aaa"); // 可以得到返回值,来判断是否加成功。
    //queue.offer("aaa", 1, TimeUnit.SECONDS);
    System.out.println(queue);
}
}

运行结果:
在这里插入图片描述
DelayQueue --(延迟队列,并排好序)无界队列-来实现
是需要实现Delayed接口的
这个可以用来干什么呢?可以用来定时执行任务

public class Queue3 {

//无界队列
static BlockingQueue<MyTask> tasks = new DelayQueue<>();

static Random random = new Random();

static class MyTask implements Delayed {

    long runningTime;

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

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

    @Override
    public int compareTo(Delayed o) {

        if (this.getDelay(TimeUnit.MICROSECONDS) < o.getDelay(TimeUnit.MICROSECONDS))
            return -1;
        else if(this.getDelay(TimeUnit.MICROSECONDS) > o.getDelay(TimeUnit.MICROSECONDS))
            return 1;
        else
        return 0;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MICROSECONDS);
    }

}

public static void main(String[] args) throws InterruptedException { // throws InterruptedException 这里抛出同类异常,
//可以避免程序类相同异常抓取,造成代码块重复。

    long now = System.currentTimeMillis();
    MyTask myTask1 = new MyTask(now + 1000);
    MyTask myTask2 = new MyTask(now + 2000);
    MyTask myTask3 = new MyTask(now + 2500);
    MyTask myTask4 = new MyTask(now + 1000);
    MyTask myTask5 = new MyTask(now + 500);

    tasks.put(myTask1);
    tasks.put(myTask2);
    tasks.put(myTask3);
    tasks.put(myTask4);
    tasks.put(myTask5);

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

并发容器–LinkedTransferQueue(有一定容量)超高并发时使用

public class LinkedTransfer {

public static void main(String[] args) throws InterruptedException {
    // 有一定容量
    LinkedTransferQueue<String> strs =  new LinkedTransferQueue<String>();
    new Thread(()->{
        try {
            System.out.println(strs.take());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    // 有一个队列,消费者启动,生产者生产出的产品,不先去放入队列
    // 而是直接去找有没有消费者,如果有就直接扔给消费
    // 在超高并发时使用
    strs.transfer("aaa");

    //如果先启动生产者,如果找不到消费者就会阻塞
}
}

并发容器–SynchronousQueue(容量为0)

public class SynchronousQueue1 {

public static void main(String[] args) throws InterruptedException {
    // 0容量
    BlockingQueue<String> strs =  new SynchronousQueue<String>();
    new Thread(()->{
        try {
            System.out.println(strs.take());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    //strs.transfer("aaa");
    strs.put("aaa"); // 阻塞等待消费者
    System.out.println(strs.size());
}
}

总结:
1,对于map/set选择使用,我只列出了map,set一样有的。
没有并发:
HashMap , TreeMap,LinkedHashMap。

较小并发量:
Hashtable , Collections.synchronizedXXX

并发量较大:
ConcurrentHashMap
且要求排序:
ConcurrentSkipListMap

2,队列
不需要同步的队列:
ArrayList , LinkedList , Collections.synchronizedXXX

同步队列:
ConcurrentLinkedQueue , BlockingQueue (实现方式:LinkedBlockingDeque ,LinkedTransferQueue , ArrayBlockingQueue, SynchronousQueue), DelayQueue(执行定时任务)

特殊:CopyOnWriteArrayList(读操作并发量大时使用)

猜你喜欢

转载自blog.csdn.net/weixin_42603009/article/details/89078091