JAVA学习笔记(并发编程 - 伍)- 线程安全策略

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bingdianone/article/details/83415484

四种线程安全策略

  1. 线程封闭:
    一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改
  2. 安全共享:
    一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它
  3. 线程安全对象:
    一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它
  4. 被守护对象:
    被守护对象只能通过获取特定的锁来访问

不可变对象

有一种对象发布了就是安全的,这就是不可变对象,本小节简单介绍一下不可变对象。不可变对象可以在多线程在保证线程安全,不可变对象需要满足的条件:

  • 不可变对象需要满足的条件
    – 对象创建以后其状态就不能修改
    – 对象所有域都是final类型
    – 对象是正确创建的(在对象创建期间, this引用没有逸出)
  • 创建不可变对象的方式(参考String):
    – 将类声明成final类型,使其不可以被继承
    – 将所有的成员设置成私有的,使其他的类和对象不能直接访问这些成员
    对变量不提供set方法
    – 将所有可变的成员声明为final,这样只能对他们赋值一次
    – 通过构造器初始化所有成员,进行深度拷贝
    – 在get方法中,不直接返回对象本身,而是克隆对象,返回对象的拷贝

提到不可变的对象就不得不说一下final关键字,该关键字可以修饰类、方法、变量:

  • final关键字:类、方法、变量
    – 修饰类:不能被继承(final类中的所有方法都会被隐式的声明为final方法)
    – 修饰方法:
    1、锁定方法不被继承类修改;
    2、可提升效率(一个类的private会被final隐式修饰)
    – 修饰变量:基本数据类型变量(初始化之后不能修改)、引用类型变量(初始化之后不能再修改其引用)
    – 修饰方法参数:同修饰变量

下面看一下final的应用:

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.Maps;
import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;

/**
 * final的使用
 */
@Slf4j
@NotThreadSafe
public class ImmutableExample1 {
    private final static Integer a=1;
    private final static String b="2";
    private final static Map<Integer,Integer> map= Maps.newHashMap();

    static {
        map.put(1,2);
        map.put(3,4);
        map.put(5,6);
    }

    public static void main(String[] args) {
//        a=2; //不可修改
//        b="3"; //不可修改
//        map=Maps.newHashMap(); //不可修改对象
        map.put(1,3);//可修改里面的值
        log.info("{}",map.get(1));
    }

    private void test(final int a){
//        a=1;  //传参不可修改
    }
}

  • Collections.unmodifiableXXX: Collection, List, Set, Map…
  • Guava: ImmutableXXX: Collection, List, Set, Map…

由于这些工具类的存在,所以创建不可变对象并不是很费劲,而且其实现源码也不会很难懂。所以如果需要自定义不可变对象,也可以参考这些工具类的实现源码去进行实现。接下来看一下如何使用Collections.unmodifiableXXX方法将map转换为一个不可变的对象,代码如

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.Maps;
import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.Map;

/**
 * unmodifiableXXX类
 */
@Slf4j
@NotThreadSafe
public class ImmutableExample2 {
    private final static Integer a=1;
    private final static String b="2";
    private static Map<Integer,Integer> map= Maps.newHashMap();

    static {
        map.put(1,2);
        map.put(3,4);
        map.put(5,6);
        map=Collections.unmodifiableMap(map);//Collections修饰后map不可修改
    }

    public static void main(String[] args) {
        map.put(1,3);//不可修改
        log.info("{}",map.get(1));
    }

}
/*
报错;抛出异常
Exception in thread "main" java.lang.UnsupportedOperationException
	at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
	at com.mmall.concurrency.example.immutable.ImmutableExample2.main(ImmutableExample2.java:29)
 */

Guava: ImmutableXXX示例:

package com.mmall.concurrency.example.immutable;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import lombok.extern.slf4j.Slf4j;

/**
 * google的Immutable
 */
@Slf4j
public class ImmutableExample3 {
    private final static ImmutableList<Integer> list=ImmutableList.of(1,2,3);

    private final static ImmutableSet set=ImmutableSet.copyOf(list);

    private final static ImmutableMap<Integer,Integer> map1=ImmutableMap.of(1,2,3,4);

 //   private final static ImmutableMap<Integer,Integer> map2=ImmutableMap.<Integer,Integer>builder()
 //           .put(1,2).put(3,4).put(5,6);

    public static void main(String[] args) {
        //此时直接不允许调用下列方法
 //       list.add(4);//不允许修改
 //       set.add(3);//不允许修改
//        map1.put(1,2);//不允许修改
//     map2.put(1,2);//不允许修改
    }
}

