线程安全

假如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. 802.1X的体系结构

  2. 运用NodeJs环境并依赖第三方库,框架等实现网站前后端分离报错问题及处理方法

    运用NodeJs环境并依赖第三方库,框架等实现网站前后端分离报错问题及处理方法 问题一: SyntaxError: missing ) after argument list in .....\vie ...

  3. Codeforces Round #587 C. White Sheet(思维+计算几何)

    传送门 •题意 先给一个白矩阵,再两个黑矩阵 如果两个黑矩阵能把白矩阵包含,则输出NO 否则输出YES •思路 计算几何题还是思维题呢? 想起了上初中高中做几何求面积的题 这个就类似于那样 包含的话分 ...

  4. k8s生产环境部署

    建议配置: 服务器 900G*2 SSD 安装操作系统 CPU 16核心*2 40G网卡*2 RAM 64G*8 操作系统建议:Centos7.4/Centos 7.6 不建议使用CentOS8 分布 ...

  5. 16.MindManager整理交互思路

    点住主题同时按ins键可以插入一个支节点 右键主题选择下方的排列图表 可以选择排列方式 按住主题同时ctr+v就会粘帖成一个子主题 文本也可以复制黏贴 主题内容可以直接选择拖动更改结构 选择主题框上的 ...

  6. 【python测试开发栈】—帮你总结Python os模块高频使用的方法

    Python中的os模块是主要和系统操作相关的模块,在平时的工作中会经常用到,花时间整理了os模块的高频使用方法,同时整理出使用时需要注意的点.归纳来讲,os模块的方法可以分为:目录操作.文件操作.路 ...

  7. Django学习小记1-安装配置

    Django是一个开放源代码的Web应用框架,由Python写成. python 中的web框架有许多例如:Django.Tornado.Flask..而Django相较与其他WEB框架其优势为:大而 ...

  8. 公子奇带你一步一步了解Java8中行为参数化

    说明:因为本公子一直从事监狱软件开发,所以本系列博客的引入也以此为背景.问题做了简化,只是为了来讲解技术点. 一.问题提出 今日在好好的撸着代码,超哥(民警)找来了,让把监狱30岁以上的民警找给他. ...

  9. $Noip2012/Luogu1083$ 借教室

    $Luogu$ $Sol$ 区间整体加减? 差分+树状数组维护前缀和! 那每给一个人借完教室之后都要判断一下现在合不合法?那复杂度比暴力还不如些... 注意到这里的单调性,假设给前$x$个人借完教室之 ...

  10. 1055 集体照 (25 分)C语言

    拍集体照时队形很重要,这里对给定的 N 个人 K 排的队形设计排队规则如下: 每排人数为 N/K(向下取整),多出来的人全部站在最后一排: 后排所有人的个子都不比前排任何人矮: 每排中最高者站中间(中 ...