Java核心技术 卷1-总结-18

  • 同步
    • Volatile域
    • final变量
    • 原子性
    • 死锁
    • 线程局部变量
    • 锁测试与超时
    • 读/写锁

同步

Volatile域

  • 多处理器的计算机能够暂时在寄存器或本地内存缓冲区中保存内存中的值。结果是,运行在不同处理器上的线程可能在同一个内存位置取到不同的值。
  • 编译器可以改变指令执行的顺序以使吞吐量最大化。这种顺序上的变化不会改变代码语义,但是编译器假定内存的值仅仅在代码中有显式的修改指令时才会改变。然而,内存的值可以被另一个线程改变。

如果使用锁来保护可以被多个线程访问的代码,那么可以不考虑这种问题。如果向一个变量写入值,而这个变量接下来可能会被另一个线程读取,或者,从一个变量读值,而这个变量可能是之前被另一个线程写入的,此时必须使用同步。

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
例如,假定一个对象有一个布尔标记done,它的值被一个线程设置却被另一个线程查询,可以使用锁:

private boolean done;
public synchronized boolean isDone() { return done; }
public synchronized void setDone() { done = true; }

使用锁并不合适,如果另一个线程已经对该对象加锁,isDonesetDone 方法可能阻塞。在这种情况下,将域声明为volatile是合理的:

private volatile boolean done;
public boolean isDone() { return done; }
public void setDone() { done = true; }

注意:Volatile变量不能提供原子性。例如,方法

public void flipDone() { done = !done; } // not atomic

不能确保翻转域中的值。不能保证读取、翻转和写入不被中断。

final变量

使用锁或volatile修饰符,可以从多个线程安全地读取一个域。还有一种情况可以安全地访问一个共享域,即这个域声明为final。考虑以下声明:

final Map<String, Double> accounts = new HashMap<>();

其他线程会在构造函数完成构造之后才看到这个accounts变量。如果不使用final,就不能保证其他线程看到的是accounts更新后的值,它们可能都只是看到null,而不是新构造的HashMap

注意:对这个映射表的操作并不是线程安全的。如果多个线程在读写这个映射表,仍然需要进行同步。

原子性

假设对共享变量除了赋值之外并不完成其他操作,那么可以将这些共享变量声明为volatile

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。 例如,AtomicInteger类提供了方法incrementAndGetdecrementAndGet,它们分别以原子方式将一个整数自增或自减。例如,可以安全地生成一个数值序列,如下所示:

public static AtomicLong nextNumber = new AtomicLong();
// In some thread...
long id m nextNumber.incrementAndGet();

incrementAndGet方法以原子方式将 AtomicLong 自增,并返回自增后的值。也就是说,获得值、增1并设置然后生成新值的操作不会中断。可以保证即使是多个线程并发地访问同一个实例,也会计算并返回正确的值。

有很多方法可以以原子方式设置和增减值,不过,如果希望完成更复杂的更新,就必须使用compareAndSet方法。例如,假设希望跟踪不同线程观察的最大值。下面的代码是不可行的:

public static AtomicLong largest = new AtomicLong();// In some thread...
largest.set(Math.max(largest.get(), observed));//Error-race condition!

这个更新不是原子的。实际上,应当在一个循环中计算新值和使用compareAndSet:

do {oldvalue = largest.get();newValue = Math.max(oldValue,observed);
} while(!largest.compareAndSet(oldValue, newValue));

如果另一个线程也在更新largest,就可能阻止这个线程更新。这样一来,compareAndSet 会返回false,而不会设置新值。

如果有大量线程要访问相同的原子值,性能会大幅下降,因为乐观更新需要太多次重试。

死锁

锁和条件不能解决多线程中的所有问题。考虑下面的情况:

  1. 线程1和线程2分别向账户1和账户2转入大于自身余额的金额,由于余额都不足以进行转账,两个线程都无法执行下去。
账户1:$200
账户2:$300
线程1:从账户1转移$300到账户2
线程2:从账户2转移$400到账户1

如图所示,线程1和线程2都被阻塞了。因为账户1以及账户2中的余额都不足以进行转账,两个线程都无法执行下去。这样的状态称为死锁(deadlock)。

  1. 导致死锁的另一种途径是让第i个线程负责向第i个账户存钱,而不是从第i个账户取钱。这样一来,有可能将所有的线程都集中到一个账户上,每一个线程都试图从这个账户中取出大于该账户余额的钱。
  2. 还有一种很容易导致死锁的情况:将signalAll方法转换为signal,该程序最终会挂起。signalAll通知所有等待增加资金的线程,与此不同的是signal方法仅仅对一个线程解锁。如果该线程不能继续运行,所有的线程可能都被阻塞。

