线程安全

假如Java程序中有多个线程在同时运行,而这些线程可能会同时运行一部分的代码。如果说该Java程序每次运行的结果和单线程的运行结果是一样的,并且其他的变量值也都是和预期的结果是一样的,那么就可以说线程是安全的。

解析什么是线程安全:卖电影票案例

假如有一个电影院上映《葫芦娃大战奥特曼》,售票100张(1-100号),分三种情况卖票:

情况1

该电影院开设一个售票窗口,一个窗口卖一百张票,没有问题。就如同单线程程序不会出现安全问题一样。

情况2

该电影院开设n(n>1)个售票窗口,每个售票窗口售出指定号码的票,也不会出现问题。就如同多线程程序,没有访问共享数据,不会产生问题。

情况3

该电影院开设n(n>1)个售票窗口,每个售票窗口出售的票都是没有规定的(如:所有的窗口都可以出售1号票),这就会出现问题了,假如三个窗口同时在卖同一张票,或有的票已经售出,还有窗口还在出售。就如同多线程程序,访问了共享数据,会产生线程安全问题。

卖100张电影票Java程序实现:出现情况3类似情况

public class MovieTicket01 implements Runnable {

    /**
* 电影票数量
*/
private static int ticketNumber = 100; /**
* 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务
*/
@Override
public void run() {
// 设置此线程要执行的任务
while (ticketNumber > 0) {
// 提高程序安全的概率,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 电影票出售
System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket01.ticketNumber + "号电影票");
ticketNumber --;
}
}
}
// 测试
public class Demo01MovieTicket {
public static void main(String[] args) {
// 创建一个 Runnable接口的实现类对象。
MovieTicket01 movieTicket = new MovieTicket01(); // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。
Thread window0 = new Thread(movieTicket);
Thread window1 = new Thread(movieTicket);
Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认
window0.setName("window0");
window1.setName("window1");
window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法
window0.start();
window1.start();
window2.start();
}
}
控制台部分输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window2)正在出售:99号电影票
售票窗口(window1)正在出售:100号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window2)正在出售:97号电影票
售票窗口(window1)正在出售:97号电影票
售票窗口(window1)正在出售:94号电影票
售票窗口(window2)正在出售:94号电影票
.
.
.
.
.
.
售票窗口(window0)正在出售:7号电影票
售票窗口(window2)正在出售:4号电影票
售票窗口(window0)正在出售:4号电影票
售票窗口(window1)正在出售:2号电影票
售票窗口(window1)正在出售:1号电影票
售票窗口(window2)正在出售:0号电影票
售票窗口(window0)正在出售:-1号电影票

可以看到,三个窗口(线程)同时出售不指定号数的票(访问共享数据),出现了卖票重复,和出售了不存在的票号数(0、-1)

Java程序中为什么会出现这种情况

  1. 在CPU线程的调度分类中,Java使用的是抢占式调度。
  2. 我们开启了三个线程,3个线程一起在抢夺CPU的执行权,谁能抢到谁就可以被执行。
  3. 从输出结果可以知道,刚开始抢夺CPU执行权的时候,线程0(window0窗口)先抢到,再到线程1(window1窗口)抢到,最后线程2(window2窗口)才抢到。
  4. 那么为什么100号票已经在0号窗口出售了,在1号窗口还会出售呢?其实很简单,线程0先抢到CPU执行权,于是有了执行权后,他就开始嚣张了,作为第一个它通过while判断,很自豪的拿着ticketNumber = 100进入while里面开始执行。
  5. 可线程0是万万没有想到,这时候的线程1,在拿到执行权后,在线程0刚刚实现print语句还没开始ticketNumber --的时候,线程1以ticketNumber = 100跑进了while里面。
  6. 线程2很遗憾,在线程0执行了ticketNumber --了才急匆匆的进入while里面,不过它也不甘落后,于是拼命追赶。终于,后来居上,在线程1还没开始print的时候,他就开始print了。于是便出现了控制台的前三条输出的情况。
    售票窗口(window0)正在出售:100号电影票
    售票窗口(window2)正在出售:99号电影票
    售票窗口(window1)正在出售:100号电影票

    window0、window1、window2分别对应线程0、线程1、线程2

  7. 以此类推,直到最后程序执行完毕。

解决情况3的共享数据问题

通过线程的同步,来解决共享数据问题。有三种方式,分别是同步代码块、同步方法、锁机制。

同步代码块

public class MovieTicket02 implements Runnable {

