Thread(5)

接着就是学习一下这个锁,首先我们前面说过用线程安全类就可以保持线程安全,但是我们前面强调了一点,就是只能使用唯一的线程安全类。如果在一个线程安全的类中使用是个安全的状态变量会出现什么的情况呢?
想象一个情形,我们缓存最新的计算结果,以对应两个连续的客户请求相同的数字进行因数分解,希望由此提高Servlet的性能(这未必是一个有效的缓存策略。要实现这个策略,我们需要记住两件事,最新请求的数字和它的因数。
我们前面使用AtomicLong,以线程安全的方式管理计数量的状态,我们可以使用同系的AtomicReference类型管理缓存的数字和它的因数吗?
看个例子先:
@NotThreadSafe
public class UnsafeCachingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();
    private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();
    
    public void service(ServletRequest req,SerlvetResponse resp){
        BigInteger i = extractFromReques(req);
        if(i.equals(lastNumber.get()))
            encodeIntoResponse(resp,lastFactors.get());
        else{
            BigInteger[] factors = factor(i);
            lastNumber.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp,factors);
        }
    }
}
初步一看以为是线程安全的,引用它使用的都是能保证线程安全的类,但是仔细思考一下这样的操作仍然是错误的.
线程安全性的定义要求无论是多线程中的时序或交替操作,都要保证不破坏那些不变的约束。UnsafeCachingFactorizer的一个不变的约束是,缓存在lastFactors中的各个因子的乘积应该等于缓存在lastNumber中的数值,只有遵守这个不变的约束,我们的Serlvet才是正确的。当一个不变约束涉及多个变量时,变量间不是彼此独立的,注意变量不是独立的这个关键词:某个变量的值制约着其他几个变量的值。因此,更新一个变量的时候,要在同一个原子操作中更新其他的几个。
当一些特殊的时序中,UnsafeCachingFactorizer可能破坏这一个不变的约束,即使是原子引用,并且每个set调用都是原子的,我们也无法保证会同时更新lastNumber和lastFactors,有可能CPU在更新你的lastNumber后就当前的线程就让你的线程睡觉去了,然后去调用其他的线程,那么lastFactors这个值就没有被即使的被更新。类似地,也不能保证每个线程都会同时获得两个值,当线程A尝试获取两个值的时间里,线程B可能已经修改了他们了,线程A过后会观察到Servlet违反了不变约束。

内部锁:
java提供了强制原子性的内置锁机制,synchronized块,一个synchronized有两个部分:锁对象的引用,以及这个锁保护的代码块。synchronized方法是对跨越了真个方法体的synchronized块的简短描述,至于synchronized方法的锁,就是该方法所在的对象的本身。
synchronized(lock){
//访问或修改被锁保护的共享状态
}
每个java对象都可以隐式地扮演一个用于同步的锁的角色,这些内置的锁被称作内部锁或监视器锁,执行线程进入synchronized块之前会获得锁,而无论通过正常控制路径退出,还是从块中抛出异常,线程都在放弃对synchronized块的控制时自动释放锁。获得内部锁的唯一途径是,进入这个内部锁保护的同步块或方法。
内部锁在java中扮演了互斥锁的角色,意味着之多只有一个线程可以拥有锁,当线程A尝试请求一个被线程B占有的锁时,线程A必修等待或者阻塞,直到B释放它,如果B永远不释放锁,A将永远等下去。
同一个时间,只能有一个线程可以运行特定锁保护的代码块,因此,同一个锁保护synchronized块会各自原子执行,不会相互干扰。
这样看来用synchronized锁住代码段进行原子操作就最好了吗?万物都是相生相克的,这样是安全了,但是如果你的synchroized锁住的代码段不合理的话,会导致你的程序运行的十分的慢。看一个例子:
@ThreadSafe
public class SynchronizedFactorizer implements Servlet{
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    public synchronized void service(ServletRequest req,ServletResponse resp){
        BigInteger i = extractFormRequest(req);
        if(i.equals(lastNumber))
            encodeIntoResponse(resp,lastFactors);
        else{
            BigInteger[] factors = factor(i);
            lastNumber = i;
            lastFactors = factors;
            encodeIntoResponse(resp,factors);
        }
    }
}
这个例子的确是保证了线程的安全,但是把所有的代码段都放在了synchronized块,这就意味着其他线程必须要等待当前的线程运行结束后才能执行这段代码,可以发现这样的效率是十分的低的。

另外一个重要的概念:重进入
想问大家一个问题:当有一个线程已经占有锁了并且在执行了,在什么情况,什么线程可以获得锁并且执行代码呢?看下面一个例子
public class Widget{
    public synchroized void doSomething(){
        .....
    }
    
    public class LoggingWidget extends Widget{
        public synchroized void doSomething(){
            System.out.println(toString() + ": calling doSomething");
            super.doSomething();
        }
    }
}
这种情况下是可以去获得被占有的锁的,要不这样的例子就会造成死锁了,LoggingWidget占用了Widget的锁了,为什么呢?上面说过:synchronized方法的锁,就是该方法所在对象的锁,这个方法是从Widget继承下来的。接着会打印一个话出来,然后去调用super.doSomething()的方法,可以看出这个基类的方法也被锁住了,它需要获得Widget锁才可以进入,如果是不可重入的话,那么LoggingWidget占据着锁不放,而Widget在请求锁,那么这时候整个程序就在在super.doSomething()停住了。。。但是事实上是不会发生这样的情形的,原因就是Reentrancy(可重入)。
可重进入的实现是通过为每个锁关联一个计数和一个它占有它的线程。当计数为0时,认为锁是未被占有的,线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数置为1.如果同一个线程再次请求这个锁,计数将递增:每次占用线程退出同步块,计数器值将递减,直到计数器达到0时,锁被释放。注意是同一个线程请求这个锁。

用锁来保护状态:
这里有一点是需要注意的:即使用锁来协调访问变量时,每次访问变量都需要用同一个锁。
这里给出一个缓存最新请求和结果的线程安全的Servlet:
@ThreadSafe
public class CachedFactorizer implements Servlet{
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    private long hits;
    private long cacheHits;
    
    public synchronized long getHits(){return hits;}
    public synchronized double getCacheHitRatio(){
        return (double) cacheHits / (double)hits;
    }
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized(this){
            ++hits;
            if(i.equals(lastNumber)){
                ++cacheHits;
                factors = lastFactors.clone();
            }
            if(factors == null){
                factors = factor(i);
                synchronized(this){
                    lastNumber = i;
                    lastFactors = factors.clone();
                }
            }
            encodeIntoResponse(resp,factors);
        }
    }
}

下一块的内容会比较抽象,但我相信我有信心学好它。先把下一章的内容学习完再总结一下,一些地方估计会引用别人写的例子来进行分析.......然后再跟大家一起分享。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值