Java高并发编程(三)

目录:

  1.线程安全单例模式的几种实现方式

  2.同步容器

  3.并发容器

一、线程安全单例模式的几种实现方式

  1.饿汉式(不使用同步锁,典型的用空间换时间)

public class Singleton1 {  

  private static Singleton1 mySingleton = new Singleton1();  

  private Singleton1(){
      System.out.println("single");
  }  

  public static Singleton1 getSingle(){
      return mySingleton;
  }

  public static void main(String[] args) {
        Thread[] ths = new Thread[200];
        for(int i=0; i<ths.length; i++) {
            ths[i] = new Thread(()->{
                Singleton1.getSingle();
            });
        }

        Arrays.asList(ths).forEach(o->o.start());
    }
} 

运行结果:

  2.懒汉式(使用同步锁,延时加载,典型的时间换空间)

public class Singleton2 {  

    private static Singleton2 mySingleton;  

    private Singleton2 (){
         System.out.println("single");
    }   

    public static synchronized Singleton2 getSingle(){    //对获取实例的方法进行同步
        if (mySingleton == null)
            mySingleton = new Singleton2();
        return mySingleton;
    }

    public static void main(String[] args) {
        Thread[] ths = new Thread[200];
        for(int i=0; i<ths.length; i++) {
            ths[i] = new Thread(()->{
                Singleton2.getSingle();
            });
        }

        Arrays.asList(ths).forEach(o->o.start());
    }
}  

运行结果:

  3.双重同步锁(缩小粒度,双重检查

public class Singleton3 {  

    private volatile static Singleton3 mySingleton;  

    private Singleton3 (){
        System.out.println("single");
    }   

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

    public static void main(String[] args) {
        Thread[] ths = new Thread[200];
        for(int i=0; i<ths.length; i++) {
            ths[i] = new Thread(()->{
                Singleton3.getSingle();
            });
        }

        Arrays.asList(ths).forEach(o->o.start());
    }
}

运行结果:

  为mySingleton加上volatile关键字,以确保能先行发生关系(happens-before relationship),使所有的写操作都发生于读操作之前,避免出现在另一个线程中看到一个初始化一半的mySingleton的情况,同时双重同步锁的效率也很高

  4.使用内部类的单例模式(既不用加锁,也能实现懒加载)

public class Singleton4 {

    private Singleton4() {
        System.out.println("single");
    }

    private static class Inner {
        private static Singleton4 mySingleton = new Singleton4();
    }

    public static Singleton4 getSingle() {
        return Inner.mySingleton;
    }

    public static void main(String[] args) {
        Thread[] ths = new Thread[200];
        for(int i=0; i<ths.length; i++) {
            ths[i] = new Thread(()->{
                Singleton4.getSingle();
            });
        }

        Arrays.asList(ths).forEach(o->o.start());
    }

}

运行结果:

二、同步容器

  1.vector、stack、hashtable等,保证了方法的原子性

  2.小例子程序演示同步容器

/**
 * 有N张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 *
 * 分析下面的程序可能会产生哪些问题?
 * 重复销售?超量销售?
 *
 */
package yxxy.c_024;

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

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为例再做一遍售票操作

/**
 * 有N张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 *
 * 分析下面的程序可能会产生哪些问题?
 *
 * 使用Vector或者Collections.synchronizedXXX
 * 分析一下,这样能解决问题吗?
 *
 */
package yxxy.c_024;

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

public class TicketSeller2 {
    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(()->{
                while(tickets.size() > 0) {

                /*    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/

                    System.out.println("销售了--" + tickets.remove(0));
                }
            }).start();
        }
    }
}

运行结果:

...

从运行结果来看,同步容器似乎已经达到了我们想要的目的,但其中还有一些问题

当我们使用了同步容器时,虽然容器的方法是具有原子性的,但当判断与操作分离时,中间的部分还是可能会被其他线程所打断,下面我们模拟一下判断与remove之间有其他的逻辑

/**
 * 有N张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 *
 * 分析下面的程序可能会产生哪些问题?
 *
 * 使用Vector或者Collections.synchronizedXXX
 * 分析一下,这样能解决问题吗?
 *
 */
package yxxy.c_024;

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

public class TicketSeller2 {
    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(()->{
                while(tickets.size() > 0) {

                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("销售了--" + tickets.remove(0));
                }
            }).start();
        }
    }
}

运行结果:

我们看到程序出现了错误,所以虽然同步容器保证了方法的原子性,但可能会发生判断与操作分离,整体操作不具备原子性

三、并发容器

  1.并发容器支持多线程

  ConcurrentSkipListMap跳表结构,他是排好序的容器执行插入操作较快

  ConcurrentHashMap将容器分成了16段,每次执行插入操作的时候只执行其中的一段,而HashTable为锁定一整段

  ConcurrentLinkedQueue无界队列,内存耗不完的情况下可以一直加(offer,add),拿(poll拿出来删掉,peek拿出来不删)

  BolckingQueue阻塞式队列,加(put如果满了就会等待),拿(take如果空了就会等待)

    几个特殊的阻塞式队列:

    1)TransferQueue转移队列,相较于其他队列,多了一个transfer方法生产者将任务直接发给消费者,不进入结束队列,在transfer找不到消费者情形下阻塞,实时处理用的较多,队列还有一定容量

    2)DelayQueue计时任务队列

    3)synchronusQueue同步队列,特殊的transferQueue,没有容量的队列,不能调用add方法,只能调用put方法阻塞等待消费者消费,生产者生产的任务直接给消费者消费

