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

  • 线程属性
    • 线程优先级
    • 守护线程
    • 未捕获异常处理器
  • 同步
    • 竞争条件的一个例子
    • 竞争条件详解
    • 锁对象

线程属性

线程的各种属性包括:线程优先级、守护线程、线程组以及处理未捕获异常的处理器。

线程优先级

在Java程序设计语言中,每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。每当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。

守护线程

可以通过调用

t.setDaemon(true);

将线程转换为守护线程(daemon thread)。守护线程的唯一用途是为其他线程提供服务。 计时线程就是一个例子,它定时地发送"计时器嘀嗒"信号给其他线程或清空过时的高速缓存项的线程。当只剩下守护线程时,虚拟机就退出了,由于如果只剩下守护线程,就没必要继续运行程序了。守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

未捕获异常处理器

**线程的run方法不能抛出任何受查异常,但是,非受查异常会导致线程终止。在这种情况下,线程就死亡了。**但是,不需要任何catch子句来处理可以被传播的异常。相反,就在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。

该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。这个接口只有一个方法。

void uncaughtException(Thread t, Throwable e)

可以用setUncaughtExceptionHandler方法为任何线程安装一个处理器。也可以用Thread 类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认的处理器。替换处理器可以使用日志API发送未捕获异常的报告到日志文件。

同步

在大多数实际的多线程应用中, 两个或两个以上的线程需要共享对同一数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了一个修改该对象状态的方法,这时根据各线程访问数据的次序,可能会产生讹误的对象。这样一个情况通常称为竞争条件(race condition)。

竞争条件的一个例子

为了避免多线程引起的对共享数据的讹误,必须学习如何同步存取。

在下面的测试程序中,模拟一个有若干账户的银行。随机地生成在这些账户之间转移钱款的交易。每一个账户有一个线程。每一笔交易中,会从线程所服务的账户中随机转移一定数目的钱款到另一个随机账户。

模拟代码具有transfer方法的Bank类。该方法从一个账户转移一定数目的钱款到另一个账户。如下是Bank类的transfer方法的代码。

public void transfer(int from, int to, double amount) {// CAUTION: unsafe when called from multiple threads \System.out.print(Thread.currentThread());accounts[from] -= amount;System.out.printf("%10.2f from %d to %d",amount, from, to);accounts[to] += amount;System.out.printf("Total Balance:%10.2f%n", getTotalBalance());
}

这里是Runnable类的代码。它的run方法不断地从一个固定的银行账户取出钱款。在每一次迭代中,run方法随机选择一个目标账户和一个随机账户,调用bank对象的transfer方法,然后睡眠。