线程封闭

概念:把对象封装到一个线程里,只有这个线程能看到这个对象。那么即便这个对象本身不是线程安全的,但由于线程封闭的关系让其只能在一个线程里访问,所以也就不会出现线程安全的问题了

  • Ad-hoc线程封闭:完全由程序控制实现,最糟糕的方式,忽略
  • 堆栈封闭:局部变量,当多个线程访问同一个方法的时候,方法内的局部变量都会被拷贝一份副本到线程的栈中,所以局部变量是不会被多个线程所共享的,因此无并发问题。所以我们在开发时应尽量使用局部变量而不是全局变量
  • ThreadLocal线程封闭:每个Thread线程内部都有个map,这个map是以线程本地对象作为key,以线程的变量副本作为value。而这个map是由ThreadLocal来维护的,由ThreadLocal负责向map里设置线程的变量值,以及获取值。所以对于不同的线程,每次获取副本值的时候,其他线程都不能获取当前线程的副本值,于是就形成了副本的隔离,多个线程互不干扰。所以这是特别好的实现线程封闭的方式

线程封闭的例子:

  1. 局部变量,无并发问题
  2. 数据库链接对应JDBC里的connection对象;connection本身没有对线程安全做太多的处理;JDBC规范里也没有要求connection对象必须是安全的;实际在应用程序中,线程从连接池获取了一个连接对象使用后返回给连接池,由于大多请求是由单线程同步方式来处理的;在connection返回之前连接池不会把它分配给其他线程;这是隐含的线程封闭。

使用ThreadLocal存储登录的用户信息对象

package com.mmall.concurrency.example.threadLocad;

/**
 * 线程封闭ThreadLocal  1
 */
public class RequestHolder {
    private final static ThreadLocal<Long> requestHolder=new ThreadLocal<>();
    
 // ThreadLocal 内部维护一个map,key为当前线程id,value为当前set的变量
    public static void add(Long id){
        requestHolder.set(id);
    }

//会通过当前线程id获取数据
    public static Long getId(){
        return  requestHolder.get();
    }

//移除变量信息如果不移除,那么变量不会释放掉,会造成内存泄漏
    public static void remove(){
        requestHolder.remove();
    }
}

线程不安全类与写法

所谓线程不安全的类,是指该类的实例对象可以同时被多个线程共享访问,如果不做同步或线程安全的处理,就会表现出线程不安全的行为。

StringBuilder -> StringBuffer(StringBuffer里的方法几乎都加有synchronized)
字符串拼接,在Java里提供了两个类可完成字符串拼接,就是StringBuilder和StringBuffer,其中StringBuilder是线程不安全的,而StringBuffer是线程安全的
  StringBuffer之所以是线程安全的原因是几乎所有的方法都加了synchronized关键字,所以是线程安全的。但是由于StringBuffer 是以加 synchronized 这种暴力的方式保证的线程安全,所以性能会相对较差,在堆栈封闭等线程安全的环境下应该首先选用StringBuilder。
  
相关代码案例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * String拼接stringBuilder
 */
@Slf4j
@NotThreadSafe
public class StringExample1 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;

    public static StringBuilder stringBuilder =new StringBuilder();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update();
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("count:{}",stringBuilder.length());
    }

    private static void update(){
        stringBuilder.append("1");
    }
}
/*线程安全情况下正确为5000
17:03:31.107 [main] INFO com.mmall.concurrency.example.commonUnsafe.StringExample1 - count:4980
 */
package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * string拼接stringBuffer
 */
@Slf4j
@ThreadSafe
public class StringExample2 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;

    public static StringBuffer stringBuffer =new StringBuffer();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update();
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("count:{}",stringBuffer.length());
    }

    private static void update(){
        stringBuffer.append("1");
    }
}
/*
17:04:08.249 [main] INFO com.mmall.concurrency.example.commonUnsafe.StringExample2 - count:5000
 */

SimpleDateFormate -> JodaTime(推荐使用不仅仅安全;处理也有优势)
SimpleDateFormat 的实例对象在多线程共享使用的时候会抛出转换异常,正确的使用方法应该是采用堆栈封闭,将其作为方法内的局部变量而不是全局变量,在每次调用方法的时候才去创建一个SimpleDateFormat实例对象,这样利于堆栈封闭就不会出现并发问题。另一种方式是使用第三方库joda-time的DateTimeFormatter类(推荐使用)

