多线程带来的风险-线程安全

1、多线程的三大特性

  • 原子性::可以理解为 CPU 执行一次简单的基本操作
  • 可见性:JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线
    程之间不能及时看到改变,这个就是可见性问题
  • 有序性:一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

案例描述:
电影院有三个窗口,售两种票共 100 张,输出售票情况

class SellTicket implements Runnuble {
	private int ticket = 50;
	public void run() {
		while (true) {
			if (ticket > 0) {
			// 模拟更加真实场景,每次出票后延迟 100 毫秒
				try {
					Thread.sleep(100);
				} catch (InterruptException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currrentThread().getName()+"正在出票"(ticket--)+"票数");
			}
		}
	}
}

public class SellTIcketDemo {
    public static void main(String[] args) {
        //创建线程资源对象
        SellTicket s = new SellTicket();
        //创建三个线程对象
        Thread t1 = new Thread(s,"窗口1");
        Thread t2 = new Thread(s,"窗口2");
        Thread t3 = new Thread(s,"窗口3");
        //启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述

分析:

每次输出的结果不同,此次结果出现了两种不安全的情况:相同的票和负数票的情况
那么那些问题会引起线程的不安全?
1>是否是多线程环境
2>是否有数据共享
3>是否有多个操作于共享数据

而在以上的代码中前两条是完全符合的,问题在于是否有多个操作共享数据?
注意 ticket-- 操作;CPU并不是简单的执行了一次减一,而是进行了三步:

1> 从内存读取变量到 CPU
2> 修改变量
3> 写回内存

出现相同的票(CPU的操作原子性决定的)
CPU 的每一次执行都是原子性(最简单基本)的操作,比如 ticket–其实并不是一次操作;
首先记录tiket以前的值 50
接着执行 tiket-- 的操作
然后输出以前的值 50 t1输出,就在这之间可能 t2 就来了(并发/并行),则 t2 也输出一样的了
tiket 的值变成 49

出现负数票的情况,就是在休眠的时候(随机性和延迟导致)
t1 、t2、t3 恰好都在 100 毫秒内分别进来了,所以三个都会执行
所以最坏情况下:
正在出售第 1 张票 ticket = 0
正在出售第 0 张票 tiket = -1
正在出售第 -1 张票 tiket = -2

常见的非原子性操作
n++;–n 从内存读取变量到CPU,修改变量、写回内存
对象的new操作 分配对象的内存、初始化对象、将对象赋值给变量

2、synchronized 实现线程同步

  • 同步代码块
synchronized(对象) {
	需要同步的代码块
			}
//注意同步可以解决安全问题的根本原因在对象上,该对象如同锁的功能
//多个线程必须是同一把锁
//锁的对象是任意对象

1> 特点:多个线程是前提、多个线程使用的是同一个锁对象
2> 好处:解决了多线程的安全问题
3> 弊端:当线程相对较多时,每个线程都会去判断同步上的锁,这是很耗费资源的会降低程序的运行效率

  • 同步方法
    把同步关键字加在方法上
    同步方法的锁对象:this
    静态方法及锁对象问题:当前类的class文件对象

修改后线程安全

public class Sell_Ticket implements Runnable{
    //定义 50 张票
    private int ticket = 50;
    //创建锁对象
    private Object obj = new Object();
    public void run() {
        while (true) {
            synchronized(obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售" + (ticket--) + "票");
                }
            }
        }
    }
}


public class Sell_TicketDemo {
    public static void main(String[] args) {
        //创建资源对象
        Sell_Ticket my = new Sell_Ticket();
        //创建线程对象
        Thread t1 = new Thread(my,"窗口1");
        Thread t2 = new Thread(my,"窗口2");
        Thread t3 = new Thread(my,"窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}


发布了52 篇原创文章 · 获赞 6 · 访问量 1441

猜你喜欢

转载自blog.csdn.net/qq_40488936/article/details/104908799
今日推荐