Java多线程编程(同步、死锁、生产消费者问题)
Java多线程编程(同步、死锁、生产消费):
关于线程同步以及死锁问题:
线程同步概念:是指若干个线程对象并行进行资源的访问时实现的资源处理保护操作;
线程死锁概念:是指两个线程都在等待对方先完成,造成程序的停止的状态;
先了解相应的概念,后面深入理解。
同步:
举个例子:还是卖票问题(经典️)
不存在同步
开启三个线程(售票员)测试
package com.xbhog;
class MyThread implements Runnable {// 定义线程执行类
private int ticket = 3;// 总票数为6张
@Override
public void run() {
while (true) { // 持续卖票
if (this.ticket > 0) { // 还有剩余票
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取当前线程的名字
System.out.println(Thread.currentThread().getName() +
"卖票,ticket = " + this.ticket--);
} else {
System.out.println("***** 票已经卖光了 *****");
break;// 跳出循环
}
}
}
}
public class Java多线程核心 {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "售票员A").start(); // 开启卖票线程
new Thread(mt, "售票员B").start(); // 开启卖票线程
new Thread(mt, "售票员C").start(); // 开启卖票线程
}
}
结果:
第一次随机运行: | 第二次随机运行: |
---|---|
售票员B卖票,ticket = 2 售票员C卖票,ticket = 3 售票员A卖票,ticket = 3 售票员A卖票,ticket = 1 售票员B卖票,ticket = -1 * 票已经卖光了 * 售票员C卖票,ticket = 0 * 票已经卖光了 * * 票已经卖光了 * | 售票员B卖票,ticket = 1 * 票已经卖光了 * 售票员A卖票,ticket = 3 * 票已经卖光了 * 售票员C卖票,ticket = 2 * 票已经卖光了 * |
存在上述原因是因为在代码中两个地方存在多线程访问时出现模糊的问题:
this.ticket>0;
this,ticket--;
假设现在剩余的票数为1张;当第一个线程满足售票的条件的时候(此时还未减少票数),其他的线程也可能同时满足售票的条件,这样同时进行自减减就可能造成负数!
解决上述问题就需要采用线程同步技术实现;
首先需要明确,在Java中实现线程同步(synchronized)的方法有两个:
同步代码块(同步策略加在方法内部)
package com.xbhog.多线程1;
class MyThread implements Runnable { // 定义线程执行类
private int ticket = 3; // 总票数为6张
@Override
public void run() {
while (true) { // 持续卖票
synchronized(this) { // 同步代码块
if (this.ticket > 0) { // 还有剩余票
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"卖票,ticket = " + this.ticket--);
} else {
System.out.println("***** 票已经卖光了 *****");
break; // 跳出循环
}
}
}
}
}
public class Java多线程同步代码块 {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt, "售票员A").start(); // 开启卖票线程
new Thread(mt, "售票员B").start(); // 开启卖票线程
new Thread(mt, "售票员C").start(); // 开启卖票线程
}
}售票员A卖票,ticket = 3
售票员C卖票,ticket = 2
售票员B卖票,ticket = 1
***** 票已经卖光了 *****
***** 票已经卖光了 *****
***** 票已经卖光了 *****同步方法(同步策略加在方法上)
class MyThread implements Runnable { // 定义线程执行类
private int ticket = 3; // 总票数为6张
@Override
public void run() {
while (this.sale()) { // 调用同步方法
;
}
}
public synchronized boolean sale() { // 售票操作
if (this.ticket > 0) {
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
"卖票,ticket = " + this.ticket--);
return true;
} else {
System.out.println("***** 票已经卖光了 *****");
return false;
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "售票员A").start(); // 开启卖票线程
new Thread(mt, "售票员B").start(); // 开启卖票线程
new Thread(mt, "售票员C").start(); // 开启卖票线程
}
}售票员A卖票,ticket = 3
售票员C卖票,ticket = 2
售票员B卖票,ticket = 1
***** 票已经卖光了 *****
***** 票已经卖光了 *****
***** 票已经卖光了 *****
同步的本质:在同一个时间段只允许有一个线程执行资源,所以在此线程对象未执行完的过程中其他线程对象将处于等待的状态。
同步的优点与缺点:
可以保证数据的准确性
数据线程的访问安全
程序的处理性能下降
死锁:
实例:
假如现在又张三想要李四的画,李四想要张三的书,那么张三对李四说:把你的画给我,我就给你书;
李四对张三说:把你的书给我,我就给你画;
此时:张三在等待李四,李四在等待张三,两人一直等待下去形成死锁;
观察线程的死锁:(实现张三李四)
package com.xbhog.死锁;
class Book {
public synchronized void tell(Painting paint) { // 同步方法
System.out.println("张三对李四说:把你的画给我,我就给你书,不给画不给书!");
paint.get();
}
public synchronized void get() { // 同步方法
System.out.println("张三得到了李四的画开始认真欣赏。");
}
}
class Painting {
public synchronized void tell(Book book) { // 同步方法
System.out.println("李四对张三说:把你的书给我,我就给你画,不给书不给画!");
book.get();
}
public synchronized void get() { // 同步方法
System.out.println("李四得到了张三的书开始认真阅读。");
}
}
public class DeadLock implements Runnable{
private Book book = new Book();
private Painting paint = new Painting();
public DeadLock() {
new Thread(this).start();
book.tell(paint);
}
@Override
public void run() {
paint.tell(book);
}
public static void main(String[] args) {
new DeadLock() ;
}
}
由于现在电脑的配置问题,该代码有可能在一次运行中展示不出效果来,需要多次运行观察效果;
效果图:
由此引申出了生产者与消费者模型。
生产者与消费者问题:
首先需要明确生产者与消费者为两个线程对象,是对同一资源进行数据的保存与读取;
基本操作是:生产者生产一个资源,消费者则取走一个资源,一一对应。
对应类关系图:
我们需要设想一个问题,如果不加任何操作的话,会出现什么问题?
数据错位:当生产者线程只是开辟了一个栈空间保存信息名称,在想存数据但是还没存数据的时候切换到了消费者线程上,那么消费者线程将会把这个信息名称与上个信息的内容进行结合联系,这样就造成了数据的错位。
重复数据:当生产者放了若干次的数据,消费者才开始取数据,或者消费者取完,但生产者还没生产新数据时又取了直接已经取过得数据。
解决以上两个问题需要涉及到以下两个知识点:
设置同步代码块或设置同步方法>>>解决数据错误问题
Object线程等待与唤醒>>>解决数据重复设置以及重复取出的问题
增加数据同步方法或同步代码块:
在本程序中,生产者与消费者代表的都是线程对象,所以同步操作只能在Message类中,可以将set与get方法设置为单独的同步方法。
class Message {
private String title ; // 保存信息的标题
private String content ; // 保存信息的内容
public synchronized void set(String title, String content) {
this.title = title;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.content = content;
}
public synchronized String get() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.title + " --> " + this.content;
}
// setter、getter略
}
class Producer implements Runnable { // 定义生产者
private Message msg = null ;
public Producer(Message msg) {
this.msg = msg ;
}
@Override
public void run() {
for (int x = 0; x < 50; x++) { // 生产50次数据
if (x % 2 == 0) {
this.msg.set("xbhog","22") ; // 设置属性
} else {
this.msg.set("xbhog","www.cnblog.cn/xbhog") ; // 设置属性
}
}
}
}
class Consumer implements Runnable { // 定义消费者
private Message msg = null ;
public Consumer (Message msg) {
this.msg = msg ;
}
@Override
public void run() {
for (int x = 0; x < 50; x++) { // 取走50次数据
System.out.println(this.msg.get()); // 取得属性
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Message msg = new Message() ; // 定义Message对象,用于保存和取出数据
new Thread(new Producer(msg)).start() ; // 启动生产者线程
new Thread(new Consumer(msg)).start() ; // 取得消费者线程
}
}
Object线程等待与唤醒机制:
线程的等待与唤醒只能依靠Object来完成,如果想要让生产者与消费者一个一个拿,一个一个取,那么需要加入标志位来确定线程的当前状态;
由图所示:
当生产者线程与消费者线程进入时,判断当前的标志位是否为true,
true:表示生产者可以生产资源,但是消费者不能取走资源
false:表示生产者不能生产资源,但是消费者需要取走资源
class Message {
private String title ;
private String content ;
private boolean flag = true; // 表示生产或消费的形式
// flag = true:允许生产,但是不允许消费
// flag = false:允许消费,不允许生产
public synchronized void set(String title,String content) {
if (this.flag == false) { // 无法进行生产,等待被消费
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.title = title ;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.content = content ;
this.flag = false ; // 已经生产过了
super.notify(); // 唤醒等待的线程
}
public synchronized String get() {
if (this.flag == true) { // 还未生产,需要等待
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
return this.title + " - " + this.content ;
} finally { // 不管如何都要执行
this.flag = true ; // 继续生产
super.notify(); // 唤醒等待线程
}
}
}
在本程序中追加一个数据产生与消费的控制逻辑成员属性,通过此程序的值控制实现线程的等待与唤醒处理操作,从而解决线程重复操作的问题。
Java多线程编程(同步、死锁、生产消费者问题)的更多相关文章
- Java 多线程 -- 协作模型:生产消费者实现方式一:管程法
多线程通过管程法实现生产消费者模式需要借助中间容器作为换从区,还包括生产者.消费者.下面以蒸馒头为列,写一个demo. 中间容器: 为了防止数据错乱,还需要给生产和消费方法加锁 并且生产者在容器写满的 ...
- Java 多线程 -- 协作模型:生产消费者实现方式二:信号灯法
使用信号灯法实现生产消费者模式需要借助标志位. 下面以演员表演,观众观看电视为列,写一个demo 同一资源 电视: //同一资源 电视 class Tv { String voice; // 信号灯 ...
- Java多线程编程(4)--线程同步机制
一.锁 1.锁的概念 线程安全问题的产生是因为多个线程并发访问共享数据造成的,如果能将多个线程对共享数据的并发访问改为串行访问,即一个共享数据同一时刻只能被一个线程访问,就可以避免线程安全问题.锁 ...
- Java多线程编程——进阶篇二
一.线程的交互 a.线程交互的基础知识 线程交互知识点需要从java.lang.Object的类的三个方法来学习: void notify() 唤醒在此对象监视器上等待的单个 ...
- Java多线程编程详解
转自:http://programming.iteye.com/blog/158568 线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Ja ...
- Java多线程编程核心技术
Java多线程编程核心技术 这本书有利于对Java多线程API的理解,但不容易从中总结规律. JDK文档 1. Thread类 部分源码: public class Thread implements ...
- Java多线程编程核心技术(三)多线程通信
线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体.线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时 ...
- 《Java多线程编程核心技术》知识梳理
<Java多线程编程核心技术> @author ergwang https://www.cnblogs.com/ergwang/ 文章末尾附pdf和png下载链接 第1章 Java多线程技 ...
- Java多线程编程总结(精华)
Java多线程编程总结 2007-05-17 11:21:59 标签:多线程 java 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http ...
随机推荐
- 1094 The Largest Generation ——PAT甲级真题
1094 The Largest Generation A family hierarchy is usually presented by a pedigree tree where all the ...
- 创建时间和更新时间两个选一个的情况和select case when ... then ... else ... end from 表 的使用
1.查询时间,如果更新时间update_time为空就查创建时间create_time,否则查更新时间update_time select update_time,create_time, case ...
- JS判断对象是否包含某个属性
1.使用hasOwnProperty()判断 hasOwnProperty方法的参数就是要判断的属性名称,当对象的属性存在时返回true,否则返回false. var obj = { name:'ja ...
- idea更改包名无法加载主类解决
把工程下面的.idea目录下的workspace.xml里面的路径改成你最新的路径即可. <option name="SPRING_BOOT_MAIN_CLASS" valu ...
- Pycharm模块导入失败,带有红色波浪线。
在Pycharm中打开一个python开源工程,结果在导入库的部分一堆红色波浪线显示错误,并且按住Ctrl + 鼠标左击无法跳转到指定类或方法,如下图所示. 解决方法: (1)首先忽略掉这些报错,先运 ...
- HDOJ-1069(动态规划+排序+嵌套矩形问题)
Monkey and Banana HDOJ-1069 这里实际是嵌套矩形问题的变式,也就是求不固定起点的最长路径 动态转移方程为:dp[i]=max(dp[j]+block[i].h|(i,j)∈m ...
- ElasticSearch(ES)使用Nested结构存储KV及聚合查询
自建博客地址:https://www.bytelife.net,欢迎访问! 本文为博客同步发表文章,为了更好的阅读体验,建议您移步至我的博客 本文作者: Jeffrey 本文链接: https://w ...
- CTF-杂项笔记
01 赛题解答 (1)目标:了解modbus协议 (2)解题: 密文:666C61677B4533334237464438413342383431434139363939454 ...
- CMDB项目要点之技术点(面试题)
1.单例模式 日志对象用单例模式 django admin中注册类是,用到单例模式 为什么要用单例模式 同一个对象操作 维护全局变量 + 对全局变量做一些操作 # __new__ import thr ...
- android分析之消息处理
前序:每个APP对应一个进程,该进程内有一个ActivityThread的线程,称为主线程(即UI主线程),此外,还有其他线程,这个再论. android的消息系统分析. 每个Thread只对应一个L ...