SimpleDateFormat 错误示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * SimpleDateFormat错误用法
 */
@Slf4j
@NotThreadSafe
public class DateFormateExample1 {
    private static SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update();
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
    }

    private static void update(){
        try {
            simpleDateFormat.parse("20180208");//会报错
        } catch (ParseException e) {
            log.error("parse exception",e);
        }
    }
}
/*
运行结果会出现很多异常
Exception in thread "pool-1-thread-19" Exception in thread "pool-1-thread-20" Exception in thread "pool-1-thread-17" Exception in thread "pool-1-thread-18" Exception in thread "pool-1-thread-10" Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-7" Exception in thread "pool-1-thread-8" Exception in thread "pool-1-thread-6" Exception in thread "pool-1-thread-14" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-15" Exception in thread "pool-1-thread-12" Exception in thread "pool-1-thread-13" Exception in thread "pool-1-thread-16" Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-11" Exception in thread "pool-1-thread-22" java.lang.NumberFormatException: For input string: "E81"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:589)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
 */

SimpleDateFormat 正确示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
 * SimpleDateFormat正确用法
 */
@Slf4j
@ThreadSafe
public class DateFormateExample2 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update();
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
    }

    private static void update(){
        try {
            //每次声明一个新的变量来使用才不会出现线程不安全的异常
            SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyyMMdd");
            simpleDateFormat.parse("20180208");//不会报错
        } catch (ParseException e) {
            log.error("parse exception",e);
        }
    }
}

DateTimeFormatter的使用示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 线程安全的DateTimeFormat
 * 不是java内部的org.joda.time.DateTime;
 */
@Slf4j
@ThreadSafe
public class DateFormateExample3 {
    private static DateTimeFormatter dateTimeFormat=DateTimeFormat.forPattern("yyyyMMdd");

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update();
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
    }

    private static void update(){
        DateTime.parse("20180208",dateTimeFormat);
    }
}

ArrayList HashSet HashMap 等Collections线程不安全
ArrayList的代码示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * 线程不安全ArrayList
 */
@Slf4j
@NotThreadSafe
public class ArrayListExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static List<Integer> list=new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("listSize:{}",list.size());
    }

    private static void update(int i){
        list.add(i);
    }
}
/*线程安全下正确结果应该为5000
17:17:11.767 [main] INFO com.mmall.concurrency.example.commonUnsafe.ArrayListExample - listSize:4976
 */

HashSet代码示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
 * 线程不安全HashSet
 */
@Slf4j
@NotThreadSafe
public class HashSetExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Set<Integer> hashSet=new HashSet<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("Size:{}",hashSet.size());
    }

    private static void update(int i){
        hashSet.add(i);
    }
}
/*线程安全下正确结果应该为5000
17:18:20.103 [main] INFO com.mmall.concurrency.example.commonUnsafe.HashSetExample - Size:4974
 */

Hashmap示例:

package com.mmall.concurrency.example.commonUnsafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
 * 线程不安全HashMap
 */
@Slf4j
@NotThreadSafe
public class HashMapExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Map<Integer,Integer> hashMap=new HashMap<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("Size:{}",hashMap.size());
    }

    private static void update(int i){
        hashMap.put(i,i);
    }
}
/*线程安全下正确结果应该为5000
17:18:53.258 [main] INFO com.mmall.concurrency.example.commonUnsafe.HashMapExample - Size:4980
 */

先检查再执行: if(condition(a)) { handle(a)}(容易犯错的写法)
在这个操作里,可能会有两个线程同时通过if的判断,然后去执行了处理方法,那么就会出现两个线程同时操作一个对象,从而出现线程不安全的行为。这种写法导致线程不安全的主要原因是因为这里分成了两步操作,这个过程是非原子性的,所以就会出现线程不安全的问题。

线程安全-同步容器

在上一小节中,介绍了一些常用的线程不安全的集合容器,在使用这些容器时,需要自行处理线程安全问题。所以使用起来相对会有些不便,而Java在这方面提供了相应的同步容器,可以在多线程情况下可以结合实际场景考虑使用这些同步容器。

集合接口下的同步容器实现类:

HashMap-> HashTable (key, value不能为null)

package com.mmall.concurrency.example.commonSafe;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;


import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * hashTable
 */
@Slf4j
@ThreadSafe
public class HashTableExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Map<Integer,Integer> map=new Hashtable<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("Size:{}",map.size());
    }

    private static void update(int i){
        map.put(i,i);
    }
}
/*
19:16:26.449 [main] INFO com.mmall.concurrency.example.commonSafe.HashTableExample - Size:5000
 */

