Java多线程--线程安全问题的相关研究
在刚刚学线程的时候我们经常会碰到这么一个问题:模拟火车站售票窗口售票。代码如下:
package cn.blogs.com.isole; /* 模拟火车站售票窗口售票,假设有50张余票 */ public class _synchronizeds { public static void main(String[] args) { //创建3个线程对象,分别代表售票的3个窗口 Ticket t1 = new Ticket("窗口1"); Ticket t2 = new Ticket("窗口2"); Ticket t3 = new Ticket("窗口3"); //开启线程,开始卖票 t1.start(); t2.start(); t3.start(); } } //售票 class Ticket extends Thread{ //定义剩余票,静态的 static int num=50; //构造方法为线程重新命名 public Ticket(String name){ super(name); } //重写run()方法,将自定义线程代码写入 @Override public void run() { while(true){ if(num>0){ num--; System.out.println(Thread.currentThread().getName()+"卖出了"+num+"号票"); }else{ System.out.println("票售罄了"); break; } } } }
多次启动线程测试发现问题如下图:
这个就是线程的安全问题了!
在什么情况下才可能出现线程安全问题:
1.必须存在多个线程对象,而且线程之间共享着一个资源
2.必须存在多个语句操作了共享资源,如下方if内的2句代码
线程安全的解决方案:
思路:在共享资源里设定在一个时间片只允许一个线程完全操作完毕之后才允许其他线程操作
sun提供了线程同步机制让我们解决这类线程安全问题的:
方式1.同步代码块:
同步代码块的格式:
synchronized(锁对象){
需要被同步的代码...
}
要注意的事项:
1.任意的一个对象都可以作为锁对象(凡是对象 内部都维护了一个状态的,例如state = 1 开 0关
java的同步机制就是使用了对象中的状态作为了锁的标识)
2.在同步代码块中调用了sleep方法并不会释放锁对象的,而是暂停执行一段时间再继续执行
3.只有真正存在线程安全问题的时候才使用同步代码块,否则会降低效率 因为每次都得判断锁的状态是开还是关
4.多线程操作的锁对象必须是唯一共享的,否则无效(static)因为不同的话只能锁自己的锁
方式2.同步函数:就是使用synchronized修饰函数
同步函数要注意的事项:
1.如果是一个非静态的同步函数的锁 对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)
2.同步函数的锁对象是固定的,不能被指定
针对以上2种方案推荐使用:同步代码块
原因.同步代码块的锁对象可以由我们随意指定,方便控制
以下代码即使用同步代码块解决上述的问题:
package cn.blogs.com.isole; /* 模拟火车站售票窗口售票,假设有50张余票 */ public class _synchronizeds { public static void main(String[] args) { //创建3个线程对象,分别代表售票的3个窗口 Ticket t1 = new Ticket("窗口1"); Ticket t2 = new Ticket("窗口2"); Ticket t3 = new Ticket("窗口3"); //开启线程,开始卖票 t1.start(); t2.start(); t3.start(); } } //售票 class Ticket extends Thread{ //定义剩余票,静态的 static int num=50; //构造方法为线程重新命名 public Ticket(String name){ super(name); } //重写run()方法,将自定义线程代码写入 @Override public void run() { while(true){ synchronized("锁的对象可以是任意的,但是要共享"){//锁开始的位置,当线程执行到这里的时候,锁的状态值变成0代表不可进入 if(num>0){ num--; System.out.println(Thread.currentThread().getName()+"卖出了"+num+"号票"); }else{ System.out.println("票售罄了"); break; } }//锁的结束位置,当线程执行到这里,锁的状态变成1代表可进入 } } }
这个时候执行就不会出现线程安全的相关问题了
线程的通讯机制(模拟生产者与消费者之间的产品关系)代码如下:
class Product{ String name;//名字 double price;//价格 boolean flag = false;//产品生产的标识 } //生产者 class Producer extends Thread{ Product p ; //产品 public Producer(Product p){ this.p = p; } public void run(){ int i = 0; while(true){ synchronized(p) { if(p.flag == false){//如果没有生产 if(i%2==0){ p.name = "苹果"; p.price = 6.5; }else{ p.name = "香蕉"; p.price = 1.1; } System.out.println("生产者生产出了:"+p.name+"价格是:"+p.price); i++; p.flag=true; p.notify();//唤醒消费者去消费 }else{ //生产者生产完毕,等待消费者消费 try { p.wait();//由锁对象调用 } catch (InterruptedException e) { e.printStackTrace(); } } }//锁结束 }//循环结束 } } //消费者 class Customer extends Thread{ Product p; public Customer(Product p){ this.p = p; } public void run(){ while(true){ synchronized(p){ if(p.flag == true){//如果有生产完毕的产品 System.out.println("消费者消费了:"+p.name+"价格:"+p.price); p.flag = false; p.notify();//唤醒生产者生产 }else{ //产品还没有生产,应该等待生产者先生产 try { p.wait();//消费者等待 } catch (InterruptedException e) { e.printStackTrace(); } } }//锁结束 } } } public class _8Demo_Thread4 { public static void main(String[] args) { Product p = new Product();//产品 Producer p1 = new Producer(p);//生产者 Customer p2 = new Customer(p);//消费者 p1.start(); p2.start(); } }
线程的通讯:一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务
生产者-消费者 生产者的产品给消费者使用 产品是共享的
问题1:出现了线程安全问题(价格错乱了)
加synchronized锁住 且锁的对象使用p
问题2:生产者生产完产品之后才能被消费者使用
线程通信的2个方法
wait(); //等待 如果执行则该线程进入等待的状态 必须要其他线程调用notify()才能唤醒
notify(); 唤醒等待的线程
wait()与notify()方法要注意的事项:
1.这2个方法是属于Object对象的 原因:锁的对象是我们自己定义的,而不是Thread定义的,所以调用这两个方法的对象可能是任意的 而任意的类都是Object类的子类
2.wait方法与notify方法必须在同步代码块或者是同步函数中才能使用
3.wait方法与notify必须要由锁对象调用
wait:一个线程如果执行了wait方法,那么该线程就会进去一个以"锁对象"为标识符的线程池中等待 一旦执行wait会释放锁
notify();如果一个线程执行notify方法,那么就会唤醒以锁对象为标识符的线程池中等待线程中其中一个
notifyAll(); //唤醒线程池中所有等待的线程
附:死锁问题
java的同步机制解决了线程安全问题,但是也同时引发死锁的现象
死锁现象存在的根本原因:
1.存在2个或2个以上的线程
2.存在的共享资源个数大于等于2个
死锁现象的解决方案:没有方案,只能尽量的避免发生而已...
附:守护线程
1.守护线程:就是main同生共死,随着main的结束而结束,而普通线程是在任务代码执行结束才停止。
2.用户线程:Java虚拟机在它所有非守护线程已经离开后自动离开。守护线程则是用来服务用户线程的,如果没有其他用户线程在运行,那么就没有可服务对象,也就没有理由继续下去。
例如:我们所熟悉的Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
Java多线程--线程安全问题的相关研究的更多相关文章
- Java多线程——线程安全问题
一.什么情况下会产生线程安全问题? 同时满足以下两个条件时: 1,多个线程在操作共享的数据.2,操作共享数据的线程代码有多条. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导 ...
- Java多线程--线程及相关的Java API
Java多线程--线程及相关的Java API 线程与进程 进程是线程的容器,程序是指令.数据的组织形式,进程是程序的实体. 一个进程中可以容纳若干个线程,线程是轻量级的进程,是程序执行的最小单位.我 ...
- Java多线程——线程之间的协作
Java多线程——线程之间的协作 摘要:本文主要学习多线程之间是如何协作的,以及如何使用wait()方法与notify()/notifyAll()方法. 部分内容来自以下博客: https://www ...
- Java多线程——线程之间的同步
Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...
- java 多线程—— 线程让步
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- java 多线程—— 线程等待与唤醒
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java基础-线程安全问题汇总
Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...
- Java多线程-线程的同步(同步方法)
线程的同步是保证多线程安全访问竞争资源的一种手段.线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问题,当然,这些问题没有很明确的答案,但有些 ...
- Java多线程——线程的优先级和生命周期
Java多线程——线程的优先级和生命周期 摘要:本文主要介绍了线程的优先级以及线程有哪些生命周期. 部分内容来自以下博客: https://www.cnblogs.com/sunddenly/p/41 ...
随机推荐
- RemodelanyWhere11.0.2673版本下载
百度云盘链接:http://pan.baidu.com/s/1geL5lez 密码:hisq 原文转载至:http://blog.sun0816.com/13623.html
- CentOS安装JDK-1.7
注:以下所有操作均在CentOS 6.5 x86_64位系统下完成. #准备工作# 准备用rpm下载前,看系统是否已经安装有JDK,如果没有则进入正式安装步骤. # rpm -qa | grep jd ...
- Spring MVC之@RequestParam @RequestBody @RequestHeader 等详解
(转自:http://blog.csdn.net/walkerjong/article/details/7946109#) 引言: 接上一篇文章,对@RequestMapping进行地址映射讲解之后, ...
- (转)深入理解Java的接口和抽象类
原文地址: http://www.cnblogs.com/dolphin0520/p/3811437.html 对于面向对象编程来说,抽象是它的一大特征之一.在Java中,可以通过两种形式来体现OOP ...
- python爬虫学习(9) —— 一些工具和语法
1. Beautiful Soup 在它的官网有这样一段话: You didn't write that awful page. You're just trying to get some data ...
- 嵌入式Linux驱动学习之路(二十六)DM9000C网卡驱动程序
基于DM9000C的原厂代码修改dm9000c的驱动程序. 首先确认内存的基地址 iobase. 确定中断号码. 打开模块的初始化函数定义. 配置内存控制器的相应时序(结合DM9000C.C的手册). ...
- NHibernate之映射文件配置说明
NHibernate之映射文件配置说明 1. hibernate-mapping 这个元素包括以下可选的属性.schema属性,指明了这个映射所引用的表所在的schema名称.假若指定了这个属性, 表 ...
- [原]使用node-mapnik生成openstreetmap-carto风格的瓦片
上回说到如何在CentOS上部署node-mapnik,本想着接下来学习如何使用node-mapnik生成openstreetmap的瓦片图,没想到在接下来的近40天的时间里忙成了狗!好不容易等到元旦 ...
- EF里一对一、一对多、多对多关系的配置和级联删除
本章节开始了解EF的各种关系.如果你对EF里实体间的各种关系还不是很熟悉,可以看看我的思路,能帮你更快的理解. I.实体间一对一的关系 添加一个PersonPhoto类,表示用户照片类 /// < ...
- 在WPF程序中打开网页:使用代理服务器并可进行JS交互
本项目环境:使用VS2010(C#)编写的WPF程序,通过CefSharp在程序的窗体中打开网页.需要能够实现网页后台JS代码中调用的方法,从网页接收数据,并能返回数据给网页.运行程序的电脑不允许上网 ...