资讯专栏INFORMATION COLUMN

<java并发编程实战>学习一

xiaoqibTn / 2661人阅读

摘要:无状态的是线程安全的,当无状态变为有状态时就是不安全的破坏了线程的安全性,非原子性操作竞态条件在并发编程中,由于不恰当的执行时序而出现的不正确结果是一种非常重要的情况,被称之为竞态条件。重入意味着获取锁的操作的粒度是线程,而不是调用。

这本书的内容是什么?
   本书提供了各种实用的设计规则,用于帮助开发人员创建安全的和高性能的并发类。
什么类是线程安全的?
    当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
无状态的sevlet是线程安全的, 当无状态变为有状态时就是不安全的
@NotThreadSafe
public class UnsafeCountingFactorizer extends GenericServlet implements Servlet {
    private long count = 0;

    public long getCount() {
        return count;
    }

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;//破坏了线程的安全性 ,非原子性操作
        encodeIntoResponse(resp, factors);
    }

    void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {
    }

    BigInteger extractFromRequest(ServletRequest req) {
        return new BigInteger("7");
    }

    BigInteger[] factor(BigInteger i) {
        // Doesn"t really factor
        return new BigInteger[] { i };
    }
}
竞态条件(Race Condition)
  在并发编程中,由于不恰当的执行时序而出现的不正确结果是一种非常重要的情况,被称之为竞态条件。 
  1)当某个计算结果的正确性取决于多线程的交替执行时序是,那么就会出现竞态条件。换句话说,那就是正确的结果取决于运气。
  2)竞态条件的本质——基于可能失效的观察结果来做出判断或者执行某个计算。
  这类竞态条件被称之为“先检查后执行”。
  下面是一种常见情况,延迟初始化。
    @NotThreadSafe
    public class LazyInitRace {
        private ExpensiveObject instance = null;
    
        public ExpensiveObject getInstance() {
            if (instance == null)
                instance = new ExpensiveObject();
            return instance;
        }
    }
    class ExpensiveObject { }
复合操作
 UnsafeCountingFactorizer 和 LazyInitRace  都包含一组需要以原子方式执行(或者说不可分割)的操作。


加锁机制
类似AtomicLong的AtomicRreference来管理因数分解的数值及分解结果? 

 // 这个方法不正确,尽管这些原子引用本身都是现成安全的,但是组合在一起就不是线程安全的了。 
 //存在lastNumber和lastFactors没有同时更新的情况
 @NotThreadSafe
    public class UnsafeCachingFactorizer extends GenericServlet implements Servlet {
        private final AtomicReference lastNumber
                = new AtomicReference();
        private final AtomicReference lastFactors
                = new AtomicReference();
    
        public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req);
            if (i.equals(lastNumber.get()))
                encodeIntoResponse(resp, lastFactors.get());
            else {
                BigInteger[] factors = factor(i);
                lastNumber.set(i);
                lastFactors.set(factors);
                encodeIntoResponse(resp, factors);
            }
        }
    
        void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
        }
    
        BigInteger extractFromRequest(ServletRequest req) {
            return new BigInteger("7");
        }
    
        BigInteger[] factor(BigInteger i) {
            // Doesn"t really factor
            return new BigInteger[]{i};
        }
    }


要保持状态一致性,就需要在单个原子操作中更新所有先关的状态变量。
内置锁
每个java对象都可以用做一个实现同步的锁,这些锁被称之为内置锁(Intrinsic lock)或监视器锁(Monitor Lock)。线程在进入同步代码块(Synchronized Block)之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正产的控制路径退出,还是通过从代码块中抛出异常退出。
获得锁的位移方法就是进入由这个锁保护的同步代码快或者方法。
@ThreadSafe
public class SynchronizedFactorizer extends GenericServlet implements Servlet {
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lastFactors;
    //同步方法 //并发性能太差,不推荐这么做
    public synchronized void service(ServletRequest req,
                                     ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        if (i.equals(lastNumber))
            encodeIntoResponse(resp, lastFactors);
        else {
            BigInteger[] factors = factor(i);
            lastNumber = i;
            lastFactors = factors;
            encodeIntoResponse(resp, factors);
        }
    }

