java并发编程实战学习 第2章

java并发编程实战学习 第2章

第1章 简介

第2章 线程安全性

什么是线程安全性

可以同时被多个线程调用,而调用者无需执行额外的动作。

  • 一个无状态的Servlet
@ThreadSafe
public class StatelessFactorizer implements Servlet {
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        encodeIntoRespose(resp, factors);
    }
}

任何一个线程访问,操作都是正确的,那么这就是线程安全的

无状态对象一定是线程安全的

  • 在上面基础上,添加一个计数统计功能
@NotThreadSafe
public class UnsafeCountingFactorizer 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;
        encodeIntoRespose(resp, factors);
    }
}

上面这个例子,在单线程环境中可以正常运行,但是在多线程中,就不是一个线程安全的。问题在++count,包含了三个操作,读取-修改-写入,结果依赖于之前的状态。

在并发编程中,不恰当的执行时序而出现不正确的结果,它叫“竞态条件”。

  • 另外一个例子 初始化
@NotThreadSafe
public class LazyInitRace {
    private ExpensiveObject instance = null;

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

也是典型的“先检查后执行”,是非线程安全的。

要修复这一问题,“读取-修改-写入”操作必须是原子性的

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

    public long getCount() {
        return count;
    }

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

在实际情况中,应尽可能地使用现有的线程安全对象(例如AtomicLong)来管理类的状态。

加锁机制

假设我们希望提升Servlet性能:将最近的计算结果缓存起来,当两个连续的请求对相同的数值进行因数分解时,可以直接使用上次结果,无数重新计算。

代码进行修改

@NotThreadSafe
public class UnsafeCachingFactorizer {
    private final AtomicReference<BigInteger> lastNumber = new AtomicReference<>();
    private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<>();

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        if(i.equals(lastNumber.get())) {
            encodeIntoRespose(resp, lastFactors.get());
        } else {
            BigInteger[] factors = factor(i);
            lastNumber.set(i);
            lastFactors.set(factors);
            encodeIntoRespose(resp, factors);
        }
    }

}

但是仍然不是线程安全的,因为变量虽然使用了线程安全对象,但是这个例子变量和变量之间并不是独立的。需要保证同时去更新。

要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

Java提供了一种内置锁的机制来支持原子性:同步代码块(Synchronized Block)。

synchronized (lock) {
//访问或修改由锁保护的共享状态
}

Java的内置锁相当于一种互斥体,最多只有一个线程能持有这个锁。当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或阻塞,直到线程B释放这个锁。如果B永远不释放锁,那么A永远等。

  • 修改代码
@ThreadSafe
public class SynchronizedFactorizer 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)) {
            encodeIntoRespose(resp, lastFactors);
        } else {
            BigInteger[] factors = factor(i);
            lastNumber = i;
            lastFactors = factors;
            encodeIntoRespose(resp, factors);
        }
    }
}

变量有关联的逻辑都在service方法内,而整个service进行了同步,因此线程安全了。这种修改方法过于极端,造成无法多个客户端进行因数分解。

内置锁时可重入的

如果内置锁不是可重入的,那么这段代码将发生死锁

public class Widget {
    public synchronized void doSomething() {
	   ...
	}
}

public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
	    System.out.println(toString() + ": call doSomething");
		supper.doSomething();
	}
}
用锁来保护状态

对于可能被对多个线程访问的可变状态变量,都需要用锁来保护。

活跃性与性能
  • 优化上面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;
                factor = lastFactors.clone();
            }
        }
        if(factors == null) {
            factors = factor(i);
            synchronized (this) {
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}

不能整个方法笼统的同步,需要一个平衡点。

当执行时间较长的计算或者可能无法快速完成的操作时(例如网络IO,或者控制台IO),一定不要持有锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《Java并发编程实战》是一本经典的Java并发编程指南,由Brian Goetz等人撰写。这本书针对Java多线程并发编程的实践问题给出了详细的解决方案和最佳实践。 该书的主要内容包括:线程安全性、对象的共享与发布、锁的优化、性能与可伸缩性、构建和组合对象、基础构建模块、任务执行、取消与关闭、线程池的使用、显式锁、构建自定义的同步工具等。 通过阅读这本书,读者可以了解Java中的各种并发问题,并学习如何设计和实现线程安全的Java应用。该书引入了很多并发编程的常见问题,例如:竞态条件、死锁、活跃性危险等,并提供了一些模式和技术来解决这些问题。 除了基础知识之外,该书还介绍了一些高级的并发编程概念,例如:并发集合、同步类、线程池等。这些内容将帮助读者更好地理解和利用Java并发编程能力。 总的来说,《Java并发编程实战》是一本权威而实用的Java并发编程指南,适合对多线程编程有一定了解的Java开发人员阅读。通过阅读这本书,读者可以更深入地理解Java并发编程的原理与应用,提高自己的并发编程能力。 ### 回答2: 《Java并发编程实战》是由美国计算机科学家布莱恩·戈策等人合著的一本书,是学习Java并发编程的经典教材。这本书系统地介绍了Java中的多线程、并发和并行编程的基本知识和应用。该书分为三个部分,分别是基础篇、高级主题篇和专题扩展篇。 在基础篇中,书中详细介绍了Java内存模型、线程安全性、对象的共享、发布和逸出等概念。同时,还对Java中的锁、线程池、阻塞队列等常用并发工具进行了深入讲解,帮助读者理解并发编程的基本原理和机制。 高级主题篇则讨论了一些更加复杂的并发编程问题,如线程间的协作、线程间通信、并发集合类的使用和自定义的同步工具的设计等内容。该篇通过讲解常见的并发问题和解决方案,提供了应对复杂并发场景的实践经验。 专题扩展篇主要讨论了一些与并发编程相关的主题,如并发性问题的调试与测试、程序性能调优等。这些内容能够帮助读者进一步提升对并发编程的理解和应用水平。 《Java并发编程实战》通过深入浅出的语言和大量实例,帮助读者掌握并发编程的基本概念和技术,为读者提供设计和编写高效多线程程序的实践经验。无论是Java初学者还是有一定经验的开发人员,都可以从中获得丰富的知识和实用的技巧,加速自己在并发编程领域的成长。 ### 回答3: 《Java并发编程实战》是一本经典的Java多线程编程指南,被广泛认可为学习并发编程的必读之作。本书由Brian Goetz等多位并发编程领域的专家合著,内容全面且深入浅出,适合从初学者到高级开发人员阅读。 该书分为四个部分,共16。第一部分介绍了并发编程的基础知识,包括线程安全性、对象的共享、对象组合等。第二部分讲解了如何构建可复用的并发构件,包括线程安全性、发布与初始化安全性等。第三部分深入讨论了Java并发编程中常见的问题和挑战,例如活跃性、性能与可伸缩性等。第四部分则介绍了一些高级主题,如显式锁、原子变量和并发集合等。 书中包含了大量的示例代码和实践案例,可以帮助读者更好地理解并发编程的概念和技术。此外,本书还提供了一些最佳实践和经验教训,帮助读者避免常见的并发编程陷阱和错误。 《Java并发编程实战》从理论到实践的结合非常好,书中所介绍的内容都是经过实践验证的,具有很高的可靠性和实用性。无论是初学者还是有一定经验的开发人员,都可以从中获得实际应用的知识和经验。 综上所述,如果你想系统地学习Java并发编程,了解如何编写高效、可靠的多线程代码,那么《Java并发编程实战》是一本值得推荐的书籍。它可以帮助你深入理解并发编程的原理和技术,提高自己在并发编程领域的能力和水平。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值