Java Concurrency(一)

现在在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这个变量,运行之后发现很多错误,有抛出异常的,有结果错误的,如下图

Java Concurrency(一)_第1张图片

究其原因,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();
        }
    }
}



你可能感兴趣的:(java,jvm,多线程,并发,concurrency)