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. Ubuntu下开启mysql远程访问

    1. 开启数据库3306端口 首先,使用如下指令查看3306端口是否对外开放. netstat -an | grep 3306 tcp 0 0 127.0.0.1:3306 0.0.0.0:* LIS ...

  2. 《找出1到正整数N中出现1的次数》

    <找出1到正整数N中出现1的次数> 编程思想:依次求出正整数每个位数上出现1的次数,累加即可得到最后想要的结果:而每一位上出现1的个数与和它相邻的其它位数上的数字有关系(以此位置上的数为对 ...

  3. 特别好用的eclipse快捷键

    alt+/ 提示 alt+shift+r重命名 alt+shift+j添加文档注释 Ctrl+shift+y小写 Ctrl+shift+x大写 ctrl+shift+f格式化代码(需要取消输入法的简繁 ...

  4. 《TCP/IP 详解 卷1:协议》第 9 章:广播和本地组播(IGMP 和 MLD)

    我已经懒了,卷一已经是去年年底看完的,但怎么说卷一的坑开了就要填完啊-- 广播和本地组播(IGMP 和 MLD) 引言 有 4 种 IP 地址,单播(unicast).任播(anycast).组播(m ...

  5. 使用JProfiler做性能分析过程

    供自己记录一下,也分享给大家使用JProfiler的过程(感谢教我使用这个工具的大佬),整个博客比较粗糙,希望对大家有帮助 1.首先安装好JProfiler,打开eclipse,右键你所要分析的项目, ...

  6. 项目冲刺Beta第二篇博客

    Beta版本冲刺计划安排 1.当天站立式会议照片: 2.工作分工: 团队成员 分工 张洪滨060  排行榜界面美化 陈敬轩059  注册成功界面美化 黄兴067  登录界面美化 林国梽068  答题界 ...

  7. Alpha版本冲刺(三)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员11:何宇恒 展示组内最 ...

  8. 0428-Scrum团队成立

    ------------------------------3.0------------------------------------------ 一.项目要求 5.Scrum团队成立 5.1 团 ...

  9. [转帖]USB-C和Thunderbolt 3连接线你搞懂了吗?---没搞明白.

    USB-C和Thunderbolt 3连接线你搞懂了吗? 2018年11月25日 07:30 6318 次阅读 稿源:威锋网 3 条评论 按照计算行业的风潮,USB Type-C 将会是下一代主流的接 ...

  10. Java继承,重写方法时改变方法的访问权限

    java中的方法天生具有继承多态特性,这点与C++有很大不同(需要在父类方发上加virtual关键字),但用起来确实方便了许多. 最简单的继承多态 声明一个接口BaseIF,只包含一个方法声明 pub ...