当前位置:   article > 正文

java线程安全问题分析、3种解决办法_substring线程安全

substring线程安全

目录

一 线程不安全

1.1 代码

1.2 解析

解析--卖出不存在的票

解析--卖出相同的票

1.3 解决办法

二 解决方法1:同步代码块

2.1 代码

 2.2 解析

三 解决方法2:同步方法

3.1 代码

 3.2 说明

*四 解决方法3:Lock锁

代码


一 线程不安全

1个窗口卖100张票:单线程不会出现线程安全
3个窗口卖100张票:各卖各的不同票(多线程程序,没有访问共享数据,不会产生问题)
3个窗口卖100张票:可以卖相同的一张票(多线程访问了共享的数据,会产生安全问题)

1.1 代码


3个窗口卖100张票:可以卖相同的一张票(多线程访问了共享的数据,会产生安全问题)

 线程不安全的代码

  1. package threadSafe;
  2. /**
  3. * 实现卖票安全
  4. */
  5. public class MyThreadSafe implements Runnable{
  6. private int ticketAccount = 100;
  7. @Override
  8. public void run() {
  9. while(ticketAccount>0){
  10. //为了提高线程出现安全的问题,睡眠一下,提高概率
  11. try {
  12. Thread.sleep(200);
  13. } catch (Exception e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(Thread.currentThread().getName()+"正在卖第"+(100-ticketAccount+1)+"张票");
  17. ticketAccount--;
  18. }
  19. }
  20. }
  1. package threadSafe;
  2. public class MainMethod {
  3. public static void main(String[] args) {
  4. //创建1个实现类,实现共享票源
  5. MyThreadSafe myThreadSafe = new MyThreadSafe();
  6. //创建3个线程模拟3个窗口卖票
  7. Thread t0 = new Thread(myThreadSafe);
  8. Thread t1 = new Thread(myThreadSafe);
  9. Thread t2 = new Thread(myThreadSafe);
  10. t0.start();
  11. t1.start();
  12. t2.start();
  13. }
  14. }

执行结果

卖出不存在的的票

卖出相同的一张票

1.2 解析


解析--卖出不存在的票


ticketAccount=1 剩最后一张票的时候:

t0线程抢到了CPU的执行权,进入循环体,sleep了
t2线程抢到了CPU的执行权,进入循环体,sleep了
t1线程抢到了CPU的执行权,进入循环体,sleep了

t2醒了抢到cpu的执行权,打印正在卖第100张票,执行ticketAccount--
ticketAccount=0,0不大于0,t2就停止循环了

t1醒了抢到cpu的执行权,在循环体里继续执行,此时ticketAccount=0,打印正在卖第101张票,执行ticketAccount--,
ticketAccount=-1,-1不大于0,t1就停止循环了

t0醒了抢到cpu的执行权,在循环体里继续执行,此时ticketAccount=-1,打印正在卖第102张票,执行ticketAccount--,
ticketAccount=-2,-2不大于0,t0就停止循环了

解析--卖出相同的票


t1 t2 同时执行到了正在卖第44张票,这时候还没执行ticketAccount--

1.3 解决办法


上述的线程安全问题不能使之出现,
我们可以让一个线程在访问共享数据的时候,(不管它有没有失去cpu的执行权),让其他线程只能等待,
等待当前线程执行完了,再允许其他线程继续执行.
保证始终一个线程在卖票就OK了

java引入了线程同步机制,有3种方法完成线程同步操作:
1 同步代码块
2 同步方法
3 锁机制

 

二 解决方法1:同步代码块

格式:
synchronized (锁对象){
  可能会出现线程安全的代码(访问共享数据的代码)
}

2.1 代码

  1. package threadSafe;
  2. /**
  3. * 实现卖票安全
  4. */
  5. public class MyThreadSafe implements Runnable {
  6. private int ticketAccount = 100;
  7. //创建一个锁对象
  8. Object object = new Object();
  9. @Override
  10. public void run() {
  11. //同步代码块
  12. synchronized (object) {
  13. while (ticketAccount > 0) {
  14. //为了提高线程出现安全的问题,睡眠一下,提高概率
  15. try {
  16. Thread.sleep(200);
  17. } catch (Exception e) {
  18. e.printStackTrace();
  19. }
  20. System.out.println(Thread.currentThread().getName() + "正在卖第" + (100 - ticketAccount + 1) + "张票");
  21. ticketAccount--;
  22. }
  23. }
  24. }
  25. }

备注:main方法的代码同之前不变

 结果

 2.2 解析

同步技术的原理:

使用了一个锁对象,叫同步锁(也叫对象锁、对象监视器)
锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

t0抢到了cpu的执行权,执行run()方法,遇到了同步码块,这时,t0会检查同步代码块是否有锁对象,
发现有,就会获取到锁对象,进入到同步中执行.

t1抢到了cpu的执行权,执行run()方法,遇到了同步码块,这时,t1会检查同步代码块是否有锁对象,
发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象.
一直等到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象,进入到同步中执行.

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

但是会出现一个问题:程序频繁地判断锁,获取锁,释放锁,程序的效率会降低.

三 解决方法2:同步方法

格式:

public synchronized 返回值 method(){
    可能会产生线程安全的代码
}

3.1 代码

  1. package threadSafe2;
  2. /**
  3. * 实现卖票安全
  4. */
  5. public class MyThreadSafe implements Runnable {
  6. private int ticketAccount = 50;
  7. @Override
  8. public void run() {
  9. //同步方法
  10. saleTicket();
  11. }
  12. //同步方法
  13. public synchronized void saleTicket() {
  14. while (ticketAccount > 0) {
  15. //为了提高线程出现安全的问题,睡眠一下,提高概率
  16. try {
  17. Thread.sleep(200);
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println(Thread.currentThread().getName() + "正在卖第" + (50 - ticketAccount + 1) + "张票");
  22. ticketAccount--;
  23. }
  24. }
  25. }

备注:main方法的代码同之前不变

结果

 3.2 说明

 同步方法也会把方法内部的代码锁住,只让一个线程执行.
同步方法的锁对象是谁?是new的实现类对象:new MyThreadSafe(),就是this
静态同步方法的锁对象是谁?是本类的class文件对象 (不能是this,this是创建对象之后产生的,静态方法优先于对象)

*四 解决方法3:Lock锁

提供了比 synchronized 更广泛的锁定操作

Lock接口中的方法:
    lock()
    unlock()

使用步骤:
1 在成员位置创建一个ReentrantLock对象
2 在可能会出现安全问题的代码,调用Lock接口中的方法lock():加同步锁
3 在可能会出现安全问题的代码,调用Lock接口中的方法unlock():释放锁

代码

  1. package threadSafe3;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. /**
  5. * 实现卖票安全
  6. */
  7. public class MyThreadSafe implements Runnable {
  8. private int ticketAccount = 80;
  9. //1 在成员位置创建一个ReentrantLock对象
  10. Lock lock = new ReentrantLock();
  11. @Override
  12. public void run() {
  13. //2 在可能会出现安全问题的代码前,调用Lock接口中的方法lock:加同步锁
  14. lock.lock();
  15. while (ticketAccount > 0) {
  16. //为了提高线程出现安全的问题,睡眠一下,提高概率
  17. try {
  18. Thread.sleep(200);
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. System.out.println(Thread.currentThread().getName() + "正在卖第" + (80 - ticketAccount + 1) + "张票");
  23. ticketAccount--;
  24. }
  25. //3 在可能会出现安全问题的代码后,调用Lock接口中的方法unlock():释放锁
  26. lock.unlock();
  27. }
  28. }

备注:main方法的代码同之前不变

结果

关于线程的状态和线程的通信请点这里

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/527791
推荐阅读
相关标签
  

闽ICP备14008679号