Java编程语言中没有任何东西可以避免或打破死锁现象。必须仔细设计程序,以确保不会出现死锁。

线程局部变量

使用ThreadLocal辅助类为各个线程提供各自的实例。 例如,SimpleDateFormat类不是线程安全的。假设有一个静态变量:

public static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyy-MM-dd");

如果两个线程都执行以下操作:

String dateStamp = dateFormat.format(new Date());

datcFormat使用的内部数据结构可能会被并发的访问所破坏。可以使用同步,但开销很大;或者也可以在需要时构造一个局部SimpleDateFormat对象,但同样会造成较大的开销。

要为每个线程构造一个实例,可以使用以下代码:

public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

要访问具体的格式化方法,可以调用:

String dateStamp = dateFormat.get().format(new Date());

在一个给定线程中首次调用get 时,会调用initialValue方法。在此之后,get方法会返回属于当前线程的那个实例。

在多个线程中生成随机数也存在类似的问题。java.util.Random类是线程安全的。但是如果多个线程需要等待一个共享的随机数生成器,会很低效。
可以使用ThreadLocal辅助类为各个线程提供一个单独的生成器,不过Java 还另外提供了一个便利类ThreadLocalRandom。只需要做以下调用:

int random = ThreadLocalRandom.current().nextInt(upperBound);

ThreadLocalRandom.current()调用会返回特定于当前线程的Random类实例。

 T get()

得到这个线程的当前值。如果是首次调用get,会调用initialize来得到这个值。

protected initialize()

应覆盖这个方法来提供一个初始值。默认情况下,这个方法返回null。

void set(T t)

为这个线程设置一个新值。

void remove()

删除对应这个线程的值。

static <S> ThreadLocal<S> withInitial(Supplier<? extends S>
supplier)

创建一个线程局部变量,其初始值通过调用给定的supplier生成。

锁测试与超时

线程在调用lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,线程可以立即离开去做其他事情。

if (myLock.tryLock()) {// now the thread owns the lock try { ... }finally{myLock.unlock();}
} else// do something else

调用tryLock时,可以使用超时参数:

if (myLock.tryLock(100, TimeUnit.MILLISECONDS))...

TimeUnit是一个枚举类型,可以取的值包括SECONDSMILLISECONDSMICROSECONDSNANOSECONDS

lock 方法不能被中断。如果一个线程在等待获得一个锁时被中断,中断线程在获得锁之前一直处于阻塞状态。如果出现死锁,那么,lock方法就无法终止。

如果调用带有用超时参数的tryLock,那么如果线程在等待期间被中断,将抛出InterruptedException异常。这是一个非常有用的特性,因为允许程序打破死锁。

在等待一个条件时,也可以提供一个超时:

myCondition.await(100, TimeUnit.MILLISECONDS))

如果一个线程被另一个线程通过调用signalAllsignal激活,或者超时时限已达到,或者线程被中断,那么await方法将返回。

如果等待的线程被中断,await方法将抛出一个InterruptedException异常。

java.util.concurrent.locks.Lock 5.0

boolean tryLock()

尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即便其他线程已经等待很久也是如此。

boolean tryLock(long time, TimeUnit unit)

尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true

void lockInterruptibly()

获得锁,但是会不确定地发生阻塞。如果线程被中断,抛出一个InterruptedException 异常。

读/写锁

java.util.concurrent.locks包定义了两个锁类:ReentrantLock类和ReentrantReadWriteLock类。如果很多线程从一个数据结构读取数据而很少线程修改其中数据的,在这种情况下,允许对读者线程共享访问是合适的。但是,写者线程依然必须是互斥访问的。
下面是使用读/写锁的必要步骤:
(1)构造一个ReentrantReadWriteLock对象:

private ReentrantReadWriteLock rwl = new ReentrantReadwriteLock();

(2)抽取读锁和写锁:

private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();

(3)对所有的获取方法加读锁:

public double getTotal Balance() {readLock.lock();try {...}finally { readLock.unlock();}
}

4)对所有的修改方法加写锁:

public void transfer(...) {writeLock.lock();try{...}finally { writeLock.unlock();}
}