    /**
* 电影票数量
*/
private static int ticketNumber = 100; /**
* 创建锁对象
*/
Object object = new Object(); /**
* 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务
*/
@Override
public void run() {
// 设置此线程要执行的任务
synchronized (object) {
// 把访问了共享数据的代码放到同步代码中
while (ticketNumber > 0) {
// 提高程序安全的概率,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 电影票出售
System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket02.ticketNumber + "号电影票");
ticketNumber --;
}
}
}
}
// 进行测试
public class Demo02MovieTicket {
public static void main(String[] args) {
// 创建一个 Runnable接口的实现类对象。
MovieTicket02 movieTicket = new MovieTicket02(); // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。
Thread window0 = new Thread(movieTicket);
Thread window1 = new Thread(movieTicket);
Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认
window0.setName("window0");
window1.setName("window1");
window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法
window0.start();
window1.start();
window2.start();
}
}
控制台输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window0)正在出售:99号电影票
售票窗口(window0)正在出售:98号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window0)正在出售:96号电影票
.
.
.
.
.
.
售票窗口(window0)正在出售:5号电影票
售票窗口(window0)正在出售:4号电影票
售票窗口(window0)正在出售:3号电影票
售票窗口(window0)正在出售:2号电影票
售票窗口(window0)正在出售:1号电影票

这时候,控制台不再出售不存在的电影号数以及重复的电影号数了。

通过代码块中的锁对象,可以使用任意的对象。但是必须保证多个线程使用的锁对象是同一。锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行。

总结:同步中的线程,没有执行完毕,不会释放锁,同步外的线程,没有锁,进不去同步。

同步方法

public class MovieTicket03 implements Runnable {

    /**
* 电影票数量
*/
private static int ticketNumber = 100; /**
* 创建锁对象
*/
Object object = new Object(); /**
* 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务
*/
@Override
public void run() {
// 设置此线程要执行的任务
ticket();
} public synchronized void ticket() {
// 把访问了共享数据的代码放到同步代码中
while (ticketNumber > 0) {
// 提高程序安全的概率,让程序睡眠10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 电影票出售
System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket03.ticketNumber + "号电影票");
ticketNumber --;
}
}
}

测试与同步代码块一样。

锁机制(Lock锁)

