Java高级程序设计笔记 • 【第3章 多线程(二)】
全部章节 >>>>
本章目录
3.1 同步代码块
3.1 线程安全
- 多线程编程时,由于系统对线程的调度具有一定的随机性,所以,使用多个线程操作同一个数据时,容易出现线程安全问题
- 当多个线程访问同一个资源时,如果控制不好,也会造成数据的不正确性
- 以银行取钱为例:
- 用户输入账户、密码,系统判断用户的账户、密码是否匹配
- 用户输入取款金额 系统判断账户余额是否大于取款金额
- 如果余额大于等于取款金额,则取款成功,否则取款失败
3.1.1 模拟银行取款
示例:创建模拟两个线程的取款类 DrawThread,该类继承 Thread 类。取钱的业务逻辑为当余额不足时无法提取现金,当余额足够时系统吐出钞票,减少余额
public class DrawThread extends Thread {
// 模拟用户账户
private Account account;
// 当前线程索取钱数
private double drawAccount;
//完成数据初始化工作
public DrawThread(String name, Account account, double drawAccount) {
super(name);
this.account = account;
this.drawAccount = drawAccount;}
public void run() {
// 账户余额大于取钱数据
if (account.getBalance() >= drawAccount) {
System.out.println(this.getName() + "\t 取款成功 ! 吐钞 :" + drawAccount);
// 修改余额
account.setBalance(account.getBalance() - drawAccount);
System.out.println("\t 余额 : " + account.getBalance());
} else {
System.out.println(this.getName() + " 取钱失败!余额不足 ");}
}
}
// 当多个线程同时修改同一个共享数据时,将涉及数据安全问题
说明:
- 由于多线程并发问题,一个线程执行余额操作可能未完毕,另外一个线程读取或者也在操作余额,必然会引起数据的不准确性
- 这个时候需要在线程中加入对数据的保护机制,从而达到防止并发引起的数据不准确。
3.1.2 同步代码块的使用
Java中多线程中引入了同步监视器,使用同步监视器的常用方式是使用同步代码块,保证同一时间只能一个线程对敏感数据的操作
语法:
synchronized( 要锁定的对象 ){
// 同步代码块的执行体
}
注意:任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完毕后,该线程会释放对该同步监视器的锁定
示例:使用同步代码块改进银行取款程序
synchronized (account) {
// 账户余额大于取钱数据
if (account.getBalance() >= drawAccount) {
System.out.println(this.getName() + "\t 取款成功 ! 吐钞 :" + drawAccount);
// 修改余额
account.setBalance(account.getBalance() - drawAccount);
System.out.println("\t 余额 : " + account.getBalance());
} else {
System.out.println(this.getName() + " 取钱失败!余额不足 ");
}
}
在run方法中加入同步代码块,锁定当前账户对象,保证其他线程无法同时操作
说明:
- 方法体中使用 synchronized 将account对象进行锁定,此种操作符合“加锁 修改 释放锁”的逻辑
- 任何线程在修改指定资源前,首先要对该资源进行锁定,在加锁期间,其他线程无法修改该资源,当该线程修改完成后,该线程释放对该资源的锁定,供下一个获取 CPU 资源的线程使用
3.1.3 实践练习
3.2 同步方法
3.2.1 同步方法
与同步代码块对应,Java 的多线程还提供了同步方法。同步方法就是使用 synchronized 关键字来修饰某个方法。
语法:
访问修饰符 synchronized 返回值类型 方法名称 ( 方法参数 ){
// 同步方法的方法体
}
说明:同步方法的同步监视器就是 this 当前对象本身,如果某个线程调用对象上的同步方法,首先就请求给对象上锁,然后执行方法体,最后释放锁。另一个调用同一个对象上同步方法的线程会被阻塞,直到锁被释放
3.2.2 同步方法的使用
示例:使用同步方法实现取款时数据安全控制
public class Account {
//账号、余额属性略…
// 提供一个线程安全的 draw() 方法来完成取钱操作
public synchronized void draw(double drawAmount) {
//部分代码略
// 修改余额关键代码
this.balance -= drawAmount;
System.out.println(" 账户 <" + this.getNo() + "> 余额为:" + this.getBalance() + " 元 ");}
//getter/setter方法略
}
public class SyncMethodThread extends Thread {
//账户对象和余额属性略
……
//线程类中的run主体方法,调用账户对象的取钱方法
public void run() {
// 直接调用 account 对象的 draw() 方法来执行取钱
// 同步方法的同步监视器是 this,this 代表调用 draw() 方法的对象
// 也就是说,线程进入 draw() 方法之前,必须先对 account 的对象加锁
account.draw(drawAmount); }
}
说明:线程对象的run方法中,未直接编写取款代码,而是调用Account账户对象的取款方法,而账户类中的取款方法使用synchronized关键字修饰,保证了同时只允许一个线程对账户对象完成取款操作,从而解决了并发时数据安全问题
3.2.3 实践练习
3.3 死锁
3.3.1 死锁的概述
同步就是指一个线程要等待另外一个线程执行完毕后才会继续执行的一种操作形式。虽然在一个程序中使用同步可以保证资源共享操作的正确性,但是过多同步或者同步控制不正确也会产生问题

