在刚刚学线程的时候我们经常会碰到这么一个问题:模拟火车站售票窗口售票。代码如下:
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 ...
随机推荐
-
Javascript之confirm的用法
confirm函数 confirm函数用于提供确认功能,它首先显示给定的message参数所包含的信息,并提供两个可选择的回答“ok”和“cancel”,然后等待用户选择其中的一个.如果用户选择“ok ...
-
CSDN CODE平台,中国版Github简要使用说明!(多图慎入)
楼主说 以前一直看到别人在用github发布自己的代码,各种牛逼,各种羡慕嫉妒恨.最后终于受不了了,也去注册了一个,注册到没什么难度.然后就没有然后了... 完全看不懂,不知道怎么用. 一次偶然的机会 ...
-
C语言 栈 链式结构 实现
一个C语言链式结构实现的栈 mStack (GCC编译). /** * @brief C语言实现的链式结构类型的栈 * @author wid * @date 2013-10-30 * * @note ...
-
c#中的linq一
c#中的linq 测试数据: using System; using System.Collections.Generic; using System.Linq; using System.Text; ...
-
【Asp.Net WebFrom】分页
Asp.Net WebForm 分页 一. 前言 Asp.Net WebForm 内置的DataPager让人十分蛋疼 本文使用的分页控件是第三方分页控件 AspNetPager,下载地址: 链接: ...
-
《Linux下sed命令的使用》
grep -v 关键字 文件 文件中的关键字给过滤掉 grep -v “^关键字” 文件 以关键字开头的给过滤掉 sed -e ‘/关键字/d’文件 输出时把关键字给删除掉 以/etc ...
-
基于STM32的USB枚举过程学习笔记
源:基于STM32的USB枚举过程学习笔记 基于STM32的USB枚举过程学习笔记(一) 基于STM32的USB枚举过程学习笔记(二) 基于STM32的USB枚举过程学习笔记(三) 基于STM32的U ...
-
对比Tornado和Twisted两种异步Python框架
做Python的人,一定知道两个性能优秀的异步网络框架:tornado,和twisted. 那么,这两个著名的框架,又有什么异同呢?tornado和twisted,我都用在几个游戏项目中,做过后端,觉 ...
-
CentOS设置服务开机启动的两种方法
一.通过服务的方式设置自启动 1. 在/etc/init.d 下建立相关程序的启动脚本 2. chkconfig --add mysqld(添加服务到chkconfig列表中) chkconfig ...
-
Unity3D 物体移动方法总结
1. 简介 在Unity3D中,有多种方式可以改变物体的坐标,实现移动的目的,其本质是每帧修改物体的position. 2. 通过Transform组件移动物体 Transform 组件用于描述物体在 ...