• 线程安全问题以及其解决方法


    1.为什么会出现线程安全问题?​

    2.java内存模型

    3.判断一个多线程应用程序是否有问题的标准:​

    4.如何解决多线程安全问题呢?​

    5.同步代码块(synchronized)

    ​同步代码块的格式: 

    锁的分类:

    用锁解决案例问题:

    ​同步的好处和弊端:    


    1.为什么会出现线程安全问题?

    实例代码:

    1. public class MyTest {
    2. public static void main(String[] args) {
    3. SellTicketRunnable runnable = new SellTicketRunnable();
    4. //创建了三个线程
    5. Thread th1 = new Thread(runnable);
    6. Thread th2 = new Thread(runnable);
    7. Thread th3 = new Thread(runnable);
    8. //给三个线程重新命名
    9. th1.setName("窗口1");
    10. th2.setName("窗口2");
    11. th3.setName("窗口3");
    12. //开启线程
    13. th1.start();
    14. th2.start();
    15. th3.start();
    16. }
    17. }
    18. public class SellTicketRunnable implements Runnable {
    19. static int num = 100; //三个线程共享了这个数据(一共有100张票)
    20. @Override
    21. public void run() {
    22. while (true) {
    23. if (num > 0) {
    24. try {
    25. Thread.sleep(20); //模拟以下延迟
    26. } catch (InterruptedException e) {
    27. e.printStackTrace();
    28. }
    29. System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
    30. }
    31. }
    32. }
    33. }

    现实中售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟.于是在代码中加入了一点延迟!这个不影响什么的!看起来没有什么问题!部分运行结果如下:

           

    看到这运行结果,就发现问题了!现实中的售票情况怎么可能会几个窗口出售同一张票呢?甚至有的时候还会出现出售0票和负票的情况.这就是出现了线程安全问题!

    那么重点来啦!为什么会出现线程安全问题呢?为什么会几个窗口出售同一张票,0票甚至负票呢?先来了解一下Java内存模型吧!!!

     2.java内存模型

    Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来,也就是读)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

    简单点就是,请看图!!!!!

     如上这样毫无规矩,随机性的抢占,当然会坏事啦!试想一下,去医院看病,这般你争我抢,直接毁灭吧!!!说个玩笑话!这样的抢占,就引起了安全问题!怎么解决,相信友友们已经有想法了.

    3.判断一个多线程应用程序是否有问题的标准:

    总结一下会出现安全隐患的标准:  得是多线程环境、有共享数据、有多条语句操作共享数据!

    4.如何解决多线程安全问题呢?

            我们无法让其没有共享数据更没有办法让其不处于多线程的环境!如果不是多线程,那当然不会出现安全隐患的问题啦!那么只能从多条语句操作共享数据下手了!那在操作共享数据的时候,能不能一次只让一个进入呢?当然可以!所以就出现了锁!  把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可,操作完了,锁立马释放掉!  就不会出现线程安全问题啦!

    5.同步代码块(synchronized)

    同步代码块的格式: 

    1. synchronized(对象){ 
    2. 要被同步的代码 ; 

    注意: 不能在大括号里直接new 对象, new 了 就没效果了!

    这个同步代码块保证数据的安全性的一个主要因素就是这个对象. 注意这个对象要定义为静态成员变量才能被所有线程共享.这个对象其实就是一把锁. 这个对象习惯叫做监视器.

    锁的分类:

    内置锁

            每个java对象都可以用做一个实现同步的锁,这些锁便被称为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁. 同步代码代码块上的锁亦是如此. 这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

    对象锁和类锁

            java的对象锁和类锁在锁的概念上基本上和内置锁是一致的. 但是两个锁实际是有很大区别的,对象锁是用于对象实例方法或者一个对象实例上的,而类锁是用于类的静态方法或者一个类的class对象上的。类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。还有一点必须注意的哟!也就是类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.

    哈哈哈!不要晕乎乎!这个锁的分类只是想让友友们了解一下而已啦!

    用锁解决案例问题:

    我们用锁解决一下上面案例出现的线程安全问题!!!!

    1. public class MyTest {
    2. public static void main(String[] args) {
    3. SellTicketRunnable runnable = new SellTicketRunnable();
    4. //创建了三个线程
    5. Thread th1 = new Thread(runnable);
    6. Thread th2 = new Thread(runnable);
    7. Thread th3 = new Thread(runnable);
    8. //给三个线程重新命名
    9. th1.setName("窗口1");
    10. th2.setName("窗口2");
    11. th3.setName("窗口3");
    12. //开启线程
    13. th1.start();
    14. th2.start();
    15. th3.start();
    16. }
    17. }
    18. public class SellTicketRunnable implements Runnable {
    19. static int num = 100; //三个线程共享
    20. static Object obj = new Object(); //上锁对象,静态成员变量
    21. @Override
    22. public void run() {
    23. while (true) {
    24. //th1 th2 th3中任意一个线程,一旦进入同步代码块,就会被加锁,其他没被加锁的对象便会在此等待
    25. synchronized (obj) { //这个就是锁
    26. if (num > 0) {
    27. try {
    28. Thread.sleep(20); //模拟延迟
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. System.out.println(Thread.currentThread().getName() + "-正在出售" + (num--) + "张票");
    33. }
    34. } //出了同步代码块,就会释放锁
    35. }
    36. }
    37. }

    现在查看结果就不会出现几个窗口出售同一张票以及出售0票和负票的情况了!!!!这可是个好东西啊!!!

    同步的好处和弊端:    

    同步的出现  解决了多线程的安全问题。 其弊端就是,当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会  降低程序的运行效率


    (小编也在努力学习更多哟!以后再慢慢分享的啦!)

    希望对友友们有所帮助!!!!

  • 相关阅读:
    三极管:潜伏3个月的彩蛋是来自火星的么
    77. 查看线程的运行状态
    Linux高级I/O:非阻塞IO fcntl | 多路转接之select | poll
    ES数据存储和集群路由原理
    .Net MVC 使用Areas后存在相同Controller时报错的解决办法; 从上下文获取请求的Area名及Controller名
    软件测试分类
    ROS系统通过类定义实现数据处理并重新发布在另一话题
    基于ARM的字符串拷贝实验(嵌入式系统)
    Moore-Penrose伪逆
    NetDevOps — YANG 协议 — 模型文件
  • 原文地址:https://blog.csdn.net/naoguoteng/article/details/126102962