在Java中,Lock锁机制又称为同步锁,加锁public void lock(),释放同步锁public void unlock()。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class MovieTicket05 implements Runnable { /**
* 电影票数量
*/
private static int ticketNumber = 100; Lock reentrantLock = new ReentrantLock(); /**
* 在实现类中重写Runnable接口的run方法,并设置此线程要执行的任务
*/
@Override
public void run() {
// 设置此线程要执行的任务
while (ticketNumber > 0) {
reentrantLock.lock();
// 提高程序安全的概率,让程序睡眠10毫秒
try {
Thread.sleep(10);
// 电影票出售
System.out.println("售票窗口(" + Thread.currentThread().getName() + ")正在出售:" + MovieTicket05.ticketNumber + "号电影票");
ticketNumber --;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
}
// 测试

public class Demo05MovieTicket {
public static void main(String[] args) {
// 创建一个 Runnable接口的实现类对象。
MovieTicket05 movieTicket = new MovieTicket05(); // 创建Thread类对象,构造方法中传递Runnable接口的实现类对象(三个窗口)。
Thread window0 = new Thread(movieTicket);
Thread window1 = new Thread(movieTicket);
Thread window2 = new Thread(movieTicket); // 设置一下窗口名字,方便输出确认
window0.setName("window0");
window1.setName("window1");
window2.setName("window2"); // 调用Threads类中的start方法,开启新的线程执行run方法
window0.start();
window1.start();
window2.start();
}
}
控制台部分输出:
售票窗口(window0)正在出售:100号电影票
售票窗口(window0)正在出售:99号电影票
售票窗口(window0)正在出售:98号电影票
售票窗口(window0)正在出售:97号电影票
售票窗口(window0)正在出售:96号电影票
.
.
.
.
.
.
售票窗口(window1)正在出售:7号电影票
售票窗口(window1)正在出售:6号电影票
售票窗口(window1)正在出售:5号电影票
售票窗口(window1)正在出售:4号电影票
售票窗口(window1)正在出售:3号电影票
售票窗口(window2)正在出售:2号电影票
售票窗口(window1)正在出售:1号电影票

与前两种方式不同,前两种方式,只有线程0能够进入同步机制执行代码,Lock锁机制,三个线程都可以进行执行,通过Lock锁机制来解决共享数据问题。

Java 多线程安全问题就到这里了,如果有什么不足、错误的地方,希望大佬们指正。

Java 多线程安全问题简单切入详细解析的更多相关文章

  1. Java多线程学习(吐血超详细总结)

    Java多线程学习(吐血超详细总结) 林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 写在前面的话:此文只能说是java多线程的一个入门,其实 ...

  2. java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)

    上一节售票系统中我们发现,打印出了错票,0,-1,出现了多线程安全问题.我们分析为什么会发生多线程安全问题? 看下面线程的主要代码: @Override public void run() { // ...

  3. Java多线程——<三>简单的线程执行:Executor

    一.概述 按照<Java多线程——<一><二>>中所讲,我们要使用线程,目前都是显示的声明Thread,并调用其start()方法.多线程并行,明显我们需要声明多个 ...

  4. 【JAVA多线程安全问题解析】

    一.问题的提出 以买票系统为例: class Ticket implements Runnable { public int sum=10; public void run() { while(tru ...

  5. java 多线程安全问题-同步代码块

    /* 多线程的安全问题: while(true) { if(tick>0) { //线程0,1,2,3在余票为1时,都停滞在这里,之后分别获得CPU执行权,打印出0,-1,-2等错票 Syste ...

  6. Java虚拟机堆和栈详细解析,以后面试再也不怕问jvm了!

    堆 Java堆是和Java应用程序关系最密切的内存空间,几乎所有的对象都放在其中,并且Java堆完全是自动化管理,通过垃圾收集机制,垃圾对象会自动清理,不需自己去释放. 根据垃圾回收机制的不同,Jav ...

  7. Java——多线程安全问题

     静态代码块中没有this /* * 线程安全问题产生的原因: * 1.多个线程操作共享的数据 * 2.操作共享数据的线程代码有多条 * * 当一个线程在执行操作共享数据的多条代码过程中,其他线程 ...

  8. Java多线程实现简单的售票程序

    设计一个多线程程序如下:设计一个火车售票模拟程序.假如火车站要有100张火车票要卖出,现在有5个售票点同时售票,用5个线程模拟这5个售票点的售票情况 1.要求打印出每个售票点所卖出的票号 2.各售票点 ...

  9. java多线程安全问题-同步修饰符于函数

    上一篇文章通过卖票使用同步代码块的方法解决安全问题本篇文章首先探讨如何找出这样的安全问题,并提出第二种方式(非静态函数synchronized修饰)解决安全问题 /* 需求: 银行有一个公共账号金库 ...

随机推荐

  1. [转]C#操作word模板插入文字、图片及表格详细步骤

    c#操作word模板插入文字.图片及表格 1.建立word模板文件 person.dot用书签 标示相关字段的填充位置 2.建立web应用程序 加入Microsoft.Office.Interop.W ...

  2. H3C RIP协议概述

  3. linux初始化中的错误处理

    你必须记住一件事, 在注册内核设施时, 注册可能失败. 即便最简单的动作常常需要内存 分配, 分配的内存可能不可用. 因此模块代码必须一直检查返回值, 并且确认要求的操作 实际上已经成功. 如果在你注 ...

  4. poj 3275 "Ranking the Cows"(DFS or Floyd+bitset<>)

    传送门 题意: 农场主 FJ 有 n 头奶牛,现在给你 m 对关系(x,y)表示奶牛x的产奶速率高于奶牛y: FJ 想按照奶牛的产奶速率由高到低排列这些奶牛,但是这 m 对关系可能不能精确确定这 n ...

  5. P1072 城市轰炸

    题目描述 一个大小为N*M的城市遭到了X次轰炸,每次都炸了一个每条边都与边界平行的矩形. 在轰炸后,有Y个关键点,指挥官想知道,它们有没有受到过轰炸,如果有,被炸了几次,最后一次是第几轮. 输入格式 ...

  6. 实体Bean

    持久化实体管理EntityManager EntityManager 在Java persistence规范中,EntityManager是为所有持久化操作提供服务的中枢.Persistence co ...

  7. vue-learning:31 - component - 组件间通信的6种方法

    vue组件间通信的6种方法 父子组件通信 prop / $emit 嵌套组件 $attrs / $liteners 后代组件通信 provide / inject 组件实例引用 $root / $pa ...

  8. LightOJ - 1284 Lights inside 3D Grid (概率计算)

    题面: You are given a 3D grid, which has dimensions X, Y and Z. Each of the X x Y x Z cells contains a ...

  9. onload事件属性,JQ中的load,ready方法

    onload事件属性,JQ中的load,ready方法 前言 页面中的很多操作,需要我们在所需资源下载完成后,才可以进行操作,而资源没有及时下载,我们进行操作的话,是会报错.因此我们需要熟练掌握哪些事 ...

  10. CITRIX VPX配置四层负载

    网络拓扑如下: Step1:开启四层负载特性 在Configuration->Traffic Management->Load Balancing上右键弹出菜单点击enable,如下图: ...