• 四、线程安全问题以及锁的概念


    JAVA 多线程会造成线程安全问题的原因总结



    一、线程安全问题案例

            多线程是我们在日常开发中十分重要的环节,这个技术点可以实现程序功能同时执行,同时完成多个任务。但是如果多线程技术使用不当,或者逻辑不合理,就会造成线程安全问题,这个问题不出现还好,一出现就是大问题,所以必须严加防范。

    这里使用实现购票功能案例来展示线程安全问题

    	    public class TestRunnable3 implements Runnable{
    	
    	    private int ticket = 10;
    	
    	    @Override
    	    public void run() {
    	
    	        while (true){
    	            if(ticket<=0){
    	                break;
    	            }
    	            //因为CPU执行的太快,导致分配不均匀,这里加上一个模拟延时的方法
    	            try {
    	                Thread.sleep(200);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	             System.out.println(Thread.currentThread().getName()+"抢到了第"+ +ticket-- + "张票");
    	        }
    	    }
    	
    	    public static void main(String[] args) {
    	        TestRunnable3 testRunnable3 = new TestRunnable3();
    	
    	        new Thread(testRunnable3,"小明").start();
    	        new Thread(testRunnable3,"小红").start();
    	        new Thread(testRunnable3,"小兰").start();
    	    }
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果: 通过运行可以发现,多个人同时抢到了同一张票,这显然是不合理的,并且这种结果还会实际应用中造成非常严重的后果,必须要避免此类状况的发生。

            这种线程安全问题是因为同一个共享资源同时被多个线程所使用导致的。想要避免这种状况就需要将这些这些共享的资源锁起来,让这些共享资源在同一时间只能由一个线程使用,只有之前的线程使用完下一个线程才能继续使用资源。



    二、多线程 锁

            多线程中的锁可以解决绝大多数因为多个线程互相争夺资源导致的线程不安全问题。
            其中的原理就像是将线程归拢成队列的形式,各个线程需要排队去使用这些资源。并且将这些资源加上一个锁,当线程使用资源时,这个锁会阻拦其他线程使用该资源。只有正在使用资源的线程执行完毕,下一个线程才可以使用该资源。

    一句话总结锁的作用:一群人排队做核算,前面的做完了,下一个才可以做。

    加锁的形式有两种:
                                    隐式加锁:隐式的加锁的方式有两种,同步代码块以及同步方法。使用隐式加锁的方式无法看到加锁和释放锁的过程。
                                    显式加锁:在JDK5以后,Java 提供了一个新的锁对象Lock,可以更加更清晰的表达如何加锁和释放锁的整个过程。


    1、隐式加锁的方式:同步代码块和同步方法

    • 同步代码块
      • 同步代码块的格式:
        • synchronized(对象){ 需要同步的代码; }

    Demo代码示例:

    	public class TestRunnable3 implements Runnable{
    	
    	        // 定义10张票
    	        private int tickets = 10;
    	        //创建锁对象
    	        private Object obj = new Object();
    	
    	        @Override
    	        public void run() {
    	            while (true) {
    	                synchronized (obj) {
    	                    if (tickets > 0) {
    	                        try {
    	                            Thread.sleep(100);
    	                        } catch (InterruptedException e) {
    	                            e.printStackTrace();
    	                        }
    	                        System.out.println(Thread.currentThread().getName()
    	                                + "正在出售第" + (tickets--) + "张票");
    	                    }
    	                }
    	            }
    	        }
    	
    	        public static void main(String[] args) {
    	            TestRunnable3 testRunnable3 = new TestRunnable3();
    	
    	            new Thread(testRunnable3,"小明").start();
    	            new Thread(testRunnable3,"小红").start();
    	            new Thread(testRunnable3,"小兰").start();
    	        }
    	    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    运行结果:
    在这里插入图片描述



    • 同步方法

      • 同步方法就是加了同步关键字的方法

    • Demo代码示例:
    public class TestRunnable3 implements Runnable{
    
            // 定义10张票
            private int tickets = 10;
    
            @Override
            public void run() {
                while (true) {
                    synchronization();
                }
            }
    
    	// 定义同步方法 
        public synchronized void synchronization(){
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
                }
            }
    
            public static void main(String[] args) {
                TestRunnable3 testRunnable3 = new TestRunnable3();
    
                new Thread(testRunnable3,"小明").start();
                new Thread(testRunnable3,"小红").start();
                new Thread(testRunnable3,"小兰").start();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    运行结果:
    在这里插入图片描述




    2、显式加锁的方式:Lock锁

            线程的安全问题虽然可以使用同步代码块和同步方法来解决。但是并不能看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。
    • Lock 接口
      • 常用方法:
        • void lock():获取锁
        • void unlock():释放锁

    Demo代码示例:

     public class TestRunnable3 implements Runnable{
    
        // 定义10张票
        private int tickets = 10;
    
        // 定义锁对象
        Lock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                // 加锁
                lock.lock();
                try {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票");
                    }
                } finally {
                    // 释放锁
                    lock.unlock();
                }
            }
        }
    
        public static void main(String[] args) {
            TestRunnable3 testRunnable3 = new TestRunnable3();
    
            new Thread(testRunnable3,"小明").start();
            new Thread(testRunnable3,"小红").start();
            new Thread(testRunnable3,"小兰").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    运行方法:
    在这里插入图片描述




    3、使用锁的好处和弊端

    • 好处:解决了线程安全问题
    • 弊端:因为使用同步的方式后线程的执行会呈现为队列的形式,虽然保证了程序的安全性,但是程序的性能也会降低。
    • 使用同步的前提:有多个线程调用同一个代码资源

    4、死锁问题及解决办法

            是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。

            减少死锁的办法:
                                    1. 专门的算法、原则。
                                     2. 尽量减少同步资源的定义。
                                     3. 尽量避免嵌套同步。

    死锁问题详解以及如何解决

  • 相关阅读:
    什么是水坑攻击
    数据分析——A/B测试二:优惠券AB测试项目
    GitHub 上标星 75k+ 的《Java 面试突击版,京东 java 面试题答案
    【代码审计-PHP】phpStudy(新版) + PhpStorm + XDebug动态调试
    强化学习 导论
    2024级199管理类联考之数学基础(下篇)
    什么是分布式软件系统
    移动流量的爆发式增长,社交电商如何选择商业模式
    数据结构的结构复杂度你了解么
    Monaco Editor教程(十七):代码信息指示器CodeLens配置详解
  • 原文地址:https://blog.csdn.net/shiyu_951/article/details/126347580