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

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

作者:尹正杰

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

一.引发线程安全问题

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

1>.售票案例

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

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note;8

9 class Tickets implementsRunnable{10 //定义出售的票源

11 private int ticket = 20;12 @Override13 public voidrun() {14 while(true) {15 //对于票数大于0才可以出售

16 if( ticket > 0) {17 try{18 Thread.sleep(10);19 System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);20 } catch(InterruptedException e) {21 e.printStackTrace();22 }23 }24 }25 }26 }27

28

29 public classThreadDemo {30 public static voidmain(String[] args) {31 //创建Runnable接口实现了对象

32 Tickets t = newTickets();33 //创建三个Thread类对象,传递Runnable接口实现类

34 Thread t1 = new Thread(t,"窗口1");35 Thread t2 = new Thread(t,"窗口2");36 Thread t3 = new Thread(t,"窗口3");37 t1.start();38 t2.start();39 t3.start();40

41 }42 }43

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

30e55b9a3dc976c9aa63d8bccd779749.png

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

04d60ff0e070e400b55f322d6f1400bc.png

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

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

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note;8

9 class Tickets implementsRunnable{10 //定义出售的票源

11 private int ticket = 20;12

13 private Object obj = newObject();14 @Override15 public voidrun() {16 while(true) {17 //线程共享数据,保证安全,可以把一段代码变成一个原子性操作,也就是说当某个线程在执行该操作时,其它的线程进不来!

18 synchronized(obj) {19 //对于票数大于0才可以出售

20 if( ticket > 0) {21 try{22 Thread.sleep(20);23 System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);24 } catch(InterruptedException e) {25 e.printStackTrace();26 }27 }28 }29 }30 }31 }32

33

34 public classThreadDemo {35 public static voidmain(String[] args) {36 //创建Runnable接口实现了对象

37 Tickets t = newTickets();38 //创建三个Thread类对象,传递Runnable接口实现类

39 Thread t1 = new Thread(t,"窗口1");40 Thread t2 = new Thread(t,"窗口2");41 Thread t3 = new Thread(t,"窗口3");42 t1.start();43 t2.start();44 t3.start();45

46 }47 }

执行结果如下:

5998030fc0c09270d6c28d17373177d0.png

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

746e5d7bbe6e99b7302ea8a462ff7d14.png

二.同步方法

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6 packagecn.org.yinzhengjie.note;7

8 class Tickets implementsRunnable{9 //定义出售的票源

10 private int ticket = 20;11 @Override12 public voidrun() {13 while(true) {14 payTicket();15 }16 }17 //同步方法需要在方法上面用关键字synchronized声明,同步方法中也有对象锁,该锁就是本类对象引用(this).18 //如果同步方法有静态修饰,那么成员变量也需要用静态修饰,静态同步方法中的锁对象是:“类名.class”,即"Tickets.class"。

19 public synchronized voidpayTicket() {20 //对于票数大于0才可以出售

21 if( ticket > 0) {22 try{23 Thread.sleep(20);24 System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);25 } catch(InterruptedException e) {26 e.printStackTrace();27 }28 }29 }30 }31

32 public classThreadDemo {33 public static voidmain(String[] args) {34 //创建Runnable接口实现了对象

35 Tickets t = newTickets();36 //创建三个Thread类对象,传递Runnable接口实现类

37 Thread t1 = new Thread(t,"窗口1");38 Thread t2 = new Thread(t,"窗口2");39 Thread t3 = new Thread(t,"窗口3");40 t1.start();41 t2.start();42 t3.start();43 }44 }

代码执行结果如下:

7d2b5c847c613959e41203f33136eaa8.png

三.Lock接口改进售票案例

d289757ac9d0faee9b798f95bf7c48b2.png

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

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6 packagecn.org.yinzhengjie.note;7

8 importjava.util.concurrent.locks.Lock;9 importjava.util.concurrent.locks.ReentrantLock;10

11 class Tickets implementsRunnable{12 //定义出售的票源

13 private int ticket = 20;14 //在类的成员位置创建lock获取锁

15 private Lock lock = newReentrantLock();16

17 @Override18 public voidrun() {19 while(true) {20 payTicket();21 }22 }23 //用lock锁也可以进行锁操作,可以和synchronzied实现同样的效果,并且可以清晰的在程序中看出在哪个位置上锁和解锁。

24 public voidpayTicket() {25 //调用Lock接口方法获取锁

26 lock.lock();27 //对于票数大于0才可以出售

28 if( ticket > 0) {29 try{30 Thread.sleep(50);31 System.out.printf(Thread.currentThread().getName()+"出售第[%d]张票\n",ticket--);32 } catch(InterruptedException e) {33 e.printStackTrace();34 }finally{35 //释放锁,调用Lock接口方法unlock

36 lock.unlock();37 }38 }39 }40 }41

