您好,登录后才能下订单哦!
java高并发中线程不安全类与写法是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
如果一个类的对象同时可以被多个线程访问,如果不做特殊的同步与并发处理,就很容易表现出线程不安全的现象,比如抛出异常,比如逻辑处理错误等,这种类就是线程不安全类。
@Slf4j 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(); update(); semaphore.release(); } catch (InterruptedException e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}", stringBuilder.length()); } public static void update() { stringBuilder.append("1"); } }
输出结果与我们预期的不一致。StringBuilder是一个线程不安全的类。
我们将StringBuilder换成StringBuffer,可以得到预期的效果。说明StringBuffer是线程安全的。
查看StringBuffer的append方法,发现这个方法与其他方法前添加了synchronized关键字。
StringBuffer因为使用了synchronized关键字,因此在使用的时候会有性能损耗的,因此在做字符串拼接时涉及到多线程可以考虑StringBuffer来处理。
但是很多时候,我们往往在一个方法里面做字符串拼接单独,定义一个StringBuilder变量就可以了。因为在一个方法内部定义局部变量时属于堆栈封闭,这时只有单个线程可以操作对象,不涉及到线程安全问题了。
@Slf4j public class DateFormatExample1 { 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(); update(); semaphore.release(); } catch (InterruptedException e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update() { try { simpleDateFormat.parse("20190729"); } catch (ParseException e) { e.printStackTrace(); log.error("parse Exception" + e); } } }
运行时,会抛出异常:
Exception in thread "pool-1-thread-3" java.lang.NumberFormatException: For input string: "E.177" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2056) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1867) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at com.vincent.example.commonUnsafe.DateFormatExample1.update(DateFormatExample1.java:50) at com.vincent.example.commonUnsafe.DateFormatExample1.lambda$main$0(DateFormatExample1.java:34) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745)
这是线程不安全的,simpleDateFormat不是一个线程安全的类,一种解决办法是将SimpleDateFormat simpleDateFormat = new SimpleDateFormat()放到方法内,封闭堆栈,修改update方法如下:
public static void update() { try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20190729"); } catch (ParseException e) { e.printStackTrace(); log.error("parse Exception" + e); } }
JodaTime是线程安全的:
@Slf4j @ThreadSafe public class DateFormatExample3 { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); 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(); update(count); semaphore.release(); } catch (InterruptedException e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(int i) { log.info("{}, {}", i, DateTime.parse("20190729",dateTimeFormatter).toDate()); } }
通常我们使用这些集合类时他们的对象通常声明在方法里面作为局部变量来使用,很少触发线程不安全的问题,但是一旦定义成static的时候而且多个线程可以进行修改的时候就会容器出问题。例如下面的代码:
@Slf4j public class ArrayListExample { // 请求总数 public static int clientTotal = 5000; // 同时并发执行的线程数 public static int threadTotal = 200; private 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(); update(count); semaphore.release(); } catch (InterruptedException e) { log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); log.info("size:{}",list.size()) ; } public static void update(int i) { list.add(i); } }
输出结果不是我们所预期的。
同样适用HashSet,HashMap也无法输出正确的结果。这些都是线程不安全的。
后面会介绍这些集合对应的线程安全类。
为什么这种写法是线程不安全的?假设a是线程安全的类,即使if(condition(a))是线程安全的操作,handle(a)也是线程安全的,但是两个结合起来就不是线程安全的了,并不是原子性的。
Atomic类在自增的时候,底层实现是通过CAS原理来保证原子性的跟新。
实际过程中,如果遇到这种情况要判断一个对象是否满足某个条件,然后做某个操作,一定先要考虑这个对象是否多线程共享的,如果是多线程共享的一定要在上面加锁,或者保证操作是原子性的才可以。否则会触发线程不安全的。
看完上述内容,你们掌握java高并发中线程不安全类与写法是什么的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。