当前位置: 首页 > news >正文

Java Concurrency(一)

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

现在在CPU上,摩尔定律已经失效,大家都不再追求高频率,而是越来越追求多核,阿姆达尔定律似乎更重要一些了。

并发有很多优点,包括充分利用CPU的多核提高性能,更简单的模型(跟异步的callback hell相比)以及响应更灵敏得GUI等。


但是并发也引出很多问题,最重要的是安全性,很多在单线程环境下理所当然正确的程序在并发下都变得不正确,为了写出并发安全的程序,需要付出更多的努力。本文主要介绍一下并发问题的根本原因,以及针对策略。

来看下面简单的代码,我们知道SimpleDateFormat比较费时,所以很多程序里面会定义一个static的SimpleDateFormat,但是奇怪的事情发生了。

package concurrentStudy;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * Created by magicalli on 2014/12/13.
 */
public class SimpleDateFormatTest01 {
    private static final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        System.out.println(dateformat.parse("2014-12-19 11:21:21"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}
模拟多个线程下会用到dateformat这个变量,运行之后发现很多错误,有抛出异常的,有结果错误的,如下图

究其原因,SimpleDateFormat不是线程安全的。只是SimpleDateFormat的doc(据说在JDK6之后才加进去的?)

* Date formats are not synchronized.
 * It is recommended to create separate format instances for each thread.
 * If multiple threads access a format concurrently, it must be synchronized
 * externally.
并发安全,主要由于:
  1. 多个线程
  2. 并发访问共享状态
  3. 并且状态可被修改

要想保证并发安全,上述3个条件,只能满足2个,于是我们有了3种解决方法(C(3, 1) == 3)。


第一种,不要多个线程访问,即加同步或者显示锁:

package concurrentStudy;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * Created by magicalli on 2014/12/13.
 */
public class SimpleDateFormatTest01 {
    private static final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    private static final Object lock = new Object();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        synchronized (lock) {
                            System.out.println(dateformat.parse("2014-12-19 11:21:21"));
                        }
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}
第二种方法,不要共享变量,即“线程封闭(Thread Confinement),又大概有两种,一是栈封闭,即使用局部变量,方法里面自己new一个SimpleDateFormat出来
package concurrentStudy;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * Created by magicalli on 2014/12/13.
 */
public class SimpleDateFormatTest03 {

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        final SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        System.out.println(dateformat.parse("2014-12-19 11:21:21"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}
但是这个有一个坏处,我们一开始说用static的目的是因为new一个SimpleDateFormat比较费时,如果在每次调用的时候new一个出来,感觉有点浪费,于是有了ThreadLocal这个神器。ThreadLocal可以简单理解为一个Map<Thread, T>,即以线程为key的map,这样可以保证每个线程有自己的SimpleDateFormat,避免了各个线程共享。
package concurrentStudy;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * Created by magicalli on 2014/12/13.
 */
public class SimpleDateFormatTest04 {

    private static final ThreadLocal<SimpleDateFormat> dateformat = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            System.out.println("init.......");
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        try {
                            System.out.println(dateformat.get().parse("2014-12-19 11:21:21"));
                        } catch (ParseException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
    }
}



运行结果可以看到,即使调用了100次,也只针对每个线程new了对象,总共只有10个SimpleDateFormat对象,大大减小了开销。

其实更好的解决方法就是,不用JDK自带的Date等一切日期类!!!用Joda-time,api更友好,更简单易用,功能更强大!

package concurrentStudy;

import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;

/**
 * Created by magicalli on 2014/12/13.
 */
public class SimpleDateFormatTest05 {
    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(dateTimeFormatter.parseDateTime("2014-12-19 11:21:21"));
                }
            }).start();
        }
    }
}



转载于:https://my.oschina.net/magicly007/blog/357969

相关文章:

  • ASP.NET中的特殊路径标识~
  • 如何使用ASP.NET开发基于推技术的聊天室?
  • 面试之STAR法则详解
  • JSF 与 HTML 标签的联系
  • c# 中的UserControl是什么 用户控件和自定义控件有什么区别
  • PHP实现懒加载
  • 漂亮的ActionBar效果
  • Entity Framework 的事务 DbTransaction
  • Android中的Unable to start activity ComponentInfo或者Unable to instantiate activity ComponentInfo的错误...
  • HTTP 方法:GET 对比 POST
  • 移动web开发中,好用的小方法
  • Resx 文件无效,未能加载 .RESX 文件中使用的类型
  • JDK中文方框乱码问题
  • 32 脚本编程风格
  • 让低版本的 Android 项目显示出 Material 风格的点击效果
  • 【108天】Java——《Head First Java》笔记(第1-4章)
  • C++类中的特殊成员函数
  • CoolViewPager:即刻刷新,自定义边缘效果颜色,双向自动循环,内置垂直切换效果,想要的都在这里...
  • CSS3 变换
  • Date型的使用
  • JavaScript 一些 DOM 的知识点
  • JDK9: 集成 Jshell 和 Maven 项目.
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • text-decoration与color属性
  • Tornado学习笔记(1)
  • uva 10370 Above Average
  • Vim 折腾记
  • vue-router的history模式发布配置
  • 搭建gitbook 和 访问权限认证
  • 验证码识别技术——15分钟带你突破各种复杂不定长验证码
  • 移动互联网+智能运营体系搭建=你家有金矿啊!
  • AI算硅基生命吗,为什么?
  • Python 之网络式编程
  • 仓管云——企业云erp功能有哪些?
  • 从如何停掉 Promise 链说起
  • (06)金属布线——为半导体注入生命的连接
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (四)库存超卖案例实战——优化redis分布式锁
  • (五)关系数据库标准语言SQL
  • (一)基于IDEA的JAVA基础12
  • (转载)OpenStack Hacker养成指南
  • .NET Framework 服务实现监控可观测性最佳实践
  • .net mvc部分视图
  • .netcore 获取appsettings
  • .net打印*三角形
  • .Net下C#针对Excel开发控件汇总(ClosedXML,EPPlus,NPOI)
  • .py文件应该怎样打开?
  • ?
  • @Not - Empty-Null-Blank
  • @property @synthesize @dynamic 及相关属性作用探究
  • @RequestMapping用法详解
  • @SuppressLint(NewApi)和@TargetApi()的区别
  • [\u4e00-\u9fa5] //匹配中文字符
  • [BT]BUUCTF刷题第4天(3.22)
  • [bug总结]: Feign调用GET请求找不到请求体实体类