ArrayList-> Vector(synchronized修饰), Stack(synchronized修饰)
注:vector的所有方法都是有synchronized关键字保护的,stack继承了vector,并且提供了栈操作(先进后出),而hashtable也是由synchronized关键字保护

但是需要注意的是同步容器也并不一定是绝对线程安全的,例如有两个线程,线程A根据size的值循环执行remove操作,而线程B根据size的值循环执行执行get操作。它们都需要调用size获取容器大小,当循环到最后一个元素时,若线程A先remove了线程B需要get的元素,那么就会报越界错误。错误示例如下:

package com.mmall.concurrency.example.commonSafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Vector;

/**
 * Vector线程不安全演示
 * 为什么所有方法具有synchronized修饰还会线程不安全?
 *当两个线程同时操作remove和git就会报错
 *当thread2想获取i=9的元素的时候,而thread1刚好将i=9的元素移除了,就会导致数组越界
 */
@Slf4j
@NotThreadSafe
public class VectorExample2 {

    private static Vector<Integer> vector = new Vector<>();

    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 10; i++) {
                vector.add(i);
            }
            Thread thread1 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        vector.remove(i);
                    }
                }
            };
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        vector.get(i);
                    }
                }
            };
            thread1.start();
            thread2.start();
        }
    }
}
/*
运行报错
Exception in thread "Thread-11882" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 5
	at java.util.Vector.remove(Vector.java:831)
	at com.mmall.concurrency.example.commonSafe.VectorExample2$1.run(VectorExample2.java:26)
 */

另外还有一点需要注意的是,当我们使用foreach循环或迭代器去遍历元素的同时又执行删除操作的话,即便在单线程下也会报并发修改异常。示例代码如下:

package com.mmall.concurrency.example.commonSafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Iterator;
import java.util.Vector;

/**
 *在遍历的时候不要去做增删操作
 */
@Slf4j
@NotThreadSafe
public class VectorExample3 {
    /**
     源码
     final void checkForComodification() {
     if (modCount != expectedModCount)
     throw new ConcurrentModificationException();
     }
     */
    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void test1(Vector<Integer> v1){// foreach
        for (Integer i : v1) {
            if(i.equals(3)){
                v1.remove(i);
            }
        }
    }
    //Exception in thread "main" java.util.ConcurrentModificationException
    private static void test2(Vector<Integer> v1){ // iterator
        Iterator<Integer> iterator = v1.iterator();
        while (iterator.hasNext()){
            Integer i = iterator.next();
            if(i.equals(3)){
                v1.remove(i);
            }
        }
    }
    //成功(遍历的时候做增删操作建议使用)
    private static void test3(Vector<Integer> v1){
        for (int i = 0; i <v1.size() ; i++) {
            if(v1.get(i).equals(3)){
                v1.remove(i);
            }
        }
    }

    public static void main(String[] args) {
        Vector<Integer> vector = new Vector<>();
        vector.add(1);
        vector.add(2);
        vector.add(3);
        test2(vector);
    }
}

最方便的方式就是使用jdk1.8提供的函数式编程接口:

private static void test5(Vector<Integer> v1){
    v1.removeIf((i) -> i.equals(5));
}

Collections.synchronizedXXX (list,set,map)方法所创建的同步容器

Collections.synchronizedList

package com.mmall.concurrency.example.commonSafe;

import com.google.common.collect.Lists;
import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

/**
 * Collections.synchronized
 */
@Slf4j
@NotThreadSafe
public class CollectionsExample1 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static List<Integer> list=Collections.synchronizedList(Lists.newArrayList());

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("listSize:{}",list.size());
    }

    private static void update(int i){
        list.add(i);
    }
}
/*
19:18:21.393 [main] INFO com.mmall.concurrency.example.commonSafe.CollectionsExample1 - listSize:5000
 */

Collections.synchronizedSet

package com.mmall.concurrency.example.commonSafe;

/**
 * Collections.synchronized
 */
import com.google.common.collect.Sets;
import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
@NotThreadSafe
public class CollectionsExample2 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Set<Integer> set=Collections.synchronizedSet(Sets.newHashSet());

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("setSize:{}",set.size());
    }

    private static void update(int i){
        set.add(i);
    }
}
/*
19:19:19.829 [main] INFO com.mmall.concurrency.example.commonSafe.CollectionsExample2 - setSize:5000
 */

Collections.synchronizedMap