    void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
    }

    BigInteger extractFromRequest(ServletRequest req) {
        return new BigInteger("7");
    }

    BigInteger[] factor(BigInteger i) {
        // Doesn"t really factor
        return new BigInteger[] { i };
    }
}
重入

如果某个线程试图获得一个已经由他自己持有的锁,那么这个请求就会成功。
“重入”意味着获取锁的操作的粒度是“线程”,而不是“调用”。

用锁来保护状态

注意两点:

1 通常,在简单性与性能之间存在着某种互相制约因素。当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性。
2 当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O操作或者控制台I/O),一定不要持有锁。




文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74387.html

相关文章

  • &lt;java并发编程实战&gt;学习

    摘要:线程封闭当访问共享的可变数据时,通常需要使用同步。如果仅在单线程内访问数据,就不要同步。这种技术成为线程封闭。栈封闭栈封闭是线程封闭的一种特例,在栈封闭中,只能通过局部变量才能访问对象。,对象是正确创建的。 线程封闭 当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不要同步。这种技术成为线程封闭(Thread Confine...

    Richard_Gao 评论0 收藏0
  • &lt;java并发编程实战&gt;学习

    摘要:对象的组合介绍一些组合模式,这些模式能够使一个类更容易成为线程安全的,并且维护这些类时不会无意破坏类的安全性保证。状态变量的所有者将决定采用何种加锁协议来维持变量状态的完整性。所有权意味着控制权。 对象的组合 介绍一些组合模式,这些模式能够使一个类更容易成为线程安全的,并且维护这些类时不会无意破坏类的安全性保证。 设计线程安全的类 在设计线程安全类的过程中,需要包含以下三个基本要素: ...

    tainzhi 评论0 收藏0
  • &lt;java并发编程实战&gt;学习

    摘要:对象的共享上一章介绍了如何通过同步来避免多个线程在同一时刻访问相同的数据,而本章将介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问。为了确保多个线程的之间对内存写入操作的可见性,必须使用同步机制。 对象的共享 上一章介绍了如何通过同步来避免多个线程在同一时刻访问相同的数据,而本章将介绍如何共享和发布对象,从而使它们能够安全地由多个线程同时访问。 列同步代码块和同步方法可...

    zhaochunqi 评论0 收藏0
  • &lt;&lt;Java并发编程实践&gt;&gt;有感 ConcurrentLinkedQueue

    摘要:上集算法实现的优点当一个线程执行任务失败不影响其他线程的进行最大限度的利用资源能提高程序的伸缩性伸缩性不修改任何代码升级硬件就能带来性能上的提高升级硬件带来的性能提高明显就是伸缩性良好的缺点代码复杂影响阅读性刚开始看的时候没有正确的思路理解 ConcurrentLinkedQueue(上集) 算法实现 CAS CAS的优点 当一个线程执行任务失败不影响其他线程的进行 最大限度的利用...

    LucasTwilight 评论0 收藏0
  • 如何利用Python写猜数字和字母的游戏

      Python作为一门常见的编程语言,可以用到的地方是比较的多的,而且他还能够去编程相关的游戏,那么,下文就会给大家教一个比较简单的小游戏,就是写猜数字和字母的游戏,详细的内容可以看下文,看完之后,可以自己去手动敲下代码哦。  前言  学完语法和正在学习语法的时候,我们可以在空闲的时候,写几个简单的小项目,今天我们就用最基础的语法看两个实战语法练习  猜数字游戏  项目游戏说明:让用户输入一个数...

    89542767 评论0 收藏0

发表评论

0条评论

xiaoqibTn

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<