Runnable r = () -> {try {while(true) {int toAccount=(int)(bank.size() * Math.random();double amount = MAX_AMOUNT * Math.random();bank.transfer(fromAccount, toAccount, amount);Thread.sleep((int)(DELAY * Math.random());}}    catch (InterruptedException e){}
};

当这个模拟程序运行时,不清楚在某一时刻某一银行账户中有多少钱。但是,知道所有账户的总金额应该保持不变,因为所做的一切不过是从一个账户转移钱款到另一个账户。

在每一次交易的结尾,transfer方法重新计算总值并打印出来。本程序永远不会结束。只能手动终止这个程序。下面是程序的输出:

Thread[Thread-11,5,main] 588.48 from 11 to 44 Total Balance: 100000.00
Thread[Thread-12,5,main] 976.11 from 12 to 22 Total Balance: 100000.00
Thread[Thread-14,5,main] 521.51 from 14 to 22 Total Bal ance: 100000.00
Thread[Thread-13,5,main] 359.89 from 13 to 81 Total Bal ance: 100000.00
...
Thread[Thread-36,5,main] 401.71 from 36 to 73 Total Balance:  99291.06
Thread[Thread-35,5,main] 691.46 from 35 to 77 Total Bal ance:   99291.06
Thread[Thread-37,5,main] 78.64 from 37 to 3 Total Balance:   99291.06
Thread[Thread-34,5,main] 197.11 from 34 to 69 Total Balance:   99291.06
Thread[Thread-36,5,main] 85.96 from 36 to 4 Total Balance:   99291.06

正如前面所示,出现了错误。在最初的交易中,银行的余额保持在$100000,这是正确的,因为共100个账户,每个账户$1000。但是,过一段时间,余额总量有轻微的变化。当运行这个程序的时候,会发现有时很快就出错了,有时很长的时间后余额发生混乱。这样的状态不会带来信任感。

竞争条件详解

上述的程序,其中有多个线程更新银行账户余额。一段时间之后,错误就会出现,总额要么增加,要么变少。当两个线程试图同时更新同一个账户的时候,这个问题就有可能出现。假定两个线程同时执行指令

accounts[to] += amount;

问题在于这不是原子操作。该指令可能被处理如下:

  • accounts[to]加载到寄存器。
  • 增加 amount。
  • 将结果写回accounts[to]

现在,假定第1个线程执行步骤1和2,然后,它被剥夺了运行权。假定第2个线程被唤醒并修改了accounts数组中的同一项。然后,第1个线程被唤醒并完成其第3步。这样,这一动作擦去了第二个线程所做的更新。于是,总金额不再正确。


transfer方法的执行过程中可能会被中断。如果能够确保线程在失去控制之前方法运行完成, 那么银行账户对象的状态永远不会出现讹误。

锁对象

有两种机制防止代码块受并发访问的干扰。Java语言提供一个synchronized关键字达到这一目的,并且Java SE 5.0引人了ReentrantLock类。synchronized关键字自动提供一个锁以及相关的"条件",对于大多数需要显式锁的情况,这是很便利的。 用ReentrantLock保护代码块的基本结构如下:

myLock.lock();// a ReentrantLock object
try {critical section
}
finally {myLock.unlock();// make sure the lock is unlocked even if an exception is thrown ”
}

这一结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock 语句。当其他线程调用lock时,它们被阻塞,直到第一个线程释放锁对象。

注意:把解锁操作括在finally子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。
注意:如果使用锁,就不能使用带资源的try语句。

使用锁来保护Bank类的transfer方法。

public class Bank {private Lock bankLock = new ReentrantLock();// ReentrantLock implements the Lock interface public void transfer(int from, int to, int amount) {bankLock.lock();try {System.out.print(Thread.currentThread();accounts[from] -= amount;System.out.printf("%10.2f from %d to %d", amount, from, to);accounts[to] += amount;System.out.printf("Total Balance:%10.2f%n",getTotalBalance());}finally {bankLock.unlock();}}
}

**假定一个线程调用transfer,在执行结束前被剥夺了运行权。假定第二个线程也调用transfer,由于第二个线程不能获得锁,将在调用lock方法时被阻塞。它必须等待第一个线程完成transfer方法的执行之后才能再度被激活。当第一个线程释放锁时,那么第二个线程才能开始运行。**如下图:

添加加锁代码到transfer方法并且再次运行程序。可以永远运行它,而银行的余额不会出现讹误。

注意每一个Bank对象有自己的ReentrantLock对象。如果两个线程试图访问同一个Bank 对象,那么锁以串行方式提供服务。但是,如果两个线程访问不同的 Bank 对象,每一个线程得到不同的锁对象,两个线程都不会发生阻塞。本该如此,因为线程在操纵不同的Bank实例的时候,线程之间不会相互影响。

锁是可重入的,因为线程可以重复地获得已经持有的锁。锁保持一个持有计数(hold count)来跟踪对lock方法的嵌套调用。线程在每一次调用lock都要调用unlock来释放锁。 由于这一特性,被一个锁保护的代码可以调用另一个使用相同的锁的方法。

例如,transfer方法调用getTotalBalance方法,这也会封锁 bankLock对象,此时 bankLock 对象的持有计数为2。当getTotalBalance方法退出的时候,持有计数变回1。当transfer方法线程1退出的时候, 持有计数变为 0。线程释放锁。

通常, 可能想要保护需若干个操作来更新或检查共享对象的代码块。要确保这些操作完成后, 另一个线程才能使用相同对象。

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

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

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

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

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

  3. 《Java核心技术 卷Ⅰ》读书笔记一

    Java核心技术·卷 I(原书第10版) 作者: [美] 凯.S.霍斯特曼(Cay S. Horstmann) 出版社: 机械工业出版社 原作名: Core Java Volume I - Funda ...

  4. 新书推荐 | Java核心技术 卷II 高级特性(原书第11版)

    新书推荐 <Java核心技术 卷II 高级特性(原书第11版)> 长按二维码 了解及购买 全新第11版!针对Java SE9.10.11全面更新!Java领域极具影响力和价值的著作之一,与 ...

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

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

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

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

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

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

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

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

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

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

最新文章

  1. python实现提取jira bug列表
  2. Vue实现仿音乐播放器14-实现搜索页面以及功能
  3. redis的学习使用,第一章
  4. Restful API接口调试工具推荐(Postman, HTTPie)
  5. Javascript中使用正则表达式进行数据验证
  6. int linux 原子操作_linux c++编程之多线程:原子操作如何解决线程冲突
  7. java中抽象类,abstract关键字
  8. 解读Scorm(0):标准
  9. linux安装gcc-c++
  10. C# webclient UploadStringAsync如何得到变量?
  11. Eclipse Java快捷键
  12. 极光笔记 | 极光clickhouse千亿级数据分析实践之路
  13. 群晖使用ClouDNS免费DDNS解析
  14. 【AI PC端算法优化】七,RGB和YUV图像颜色空间互转SSE优化
  15. treetable怎么带参数_好用的TreeTable插件
  16. android 手机格式验证,android – 使用国家/地区代码验证手机号码
  17. 新加坡读计算机专业,【去新加坡读计算机专业】 - 环外新加坡留学网
  18. android 九宫格手势密码 纯代码实现
  19. testerhome职业辅导沙龙
  20. C++11生成随机数(random库)

热门文章

  1. btrace安装,配置,使用,常见异常,解除安全限制
  2. 合肥工业大学 慕课 梦溪笔谈 习题答案
  3. matlab基本知识(入门)
  4. 软件测试工程师笔试面试题带答案(一)
  5. JavaScript(JS) string.italics( )
  6. STM32 使用 ITM 输出调试信息
  7. 1000+医软服务商的首选,新致openhis
  8. ECCV 2022|文本图像分析领域再起波澜,波士顿大学联合MIT和谷歌提出全新多模态新闻数据集NewsStories
  9. 关于BAPI_CONTRACT_CREATEFROMDATA涉及使用价格
  10. 破解RVDS2 2方法分享