package com.mmall.concurrency.example.commonSafe;

import com.mmall.concurrency.annoations.NotThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
 * Collections.synchronized
 */
@Slf4j
@NotThreadSafe
public class CollectionsExample3 {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Map<Integer,Integer> hashMap= Collections.synchronizedMap(new HashMap<>());

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("mapSize:{}",hashMap.size());
    }

    private static void update(int i){
        hashMap.put(i,i);
    }
}
/*
19:20:18.966 [main] INFO com.mmall.concurrency.example.commonSafe.CollectionsExample3 - mapSize:5000
 */

线程安全-并发容器J.U.C( java.util.concurrent)

同步容器是通过synchronized来实现同步的,所以性能较差。而且同步容器也并不是绝对线程安全的,在一些特殊情况下也会出现线程不安全的行为。有了并发容器后同步容器的使用也越来越少的,大部分都会优先使用并发容器。

ArrayList -> CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。而在CopyOnWriteArrayList写的过程是会加锁的,即调用add的时候,否则多线程写的时候会Copy出N个副本出来。

缺点:

  1. 消耗内存(拷贝数组的时候);
  2. 不能用于实时读(如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList);
    当不清楚放置多少数据做add多少次操作则不建议使用;

优点:

  1. 适合读多写少的场景;读(不加锁)写(加锁)分离
  2. 可以保证数据最终一致性
  3. 使用时另外开辟空间;避免并发冲突

CopyOnWriteArrayList.add()方法源码如下:

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //1、先加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //2、拷贝数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
             //3、将元素加入到新数组中
            newElements[len] = e;
             //4、将array引用指向到新数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

读的时候不需要加锁,但是如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。CopyOnWriteArrayList.get()方法源码如下:

public E get(int index) {
        return get(getArray(), index);
    }

CopyOnWriteArrayList容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题:

  • 内存占用问题:
    因为CopyOnWriteArrayList的写操作时的复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长。

  • 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。

  • 数据一致性问题:
    CopyOnWriteArrayList容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,即实时读取场景,那么请不要使用CopyOnWriteArrayList容器。

CopyOnWrite的应用场景:

综上,CopyOnWriteArrayList并发容器用于读多写少的并发场景。不过这类慎用因为谁也没法保证CopyOnWriteArrayList
到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

代码示例:

package com.mmall.concurrency.example.concurrent;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class CopyOnWriteArrayListExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static List<Integer> list=new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("listSize:{}",list.size());
    }

    private static void update(int i){
        list.add(i);
    }
}
/*
14:12:43.786 [main] INFO com.mmall.concurrency.example.concurrent.CopyOnWriteArrayListExample - listSize:5000
 */

HashSet, TreeSet -> CopyOnWriteArraySet,ConcurrentSkipListSet

HashSet -> CopyOnWriteArraySet
详细介绍 https://blog.csdn.net/bingdianone/article/details/83627086
CopyOnWriteArraySet是线程安全的,它底层的实现使用了CopyOnWriteArrayList,因此和CopyOnWriteArrayList概念是类似的。使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不可变的数组快照,所以迭代器不支持可变的 remove 操作。

CopyOnWriteArraySet适合于具有以下特征的场景:

  • set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。

CopyOnWriteArraySet缺点:

  • 因为通常需要复制整个基础数组,所以可变操作(add、set 和 remove 等等)的开销很大。
package com.mmall.concurrency.example.concurrent;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;


import java.util.Set;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class CopyOnWriteArraySetExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Set<Integer> set=new CopyOnWriteArraySet<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("setSize:{}",set.size());
    }

    private static void update(int i){
        set.add(i);
    }
}
/*
20:53:49.376 [main] INFO com.mmall.concurrency.example.concurrent.CopyOnWriteArraySetExample - setSize:5000
 */

TreeSet -> ConcurrentSkipListSet
ConcurrentSkipListSet是jdk6新增的类,它和TreeSet一样是支持自然排序的,并且可以在构造的时候定义Comparator<E> 的比较器,该类的方法基本和TreeSet中方法一样(方法签名一样)。和其他的Set集合一样,ConcurrentSkipListSet是基于Map集合的,ConcurrentSkipListMap便是它的底层实现
  在多线程的环境下,ConcurrentSkipListSet中的contains、add、remove操作是安全的,多个线程可以安全地并发执行插入、移除和访问操作。但是对于批量操作 addAll、removeAll、retainAll 和 containsAll并不能保证以原子方式执行。理由很简单,因为addAll、removeAll、retainAll底层调用的还是contains、add、remove的方法,在批量操作时,只能保证每一次的contains、add、remove的操作是原子性的(即在进行contains、add、remove三个操作时,不会被其他线程打断),而不能保证每一次批量的操作都不会被其他线程打断。所以在进行批量操作时,需自行额外手动做一些同步、加锁措施,以此保证线程安全。另外,ConcurrentSkipListSet类不允许使用 null 元素,因为无法可靠地将 null 参数及返回值与不存在的元素区分开来。