Java核心技术 卷1-总结-18相关推荐

  1. JAVA基础----终弄清java核心技术卷1中的int fourthBitFromRight = (n 0b1000 ) / 0b1000;是怎么回事了。。。

    一个关于位与运算的问题 对于<JAVA核心技术 卷1>(第11版) page43 第三章 3.5.8一节中有个描述如下: 如果n是一个整数变量,而二进制表示的n从右边数第四位1,则 int ...

  2. java12章_【有书共读】java核心技术卷1--第12章

    ==============java核心技术卷1第12章----Swing用户界面组件===========主要内容:1 swing和模型-视图-控制器设计模式2布局管理 3文本输入4选择组件 5菜单 ...

  3. 《Java 核心技术卷1 第10版》学习笔记------异常

    异常处理的任务就是将控制权从错误产生的地方转移给能够处理这种情况的错误处理器 . 7.1.1 异常分类 在 Java 程序设计语言中, 异常对象都是派生于 Throwable 类的一个实例 . 稍后还 ...

  4. java实现图形界面输入半径求圆面积_【读】Java核心技术卷1

    阅读原文:[读]Java核心技术卷1 看到这本书时,我的内心是崩溃的,卷1就700多页,但是这本书是很多前辈所推荐的,想必其中必有精华所在,硬着头皮上吧. 如何阅读本书 拿到书的第一眼肯定去看目录,大 ...

  5. Java 核心技术卷 II(第 8 版) – 读书笔记 – 第 1 章(下)

    22.一旦获得了一个 Charset,就可以在 Java 的 Unicode 和指定的编码格式之间进行转化,下面以 GBK 和 Unicode 之间做为例子. 从 Unicode 到 GBK: imp ...

  6. Java编程思想+Effective Java+Java核心技术+Java核心技术 卷II+Java语言程序设计(中文+英文+源码)

    Java四大名著(中文+英文+源码 ) 传说中的java四大名著,分享出来方便大家学习! 书名如下: Java编程思想 Effective Java(第2版) Java核心技术 卷I(第8版) Jav ...

  7. 《Java 核心技术 卷1》 笔记 第11章 异常、日志、断言和调试

    出现不可预计的问题时,需要进行如下处理: 报告错误 保存操作结果 允许用户退出 本章解决的问题: 验证程序正确性 记录程序错误 调试技巧 11.1 处理异常 程序出现错误时应该: 返回安全状态,能让用 ...

  8. java核心技术卷I 第1-3章 笔记

    java核心技术卷I 第1-3章 本书将详细介绍下列内容: ● 面向对象程序设计 ● 反射与代理 ● 接口与内部类 ● 异常处理 ● 泛型程序设计 ● 集合框架 ● 事件监听器模型 ● 使用Swing ...

  9. Java核心技术 卷1-总结-11

    Java核心技术 卷1-总结-11 Java 集合框架 将集合的接口与实现分离 Collection接口 迭代器 泛型实用方法 集合框架中的接口 Java 集合框架 将集合的接口与实现分离 Java集 ...

最新文章

  1. 后疫情时代,RTC期待新的场景大爆发
  2. ci github 通知_初探CI,Github调戏Action手记——自动构建并发布
  3. 用python可以免费下载音乐吗-利用Python来下载会员歌曲!想让我充会员?不存在的!...
  4. Gibbs sampling [Gibbs采样]
  5. CentOS 下线,TencentOS Server 全新登陆带来最强支持
  6. centos php memcache扩展,linux centos 安装php的memcache扩展
  7. ae2020不支持的视频驱动程序_英伟达发布支持GeForce GTX 1660 SUPER的新Linux图形驱动程序...
  8. 基于ssm整合的web考勤管理系统
  9. 广发99元旅游分期,来一场说走就走的旅行?
  10. 步进电机、伺服电机和舵机通俗解读
  11. MATLAB中拟合线性方程(最小二乘法)
  12. 蚂蚁金服提前批实习面经(2.26)
  13. c语言单位换算转换程序,c语言时间换算(c语言时间换算过n秒)
  14. 监控系统-Prometheus(普罗米修斯)(三)Grafana可视化图形工具
  15. 今天零晨的大雨好厉害呀
  16. RF自动化测试框架(一)
  17. python什么证书最有价值,python哪个证书含金量高
  18. 干货|什么是字节码?字节码扩展名是什么?
  19. vi新建一个shell脚本_编写第一个Shell脚本
  20. word文件自动变成只读模式,怎么办?

热门文章

  1. scal实现工厂方法模式
  2. VS2022换主题和背景
  3. MT4安卓版官网下载
  4. 互联网医疗以线下诊所为基地掀起共享医疗风潮
  5. photoshop是中文版怎么改成英文版!!
  6. 使用python进行普通日期和儒略历的转换
  7. c++实现彩色炫酷(?)画面
  8. 使用DirectPlay进行网络互联(2)
  9. 【物联网那些事儿】18 大物联网操作系统,Raspbian Pi、Ubuntu Core、Windows 10 IoT、Micropython、OpenWrt ....你用过哪几个?
  10. 野心外漏?Windows Defender或将独霸杀毒软件市场?