java多线程基础知识:如何编写线程安全代码

     如果多个线程访问同一个可变的状态变量时没有使用合适的同步,那么程序就可能会出错。有三种方式可以修复这个问题:

  • 1)不在线程直接共享该状态(设计成只供单线程独自使用)
  • 2)将状态变量变成不可变的变量(可见,没有变量的类也一定是线程安全的)
  • 3)在访问状态变量时使用同步

如何定义一个类是否线程安全:当多个线程访问某个类时,不管运行时环境采用何种调用方式(单线程或多线程)又或者这些线程如何交替执行,并且在主调用代码(即使用该类的客户代码)中不需要任何额外的同步或协同机制,这个类都能表现出正确的预期行为,那么这个类就是线程安全的。

发生线程安全问题的情况有几种:

1、保证原子操作。例如以为i++是原子操作,其实不然,i++是分两步完成的,所以当我们在多个线程并发操作时就可能产生错误,例如以下代码:

public class UnsafeSequence{
    private int value;
    public int getNext(){
           return value++;
    }
}
在多线程时:


正确的书写方法:

public class UnsafeSequence{
    private int value;
    public synchronized int getNext(){
           return value++;
    }
}

解决这个问题,除了可以用同步方法外,还可以使用java提供的原子变量,例如AtomicLong 、AtomicReference

public class CountingFactorizer implements Servlet {
    private final AtomicLong count = new AtomicLong(0);

    public long getCount() { return count.get(); }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();
        encodeIntoResponse(resp, factors);
    }

}
因此,servlet中的变量都必须使用同步或者原子变量。

2、竞态条件(racing condition)

例如我们常用的javabean中的get代码:

public class LazyInitRace {
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }
}

其实在多线程下也是错误的,因为同时两个线程访问getInstance方法就有可能返回了两个不同的对象

正确写法

public class LazyInitRace {
    private ExpensiveObject instance = null;

    public synchronized ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }
}


内置锁(intrinsic Lock)

使用synchronized(this)括起的代码段(如果没有指定其他锁名),都默认使用类中的内置锁,即锁住对象本身。

但使用synchronized声明的方法锁住的是调用该方法的对象。这个要注意。

内置锁是一种互斥锁,只有一个线程能持有锁,其他线程必须等待。synchronized使代码段以串行方式执行,代码段中的变量都受锁保护。

使用锁的简单原则

  • 如果使用了synchronized,那么每次只能供一个线程调用,因此servlet中的方法不宜使用。
  • 一个线程使用一个CPU内核,所以多核环境对多线程程序很有帮助。
  • 同步代码段大会影响吸性能,所以当执行时间长或者无法快速完成的操作,如网络I/O等,建议不要用锁。

structs Action的线程问题:

Struts1 Action是单例模式并且必须是线程安全的,因为仅有Action的一个实例来处理所有的请求。单例策略限制了Struts1 Action能作的事,并且要在开发时特别小心。Action资源必须是线程安全的或同步的。
Struts2 Action可以使用原型模式scope=prototype,这样对象为每一个请求产生一个实例,因此没有线程安全问题。

总结:
在Java的Web服务器环境下开发,要注意线程安全的问题。最简单的实现方式就是在Servlet和Struts Action里不要使用类变量、实例变量,但可以使用类常量和实例常量。如果有这些变量,可以将它们转换为方法的参数传入,以消除它们。
注意一个容易混淆的地方:被Servlet或Action调用的类中(如值对象、领域模型类)中是否可以安全的使用实例变量?如果你在每次方法调用时新建一个对 象,再调用它们的方法,则不存在同步问题---因为它们不是多个线程共享的资源,只有共享的资源才需要同步---而Servlet和Action的实例对于多个线程是共享 的。
换句话说,Servlet和Action的实例会被多个线程同时调用,而过了这一层,如果在你自己的代码中没有另外启动线程,且每次调用后续业务对象时都是先 新建一个实例再调用,则都是线程安全的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值