package com.mmall.concurrency.example.concurrent;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Set;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class ConcurrentSkipListSetExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Set<Integer> set=new ConcurrentSkipListSet<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("SkipListSetSize:{}",set.size());
    }

    private static void update(int i){
        set.add(i);
    }
}
/*
20:55:53.787 [main] INFO com.mmall.concurrency.example.concurrent.ConcurrentSkipListSetExample - SkipListSetSize:5000
 */

HashMap, TreeMap-> ConcurrentHashMap,ConcurrentSkipListMap

HashMap-> ConcurrentHashMap
HashMap的并发安全版本是ConcurrentHashMap,但ConcurrentHashMap不允许 null 值。在大多数情况下,我们使用map都是读取操作,写操作比较少。因此ConcurrentHashMap针对读取操作做了大量的优化,所以ConcurrentHashMap具有很高的并发性,在高并发场景下表现良好。

package com.mmall.concurrency.example.concurrent;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class ConcurrentHashMapExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Map<Integer,Integer> hashMap=new ConcurrentHashMap<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("ConcurrentHashMapSize:{}",hashMap.size());
    }

    private static void update(int i){
        hashMap.put(i,i);
    }
}
/*
20:58:39.945 [main] INFO com.mmall.concurrency.example.concurrent.ConcurrentHashMapExample - ConcurrentHashMapSize:5000
 */

TreeMap-> ConcurrentSkipListMap
ConcurrentSkipListMap的底层是通过跳表来实现的。跳表是一个链表,但是通过使用“跳跃式”查找的方式使得插入、读取数据时复杂度变成了O(logn)。
  有人曾比较过ConcurrentHashMap和ConcurrentSkipListMap的性能,在4线程1.6万数据的条件下,ConcurrentHashMap 存取速度是ConcurrentSkipListMap 的4倍左右。
  但ConcurrentSkipListMap有几个ConcurrentHashMap不能比拟的优点:

  • ConcurrentSkipListMap 的key是有序的
  • ConcurrentSkipListMap 支持更高的并发。ConcurrentSkipListMap的存取时间是O(logn),和线程数几乎无关。也就是说在数据量一定的情况下,并发的线程越多,ConcurrentSkipListMap越能体现出其优势

在非多线程的情况下,应当尽量使用TreeMap。此外对于并发性相对较低的并行程序可以使Collections.synchronizedSortedMap将TreeMap进行包装,也可以提供较好的效率。对于高并发程序,应当使用ConcurrentSkipListMap,能够提供更高的并发度。
  所以在多线程程序中,如果需要对Map的键值进行排序时,请尽量使用ConcurrentSkipListMap,可能得到更好的并发度。
注意,调用ConcurrentSkipListMap的size时,由于多个线程可以同时对映射表进行操作,所以映射表需要遍历整个链表才能返回元素个数,这个操作是个O(log(n))的操作。

package com.mmall.concurrency.example.concurrent;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.*;

@Slf4j
@ThreadSafe
public class ConcurrentSkipListMapExample {

    //请求总数
    public static int clientTotal=5000;
    //同事并发执行的线程数
    public static int threadTotal=200;
    //计数
    public static Map<Integer,Integer> hashMap=new ConcurrentSkipListMap<>();

    public static void main(String[] args) throws InterruptedException {
        //定义一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //信号量(并发数)
        final Semaphore semaphore = new Semaphore(threadTotal);
        //计数器(线程总数)
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count=i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();//当前进程是否可以执行(并发是否小于50)
                    update(count);
                    semaphore.release();//释放进程
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();//关闭线程池
        log.info("ConcurrentSkipListMapSize:{}",hashMap.size());
    }

    private static void update(int i){
        hashMap.put(i,i);
    }
}
/*
21:00:58.871 [main] INFO com.mmall.concurrency.example.concurrent.ConcurrentSkipListMapExample - ConcurrentSkipListMapSize:5000
 */

猜你喜欢

转载自blog.csdn.net/bingdianone/article/details/83415484