42 public classThreadDemo {43 public static voidmain(String[] args) {44 //创建Runnable接口实现了对象

45 Tickets t = newTickets();46 //创建三个Thread类对象,传递Runnable接口实现类

47 Thread t1 = new Thread(t,"窗口1");48 Thread t2 = new Thread(t,"窗口2");49 Thread t3 = new Thread(t,"窗口3");50 t1.start();51 t2.start();52 t3.start();53 }54 }

四.线程的死锁问题

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

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

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note;8

9 classMyLock{10 //构造方法私有化

11 privateMyLock() {}12 //由于构造方法私有化,让用户只能通过"类名.静态方法"的方式调用(此处我们不考虑反射的情况!)

13 public final static MyLock lockA = newMyLock();14 public final static MyLock lockB = newMyLock();15

16 }17

18 class Deadlock implementsRunnable{19 private int i = 0;20 @Override21 public voidrun() {22 while(true) {23 if( i % 2 == 0) {24 //先进去A同步,在进入B同步

25 synchronized(MyLock.lockA) {26 System.out.printf("【%s】已经拿到了枪,准备去拿子弹!\n",Thread.currentThread().getName());27 synchronized(MyLock.lockB) {28 System.out.printf("【%s】成功拿到子弹!\n",Thread.currentThread().getName());29 }30 }31 }else{32 //先进入B同步,在进入A同步

33 synchronized(MyLock.lockB) {34 System.out.printf("【%s】已经拿到子弹,准备去拿枪!\n",Thread.currentThread().getName());35 synchronized(MyLock.lockA) {36 System.out.printf("【%s】成功拿到枪!\n",Thread.currentThread().getName());37 }38 }39 }40 i++;41 }42 }43 }44

45

46

47 public classDeadLockDemo {48 public static voidmain(String[] args) {49

50 Deadlock dead = newDeadlock();51

52 Thread t1 = new Thread(dead,"成龙");53 Thread t2 = new Thread(dead,"李连杰");54

55 t1.start();56 t2.start();57 }58 }

以上代码执行结果如下:

5e79d8da0cd3ef79a1f06d496ad9b037.png

五.线程等待案例展示

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note1;8

9 public classResource {10 publicString name;11 publicString sex;12 //定义一个标志位,让其默认值为false

13 public boolean flag = false;14 }

Resource.java 文件内容

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note1;8

9 //定义一个输入的线程,对资源对象Resource中成员变量赋值

10 public class Input implementsRunnable {11 //让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作

12 privateResource r ;13 publicInput(Resource r) {14 this.r =r;15 }16

17 public voidrun() {18 while(true) {19 int i = 0;20 while(true) {21 synchronized(r) {22 //表示是true时,表示赋值完成,我们可以让线程进入休眠状态

23 if(r.flag) {24 try{25 //让检查进入等待状态,也就是不会执行其下面的代码!

26 r.wait();27 } catch(InterruptedException e) {28 e.printStackTrace();29 }30 }31 //如果标志位的值为false,则说明Resource对象并没有赋值,我们需要做的是赋值操作!

32 if(i % 2 == 0) {33 r.name = "尹正杰";34 r.sex = "男";35 }else{36 r.name = "yinzhengjie";37 r.sex = "man";38 }39 //以上操作完成了赋值,标记改为true!

40 r.flag = true;41 //此时将Output线程唤醒,让对方知道赋值已经完成,可以来取值啦!

42 r.notify();43 }44 i++;45 }46 }47 }48

49 }

Input.java 文件内容

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note1;8

9 //定义输出线程,对资源对象Resource中成员变量,输出值。

10 public class Output implementsRunnable {11 //让用户在调用时手动传入,若传入的对象是同一个,那么多个线程就可以实现对同一个线程进行操作

12 privateResource r ;13 publicOutput(Resource r) {14 this.r =r;15 }16

17 public voidrun() {18 while(true) {19 //注意,在选择锁的时候,若锁不相同,可能存在和我们期望的结果有偏差!

20 synchronized(r) {21 //判断标志位的值是否为false,如果是则说明其是等待状态,我们需要的就是去取值!

22 if(!r.flag) {23 try{24 r.wait();25 } catch(InterruptedException e) {26 e.printStackTrace();27 }28 }29 System.out.println(r.name+"==="+r.sex);30 //标记改为false,

31 r.flag = false;32 //表示赋值完成,唤醒Input线程。

33 r.notify();34 }35 }36 }37

38 }

Output.java 文件内容

1 /*

2 @author :yinzhengjie3 Blog:http://www.cnblogs.com/yinzhengjie/tag/Java%E5%9F%BA%E7%A1%80/

4 EMAIL:y1053419035@qq.com5 */

6

7 packagecn.org.yinzhengjie.note1;8

9 public classThreadDemo {10 public static voidmain(String[] args) {11 Resource r = newResource();12

13 Input in = newInput(r);14 Output out = newOutput(r);15

16 Thread t1 = newThread(in);17 Thread t2 = newThread(out);18

19 t1.start();20 t2.start();21 }22 }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值