还是之前的售票程序,现在改用并发容器以ConcurrentLinkedQueue来重新实验一遍

/**
 * 有N张火车票,每张票都有一个编号
 * 同时有10个窗口对外售票
 * 请写一个模拟程序
 *
 * 使用ConcurrentQueue提高并发性
 */
package yxxy.c_024;

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

public class TicketSeller4 {
    static Queue<String> tickets = new ConcurrentLinkedQueue<>();

    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();
                    if(s == null) break;
                    else System.out.println("销售了--" + s);
                }
            }).start();
        }
    }
}

运行结果:

当我们改用并发容器ConcurrentLinkedQueue时,我们调用poll方法售票直至票数为0时,字符串s会变为null,所以在接下来的if判断后会跳出

当我们在此例中使用了并发容器时,就具有高的效率同时也不会出现问题

四、其他常用同步容器

  1.Collocations.synchronizedXXX()将未加锁的容器加锁,在并发度不高的情况下可以使用此方法,在并发度较高且需排序的情况下使用concurrentSkipListMap

  2.CopyOnWriteList写时复制链接表,在读取时不加锁,因此读取的时候效率高,但写的效率特别低,应用场景:在需要大量读取少量写入时使用

五、总结

  1.对于map/set的使用

    无线程安全要求:hashmap、treemap、linkedhashmap

    并发度低:   hashtable、Collections.synchronizedXXX()

    并发度高:   concurrentHashMap、concurrentSkipListMap

  2.list

    无线程安全要求:ArrayList、LinkedList

    并发度低:   Collections.synchronizedXXX()

    并发度高:   Queue

              concurrentLinkedQueue

              BlockingQueue

                LinkedBQ

                ArrayBQ

                TransferQueue

                synchronusQueue

              DelayQueue

时间: 2024-11-11 12:00:22

Java高并发编程(三)的相关文章

马士兵java高并发编程三

1.使用静态内部类实现线程安全的单例模式 package com.weiyuan.test; /** * 采用内部类的形式实现单例模式 * 是同步安全的,并且实现了懒加载 * */ public class Sigleton { private Sigleton(){ } private static class Inner{ private static Sigleton s = new Sigleton(); } public static Sigleton getInstance(){ r

Java高并发编程(一)

1.原子量级操作(读.++操作.写分为最小的操作量单位,在多线程中进行原子量级编程保证程序可见性(有序性人为规定)) 由于某些问题在多线程条件下:产生了竞争的问题,(例如:在多线程中一个简单的计数器增加)如果在程序中不采用同步的机制,那么在程序的运行结果中,多个线程在访问此资源时候,产生Racing.解决这个问题,采用某种方式阻止其他线程在该线程使用该变量的时候使用该变量 采用原子级操作:1.采用加锁的机制(最好的操作)2.Java.concurrent.atomic包包含一些原子量操作:Ato

java高并发编程(五)线程池

摘自马士兵java并发编程 一.认识Executor.ExecutorService.Callable.Executors /** * 认识Executor */ package yxxy.c_026; import java.util.concurrent.Executor; public class T01_MyExecutor implements Executor { public static void main(String[] args) { new T01_MyExecutor(

java高并发编程(二)

马士兵java并发编程的代码,照抄过来,做个记录. 一.分析下面面试题 /** * 曾经的面试题:(淘宝?) * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束 * * 分析下面这个程序,能完成这个功能吗? * @author mashibing */ package yxxy.c_019; import java.util.ArrayList; import java.util.List

[高并发]Java高并发编程系列开山篇--线程实现

ava是最早开始有并发的语言之一,再过去传统多任务的模式下,人们发现很难解决一些更为复杂的问题,这个时候我们就有了并发. 引用 多线程比多任务更加有挑战.多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作.这可能是在单线程程序中从来不会遇到的问题.其中的一些错误也未必会在单CPU机器上出现,因为两个线程从来不会得到真正的并行执行.然而,更现代的计算机伴随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行. 那么,要开始Java并发之路,就要开始

java高并发编程--03--线程间通信

1.同步阻塞与异步非阻塞 1.1同步阻塞消息处理 服务端监听端口,客户端提交Event,服务端创建线程接收Event,处理Event,返回结果 缺陷: 同步Event提交,客户端等待时间过长(提交Event时间+接收Event时间+处理Event时间+返回结果时间)会陷入阻塞,导致二次提交Event耗时过长 由于客户端提交Event数量有限,导致系统受理业务数量有限,系统吞吐量不高 一个线程处理一个Event,线程频繁创建销毁,从而增加系统额外开销 业务达到峰值时,大量业务处理线程会导致CPU频

Java高并发编程(四)

一.Executor执行器 1.Executor接口,java线程池框架中的顶层接口,提供一个execute方法来执行任务 import java.util.concurrent.Executor; public class T01_MyExecutor implements Executor { public static void main(String[] args) { new T01_MyExecutor().execute(()->System.out.println("hel

java高并发编程--04--Hook线程以及捕获线程执行异常

1.获取线程运行时异常Thread类处理运行时异常的四个API:public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh):为某个线程UncaughtExceptionHandlerpublic static setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh):设置全局UncaughtExceptionHandlerpublic Uncaugh

Java 面试知识点解析(二)——高并发编程篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部分内容参照自这一篇文章,有一些自己补充的,也算是重新学习一下 Java 吧. 前序文章链接: Java 面试知识点解析(一)--基础知识篇 (一)高并发编程基础知识 这里涉及到一些基础的概念,我重新捧起了一下<实战 Java 高并发程序设计>这一本书,感觉到心潮澎湃,这或许就是笔者叙述功底扎实的