Java基础-线程操作共享数据的安全问题

                                        作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.引发线程安全问题

  如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

1>.售票案例

  假设某人一次性买了20张关于周杰伦的演唱会,原计划是请全部门去看演唱会的,但是由于老板的临时任务来袭,被迫需要把这20张票放在交给三个人转卖出去,请你模拟这个买票的过程。我们可以用代码来实现一下:

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note; class Tickets implements Runnable{
//定义出售的票源
private int ticket = 20;
@Override
public void run() {
while(true) {
//对于票数大于0才可以出售
if( ticket > 0 ) {
try {
Thread.sleep(10);
System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
} public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现了对象
Tickets t = new Tickets();
//创建三个Thread类对象,传递Runnable接口实现类
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start(); }
}

  运行我们写的程序之后,发现了两个问题,即(重复买票还有出现负数票的情况),如下:

2>.分享出现线程安全问题的原因

3>.同步代码块解决线程安全问题

  我们解决上面案例的思路就是:当一个线程进入数据操作的时候,无论是否休眠,其它线程只能等待。这就需要引入Java的一个关键字,同步锁的概念:

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note; class Tickets implements Runnable{
//定义出售的票源
private int ticket = 20; private Object obj = new Object();
@Override
public void run() {
while(true) {
//线程共享数据,保证安全,可以把一段代码变成一个原子性操作,也就是说当某个线程在执行该操作时,其它的线程进不来!
synchronized (obj) {
//对于票数大于0才可以出售
if( ticket > 0 ) {
try {
Thread.sleep(20);
System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
} public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现了对象
Tickets t = new Tickets();
//创建三个Thread类对象,传递Runnable接口实现类
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start(); }
}

  执行结果如下:

4>.同步代码块的执行原理

二.同步方法

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.note; class Tickets implements Runnable{
//定义出售的票源
private int ticket = 20;
@Override
public void run() {
while(true) {
payTicket();
}
}
//同步方法需要在方法上面用关键字synchronized声明,同步方法中也有对象锁,该锁就是本类对象引用(this).
//如果同步方法有静态修饰,那么成员变量也需要用静态修饰,静态同步方法中的锁对象是:“类名.class”,即"Tickets.class"。
public synchronized void payTicket() {
//对于票数大于0才可以出售
if( ticket > 0 ) {
try {
Thread.sleep(20);
System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现了对象
Tickets t = new Tickets();
//创建三个Thread类对象,传递Runnable接口实现类
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}

  代码执行结果如下:

三.Lock接口改进售票案例

  我们用synchronized关键字实现同步锁方法,我们也知道非静态默认所就是本类对象引用(this).如果同步方法有静态修饰,那么成员变量也需要用静态修饰,静态同步方法中的锁对象是:“类名.class”。但是我们很难实现在程序看出来它是在哪上锁,又是在哪解锁。这个时候我们Java开发者在JDK1.5版本后退出了Lock接口,该接口就可以清晰的表示程序应该在哪个位置上上锁,又是在哪个位置上解锁。我们用实现Lock接口的子类ReentrantLock来进行模拟,代码如下:

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/
package cn.org.yinzhengjie.note; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; class Tickets implements Runnable{
//定义出售的票源
private int ticket = 20;
//在类的成员位置创建lock获取锁
private Lock lock = new ReentrantLock(); @Override
public void run() {
while(true) {
payTicket();
}
}
//用lock锁也可以进行锁操作,可以和synchronzied实现同样的效果,并且可以清晰的在程序中看出在哪个位置上锁和解锁。
public void payTicket() {
//调用Lock接口方法获取锁
lock.lock();
//对于票数大于0才可以出售
if( ticket > 0 ) {
try {
Thread.sleep(50);
System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放锁,调用Lock接口方法unlock
lock.unlock();
}
}
}
} public class ThreadDemo {
public static void main(String[] args) {
//创建Runnable接口实现了对象
Tickets t = new Tickets();
//创建三个Thread类对象,传递Runnable接口实现类
Thread t1 = new Thread(t,"窗口1");
Thread t2 = new Thread(t,"窗口2");
Thread t3 = new Thread(t,"窗口3");
t1.start();
t2.start();
t3.start();
}
}

四.线程的死锁问题

  线程的死锁前提是:必须是多线程出现同步嵌套。多线程场景下,多个线程互相等待对方释放锁的现象。

  在实际生活中,死锁就好比两个小孩子打架,两个人彼此扯住对方的头发,谁也不撒手,都等着对方先松手为止。再比如我们看电影,尤其是成龙的动作片,经常出现两个搭档,电影中两个搭档去执行任务,一个人拿到了 抢,成龙达到了子弹,然后两个人分别跑到了走廊的两侧,发现彼此都达到了对方想要的东西,他们无法完成开枪的操作。在代码里,就变现为同步的嵌套,即拿着一个锁的同时,想要获取另外一个锁,此时另外一个锁拿走了当前线程需要锁并且等待着当前锁释放锁,即两个线程彼此等待对方释放锁的情况。我们可以用代码来模仿一下

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note; class MyLock{
//构造方法私有化
private MyLock() {}
//由于构造方法私有化,让用户只能通过"类名.静态方法"的方式调用(此处我们不考虑反射的情况!)
public final static MyLock lockA = new MyLock();
public final static MyLock lockB = new MyLock(); } class Deadlock implements Runnable{
private int i = 0;
@Override
public void run() {
while(true) {
if( i % 2 == 0 ) {
//先进去A同步,在进入B同步
synchronized(MyLock.lockA) {
System.out.printf("【%s】已经拿到了枪,准备去拿子弹!\n",Thread.currentThread().getName());
synchronized(MyLock.lockB) {
System.out.printf("【%s】成功拿到子弹!\n",Thread.currentThread().getName());
}
}
}else {
//先进入B同步,在进入A同步
synchronized(MyLock.lockB) {
System.out.printf("【%s】已经拿到子弹,准备去拿枪!\n",Thread.currentThread().getName());
synchronized(MyLock.lockA) {
System.out.printf("【%s】成功拿到枪!\n",Thread.currentThread().getName());
}
}
}
i++;
}
}
} public class DeadLockDemo {
public static void main(String[] args) { Deadlock dead = new Deadlock(); Thread t1 = new Thread(dead,"成龙");
Thread t2 = new Thread(dead,"李连杰"); t1.start();
t2.start();
}
}

  以上代码执行结果如下:

五.线程等待案例展示

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note1; public class Resource {
public String name;
public String sex;
//定义一个标志位,让其默认值为false
public boolean flag = false;
}

Resource.java 文件内容

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note1; //定义一个输入的线程,对资源对象Resource中成员变量赋值
public class Input implements Runnable {
//让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作
private Resource r ;
public Input(Resource r) {
this.r = r;
} public void run() {
while(true) {
int i = 0;
while(true) {
synchronized (r) {
//表示是true时,表示赋值完成,我们可以让线程进入休眠状态
if(r.flag) {
try {
//让检查进入等待状态,也就是不会执行其下面的代码!
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果标志位的值为false,则说明Resource对象并没有赋值,我们需要做的是赋值操作!
if(i % 2 == 0) {
r.name = "尹正杰";
r.sex = "男";
}else {
r.name = "yinzhengjie";
r.sex = "man";
}
//以上操作完成了赋值,标记改为true!
r.flag = true;
//此时将Output线程唤醒,让对方知道赋值已经完成,可以来取值啦!
r.notify();
}
i++;
}
}
} }

Input.java 文件内容

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note1; //定义输出线程,对资源对象Resource中成员变量,输出值。
public class Output implements Runnable {
//让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作
private Resource r ;
public Output(Resource r) {
this.r = r;
} public void run() {
while(true) {
//注意,在选择锁的时候,若锁不相同,可能存在和我们期望的结果有偏差!
synchronized (r) {
//判断标志位的值是否为false,如果是则说明其是等待状态,我们需要的就是去取值!
if(!r.flag) {
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.name+"==="+r.sex);
//标记改为false,
r.flag = false;
//表示赋值完成,唤醒Input线程。
r.notify();
}
}
} }

Output.java 文件内容

 /*
@author :yinzhengjie
Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/
EMAIL:y1053419035@qq.com
*/ package cn.org.yinzhengjie.note1; public class ThreadDemo {
public static void main(String[] args) {
Resource r = new Resource(); Input in = new Input(r);
Output out = new Output(r); Thread t1 = new Thread(in);
Thread t2 = new Thread(out); t1.start();
t2.start();
}
}

Java基础-线程操作共享数据的安全问题的更多相关文章

  1. java基础多线程之共享数据

    java基础巩固笔记5-多线程之共享数据 线程范围内共享数据 ThreadLocal类 多线程访问共享数据 几种方式 本文主要总结线程共享数据的相关知识,主要包括两方面:一是某个线程内如何共享数据,保 ...

  2. Java并发基础09. 多个线程间共享数据问题

    先看一个多线程间共享数据的问题: 设计四个线程,其中两个线程每次对data增加1,另外两个线程每次对data减少1. 从问题来看,很明显涉及到了线程间通数据的共享,四个线程共享一个 data,共同操作 ...

  3. JAVA笔记14__多线程共享数据(同步)/ 线程死锁 / 生产者与消费者应用案例 / 线程池

    /** * 多线程共享数据 * 线程同步:多个线程在同一个时间段只能有一个线程执行其指定代码,其他线程要等待此线程完成之后才可以继续执行. * 多线程共享数据的安全问题,使用同步解决. * 线程同步两 ...

  4. Java基础-线程安全问题汇总

    Java基础-线程安全问题汇总 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.内存泄漏和内存溢出(out of memory)的区别 1>.什么是内存溢出 答:内存溢出指 ...

  5. JAVA 并发编程-多个线程之间共享数据

    原文地址:http://blog.csdn.net/hejingyuan6/article/details/47053409# 多线程共享数据的方式: 1,如果每个线程执行的代码相同,可以使用同一个R ...

  6. JAVA多线程提高四:多个线程之间共享数据的方式

    多个线程访问共享对象和数据的方式 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 如果每个线程执行的代码不同,这 ...

  7. JAVA 并发编程-多个线程之间共享数据(六)

    多线程共享数据的方式: 1.假设每一个线程运行的代码同样.能够使用同一个Runnable对象,这个Runnable对象中有那个共享数据,比如,卖票系统就能够这么做. 2,假设每一个线程运行的代码不同. ...

  8. 【转】JAVA 并发编程-多个线程之间共享数据

    原文地址:http://blog.csdn.net/hejingyuan6/article/details/47053409# 多线程共享数据的方式: 1,如果每个线程执行的代码相同,可以使用同一个R ...

  9. JAVA多线程学习八-多个线程之间共享数据的方式

    多个线程访问共享对象和数据的方式 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 如果每个线程执行的代码不同,这 ...

随机推荐

  1. java实验三 敏捷开发与XP实践

    一.实验内容 (一)敏捷开发与XP 软件开发流程的目的是为了提高软件开发.运营.维护的效率,并提高软件的质量.用户满意度.可靠性和软件的可维护性. 光有各种流程的思想是不够的,我们还要有一系列的工具来 ...

  2. Java文件写入时是否覆盖

    这个是和服务器读数据结合着来的,是向服务器文件写数据,这就碰到了是否覆盖以前写的数据的问题,看FileWriter();的参数后面的参数名叫append,用词典查是附加的意思,灵机一动,改成false ...

  3. “吃神么,买神么”的第二个Sprint计划(计划过程内容)

     “吃神么,买神么”项目Sprint计划 ——6.1(第二天)立会内容与进度 团队组员各自任务: 陈键.吴舒婷:继续完善前台设局与布局 林欢雯.冯美欣:开展后台的界面的设计与布局 任务的进度: 陈键. ...

  4. Java 单生产者消费者问题

    package com.cwcec.test; class Resource { private int count = 0; private boolean flag = false; public ...

  5. C/C++ 打印文件名、行号、函数名的方法

    转自:http://zhidao.baidu.com/link?url=JLCaxBAXLJVcx_8jsyJVF92E_bZjo4ONJ5Ab-HGlNBc1dfzcAyFAIygwP1qr18aa ...

  6. 自动创建web.xml

    摘自:http://blog.csdn.net/weiral/article/details/51366485 今天在学习JSP时先创建了一个web项目,后来在用到web.xml文件时,才发现项目创建 ...

  7. yii框架 excel导出

    环境: yii框架 basic版 1.下载 PHPexcel  (我用的是PHPExcel-1.8.1) 2.将下载的文件夹 (PHPExcel-1.8.1)放至 vender下  (路径:basic ...

  8. centos6.7 安装JDK

      1.卸载JDK 查看系统是否已安装JDK.一般的linux都默认使用了开源的openJDK.显示JDK版本信息,已经安装JDK,否则没有安装.命令行: [root@localhost ~]# ja ...

  9. Hibernate 之主键生成策略小总结

    主键生成策略大致分两种: 手工控制策略 自动生成策略[框架自动生成和数据库自动生成] 手工控制策略: assigned:类型是任意的,需要在 save() 到数据库前,编码人员手工设置主键值,也就是调 ...

  10. MySQL的间隙锁

    什么是间隙锁当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁:对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也 ...