3.3.2 死锁的产生
所谓死锁,就是指两个线程都在等待彼此先完成,造成了程序的停滞状态。一般情况下,程序的死锁都是在程序运行时设计不当引发出现
模拟父亲操作:
public class Father {
// 定义父亲说话的方法
public void say() {
System.out.println(" 父亲对孩子说:把考试成绩单 给我,就给你玩具。");
}
// 定义得到试卷的方法
public void get() {System.out.println(" 父亲得到考试成绩单 ");}
}
模拟儿子操作:
public class Child {
// 定义孩子说话的方法
public void say() {
System.out.println(“ 孩子对父亲说:把玩具给我,就给你考试成绩单。");
}
// 定义孩子得到玩具的方法
public void get() {System.out.println(" 孩子得到玩具 ");}
}
示例:创建线程类,分别控制父子对象的执行
public class ThreadLock implements Runnable {
// 实例化一个静态 Father 类型的对象
static Father father = new Father();
// 实例化一个静态 Child 类型的对象
static Child child = new Child();
// 声明标记,用于判断哪个对象先执行
private boolean flag = false;
public void run() {
//分别控制父子执行,进行同步控制,代码略…
}
}
经验:
多个线程访问同一资源时,一定要考虑到同步问题,但过多的同步或者范围过大的同步会容易带来死锁
在进行多线程开发时,如果遇到同步问题,尽量缩小同步代码块范围或做好全面的测试工作,尽量避免出现死锁,否则程序将出现无限等待状态
3.3.3 实践练习
3.4 ThreadLocal类
3.4.1 ThreadLocal 类的概述
线程局部变量(ThreadLocal)是 Java 提供的一个线程安全类,通过使用 ThreadLocal 类可以简化多线程编程时的并发访问,使用这个工具类可以很便捷地隔离多线程程序的竞争资源
ThreadLocal 类的功能就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突
3.4.2 ThreadLocal 类的常用方法
ThreadLocal类为我们提供了大量的方法以便使用
|
方法名 |
作用 |
|
T get() |
返回当前线程所对应的线程局部变量值 |
|
void remove() |
删除当前线程局部变量的值 |
|
void set(T value) |
设置当前线程的线程局部变量值 |
3.4.3 ThreadLocal的使用
示例:作家写了 3 本书,每本书出版 5 次,模拟书籍出售情况
public class Writer {
private String name; // 作家姓名
// 定义一个 ThreadLocal 类型的变量,该变量将是一个线程的局部变量,用来保存作家每本作品的 总出版数量
private ThreadLocal<Integer> publishNumber = new ThreadLocal<Integer>();
public Writer(String name) {this.name = name; // 初始化姓名构造函数}
// 通过 ThreadLocal 类的 get() 方法,返回保存在线程中的变量值
public Integer getPublishNumber() {return publishNumber.get();}
//……
public class CalculationNumber implements Runnable {
private Writer writer; // 用于保存作家对象
private Random random = new Random(); // 实例化一个随机数对象
private int perEditionNumber = 0; // 用于保存每次出版的册数
public CalculationNumber(Writer writer) {this.writer = writer;}
public void run() {//加入线程延迟,模拟初版书籍5次……}
- ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量
- 它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题
- 每个Thread的对象都有一个ThreadLocalMap,当创建一个ThreadLocal的时候,就会将该ThreadLocal对象添加到该Map中,其中键就是ThreadLocal,值可以是任意类型
3.4.4 实践练习
总结:
- 多线程编程时,由于系统对线程的调度具有一定的随机性,所以,使用多个线程操作同一个数据时,容易出现线程安全问题
- 使用synchronized关键字可以实现多线程资源共享时的安全问题,包括同步代码块和同步方法
- 同步虽然可以解决线程之间数据安全,但是同步范围过大或者过多容易引发死锁,所以必须进行优化设计
- ThreadLocal 是 Java 提供的一个线程安全类,通过使用 ThreadLocal 类可以简化多线程编程时的并发访问,使用这个工具类可以很便捷地隔离多线程程序的竞争资源
Java高级程序设计笔记 • 【第3章 多线程(二)】的更多相关文章
- Java高级程序设计笔记 • 【目录】
持续更新中- 我的大学笔记>>> 章节 内容 实践练习 Java高级程序设计作业目录(作业笔记) 第1章 Java高级程序设计笔记 • [第1章 IO流] 第2章 Java高级程序设 ...
- Java高级程序设计笔记 • 【第2章 多线程(一)】
全部章节 >>>> 本章目录 2.1 线程的概述 2.1.1 进程 2.1.2 多线程优势 2.1.3 Thread 类 2.1.4 实践练习 2.2 Runnable接口 ...
- Java高级程序设计笔记 • 【第4章 网络编程】
全部章节 >>>> 本章目录 4.1 网络基础知识 4.1.1 IP地址 4.1.2 端口号 4.1.3 使用InetAddress 4.1.4 InetAddress 类 ...
- Java高级程序设计笔记 • 【第6章 设计模式】
全部章节 >>>> 本章目录 6.1 设计模式 6.1.1 设计模式概述和分类 6.1.2 单列模式介绍 6.1.3 单例模式的实现 6.1.4 实践练习 6.2 单例模式 ...
- Java高级程序设计笔记 • 【第5章 XML解析】
全部章节 >>>> 本章目录 5.1 XML 文档概述 5.1.1 XML文档结构 5.1.1 XML结构说明 5.1.1 XML文档元素 5.1.2 XML文档语法规范 ...
- Java高级程序设计笔记 • 【第1章 IO流】
全部章节 >>>> 本章目录 1.1 File类访问文件 1.1.1 File 类 1.1.2 File 类方法 1.1.3 实践练习 1.2 文件过滤器 1.2.1 Fi ...
- Javascript高级程序设计笔记 <第五章> 引用类型
一.object类型 创建object实例的方式有两种: //第一种使用new操作符跟构造函数 var person= new Object(); person.name="小王" ...
- Java高级程序设计作业目录(作业笔记)
持续更新中............. Java高级程序设计笔记 • [目录] 我的大学笔记>>> 第1章 IO流>>> 1.1.3 编写Java程序,在电脑硬盘里, ...
- Java Web程序设计笔记 • 【目录】
章节 内容 实践练习 Java Web程序设计作业目录(作业笔记) 第1章 Java Web程序设计笔记 • [第1章 Web应用程序] 第2章 Java Web程序设计笔记 • [第2章 JSP基础 ...
随机推荐
- 用UIScrollview做一个网易scrollviewbar
效果如上,点击出现的图片是用UIImageview添加上的,比较简陋 我用了两种方法,第一种是直接在viewcontroller里面写代码 第二种是用了一个类来封装这个scrollviewbar 对外 ...
- sql优化的8种方式 (下)
五.条件列表值如果连续使用between替代in 六.无重复记录的结果集使用union all合并 MySQL数据库中使用union或union all运算符将一个或多个列数相同的查询结 ...
- springmvc资源文件访问不到,undefined,jsp引用js文件目录
资源访问失败: 该模块下springmvc.xml文件中添加配置: <mvc:resources mapping="/js/**" location="/js/&q ...
- ssm动态查询向前台传json
1.数据协议层 public User selectById(Integer id);//通过id值查询用户 2.数据层 <select id="selectById" re ...
- 关于python中的随机种子——random_state
random_state是一个随机种子,是在任意带有随机性的类或函数里作为参数来控制随机模式.当random_state取某一个值时,也就确定了一种规则. random_state可以用于很多函数,我 ...
- 转:select、poll、epoll之间的区别总结[整理]
转:select.poll.epoll之间的区别总结[整理] select,poll,epoll都是IO多路复用的机制.I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就 ...
- secret_file
拿到题目例行检查,进入main函数 这个逆向有些复杂,程序首先让我们像dest输入256个字符,我们可以看到关键的strcmp(v15,v17),若相等则执行poppen poppen这个函数有额外的 ...
- 工厂为什么要进行计划排产,APS高级计划排程系统的优势作用是什么?
我们每个人的指挥中心是大脑,大脑对我们身体发出各种各样的指令,不停的告诉我们身体去干什么. 那么,一个制造企业的指挥中心是哪里?工厂每天都会接到各种各样的订单,通过几百上千的工人,使用各种设备来生产. ...
- 深刨显式锁ReentrantLock原理及其与内置锁的区别,以及读写锁ReentrantReadWriteLock使用场景
13.显示锁 在Java5.0之前,在协调对共享对象的访问时可以使用的机制只有synchronized和volatile.Java5.0增加了一种新的机制:ReentrantLock.与之前提到过的机 ...
- CF1108A Two distinct points 题解
Content 有 \(q\) 次询问,每次询问给定四个数 \(l_1,r_1,l_2,r_2\).对于每次询问,找到两个数 \(a,b\),使得 \(l_1\leqslant a\leqslant ...