可变状态带来的线程安全问题

可变状态带来的线程安全问题

什么是可变状态

可变状态即一个对象是可变的,换句话说,就是数据是可变的,对于多线程来讲,线程间共享的数据是共享可变状态,对于不变的数据,多线程不用使用锁就可以安全地进行访问。可以这么说,数据的可变状态导致被多线程访问时候存在线程安全问题。

隐藏的可变状态

在我们的正常理解中,解决可变状态可以让数据成为一个常量,确实,很多时候我们也会这么去使用,比如下面:

这是我们很经常使用的日期工具类

/**
 * @author linxu
 * 日期解析类
 */
public class DateParser {
  private  static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
  public  static Date parse(String s) throws ParseException {
      return FORMAT.parse(s);
  }
}

以下是测试代码:

/**
 * 可变状态引起的多线程安全问题
 */
public class DateFormatBug {
    public static void main(String[] args) throws Exception {
        //使用final修饰,表面上看起来是不变状态。
        final String dateString = "2012-01-01";
        final Date dateParsed = DateParser.parse(dateString);
        class ParsingThread extends Thread {
            @Override
            public void run() {
                try {
                    while (true) {
                        Date d = DateParser.parse(dateString);
                        //如果出现不对应的情况,则打印。
                        if (!d.equals(dateParsed)) {
                            System.out.println("Expected: " + dateParsed + ", got: " + d);
                        }
                    }
                } catch (Exception e) {
                    System.out.println("Caught: " + e);
                }
            }
        }
        Thread t1 = new ParsingThread();
        Thread t2 = new ParsingThread();
        t1.start();
        t2.start();
    }
}

对于以上的代码:

  • 涉及到的变量我们都设置成为final,即不可变

  • 采用两个线程无线循环,找到不对应的情况则打印。

    • Caught: java.lang.NumberFormatException: multiple points
    • Caught: java.lang.NumberFormatException: For input string: “”
    • Caught: java.lang.NumberFormatException: empty String
    • Expected: Sun Jan 01 00:00:00 CST 2012, got: Mon Jan 01 00:00:00 CST 1201

    我们看到了出现了以上错误,这是为什么呢?

  • 原因在于FORMAT.parse(s)这行代码中存在着可变状态数据,即在这种外星方法中存在着隐藏的可变状态,导致了线程安全问题。

  • 可见隐藏状态的风险很大,但是JAVA提供的API中并不会跟你说隐藏着什么什么,我们也不能把它归结为BUG只能是尽力去避免这类问题。

如何解决隐藏的可变状态

以上面的日期解析工具为例子,可以这么解决:

public class DateParser {
  private  static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
  private static final ReentrantLock LOCK =new ReentrantLock(false);
  public  static Date parse(String s) throws ParseException {
    try {
      LOCK .lock();
      return FORMAT.parse(s);
    } finally {
      LOCK .unlock();
    }
  }
}

总结

  • 对于能够使用final修饰的数据,尽量使用final修饰
  • 表面上是final的数据,也可能是可变的,永远不要忘记java是一门高级语言
  • 通过正确的测试能够检测出隐藏的可变状态从而进行解决
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值