• 线程基础(二)


    活动地址:CSDN21天学习挑战赛

    一.线程的安全问题

    1.1 需求

    假设现在有个电影院,播放一部很欢迎的电影
    《阿伟的故事》,那么我们该怎么去售票呢?

    开一个窗口?,这样会不会卖的太慢了哇,那要是开多个窗口,就涉及到了数据交互的问题了,我们该怎么去解决?

    在这里插入图片描述

    1.1.1 第一种解决办法:

    在这里插入图片描述

    用一个窗口售票,这样一定是没有问题的

    1.1.2 第二种解决办法

    在这里插入图片描述

    多窗口,但是是有序的,每个窗口售卖固定且分配好的的票数。
    注意:

    因为没有出现数据共享,所以这种情况也是没有任何问题的

    1.1.3 第三种解决办法,实际情况

    在这里插入图片描述
    存在的问题:

    1.出售重复的票
    2.出售不存在票:0

    需求分析

    1.每一个窗口就是一个线程 使用线程来表示多窗口售票
    2.只有出现数据共享的时候才会产生问题。
    3.使用Runnable(或者callable)方式来创建线程,因为这两种方式可以创建多个线程
    3.定义一个变量来表示票数
    4.循环售票

    线程创建类

    public static class  MyRunnable implements  Runnable{
            private  int count =100;
    
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                //使用循环一直买票
                while (true){
                    if (count > 0){
                        if (count > 0){
                            System.out.println(Thread.currentThread().getName()+"\t"+"正在购买第"+count+"张票");
                        }
                        count -- ;
                    }
                }
    
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    测试类

     public static void main(String[] args) {
            //实例化MyRunnable对象
             MyRunnable runnable = new MyRunnable();
            //实例化线程
            Thread t1 = new Thread(runnable); t1.start();
            //实例化线程
            Thread t2 = new Thread(runnable); t2.start();
            // 实例化线程
            Thread t3 = new Thread(runnable); t3.start(); }
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果
    注意:结果黄黄在这里不予展示,自己测试的话多测试几次,最好多打开几个应用去测试。重复的票还算容易测试出来,0票非常难测试出来。

    1.1.3.1 出现重复的票的原因

    当线程一输出完这句话,或者正在输出这一句话的时候
    在这里插入图片描述
    这个时候线程二进来了,他也来输出这句话,即抢占资源
    在这里插入图片描述
    这时候,即线程一正在打印这句话,且还没有去执行
    在这里插入图片描述
    操作的时候,线程二进来了,也去打印。
    故此,两者打印的票数相同。

    1.1.3.2 出现0票的原因

    同理,输出-1票的原因也很简单

    1.当票数为1的时候,线程一抢到cup的执行权,执行打印语句
    2.这时候线程二进来了,抢到了cpu的执行权,只是进入选择结构中,
    3.线程一枪到了cpu执行权,执行count -- ;
    4.线程二抢到cup的资源出售不存在的票

    二.线程安全的解决办法

    解决上述问题的根本:线程的操作只能让一个线程执行操作,一个线程执行完成操作之后,另一个线程才能执行操作。

    2.1 解决办法一: synchronized锁对象

    1.语法:
    synchronized(锁对象){
    可能产生问题的代码
    }
    2.注意点
    A.锁的对象可以是任意的对象
    B.所有线程锁的对象必须是同一个对象
    .
    3.作用:
    解决数据共享安全的问题

    线程定义类

    public class two implements  Runnable {
    
        //定义一个变量来记录票数
        private    int count =100;
        //定义一个锁对象,可以是任意对象
        private  Object object =new Object();
    
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //使用循环买票
            while(true){
                synchronized (object){
                    if (count>0){
                        System.out.println(Thread.currentThread().getName() + "\t" + "two您购买的是第" + count + "张票");
                        count --;
                    }
                }
            }
        }
    }
    
    
    • 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

    测试类

     public static void main(String[] args) {
            //实例化MyRunnable对象
            two two = new two();
            //实例化线程
            Thread t1 = new Thread(two);
            t1.start();
            //实例化线程
            Thread t2 = new Thread(two);
            t2.start();
            // 实例化线程
            Thread t3 = new Thread(two);
            t3.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果
    在这里插入图片描述
    不难发现,上面的结果都是顺序输出的,也没有执行抢占,这是为什么呢

    原因分析
    在这里插入图片描述
    总结:同步代码块,保证只有一个线程在此段时间内能够执行线程操作。

    2.2 解决办法二:同步方法 (还是synchronize锁)

    1.语法: 
    访问修饰符 synchronized 返回值类型 方法的名称(参数列表) { 
    			方法体 
    			retrun 返回值 
    } 
    2.说明:同步方法可以是普通的成员方法 也可以是静态的方法 
    3.说明:
    	 A.成员方法的锁的对象就是this 
    	 B.静态方法锁的对象是当前类的class对象
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    线程实现类

    public class MyRunnable implements Runnable {
    
        private static int count = 100;
    
        @Override
        public void run() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            while (true) {
                synchronized (MyRunnable.class) {
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "\t" + "您 购买是第" + count + "张票");
                        count--;
                    }
                }
            }
        }
    
            public static synchronized void showInfo() {
                if (count > 0) {
                    System.out.println(Thread.currentThread().getName() + "\t" + "您购买是 第" + count + "张票");
                    count--;
                }
            }
        }
    
    • 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

    测试类

    ublic static void main(String[] args) {
            //实例化MyRunnable对象
            MyRunnable two = new MyRunnable();
            //实例化线程
            Thread t1 = new Thread(two);
            t1.start();
            //实例化线程
            Thread t2 = new Thread(two);
            t2.start();
            // 实例化线程
            Thread t3 = new Thread(two);
            t3.start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    测试结果:
    在这里插入图片描述
    依旧是有序的队列,且不发生抢占资源的情况。

    2.3第三种解决办法:Lock锁

    1.Lock 简介 
    A.Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 
    B.此实现允许更灵活的结构 
    2. 实现类ReentrantLock 
    3.方法
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意两个方法

    方法一:void lock() 获取锁
    方法二:void unlock() 释放锁

    线程类

    public class MyRunnable implements Runnable {
    
        //定义一个变量来记录票数
        private int count = 1000;
        //实例化Lock锁对象
        private Lock l = new ReentrantLock();
    
        @Override
        public void run() {
            //获取锁对象
            l.lock();
            try {
                Thread.sleep(500);
                //使用循环一直买票
                while (true) {
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "\t" + "您购买是第" + count + "张 票");
                        count--;
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //释放锁对象
                l.unlock();
            }
        }
    }
    
    • 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

    测试类

    public static void main(String[] args) {
                //实例化MyRunnable对象
                MyRunnable two = new MyRunnable();
                //实例化线程
                Thread t1 = new Thread(two);
                t1.start();
                //实例化线程
                Thread t2 = new Thread(two);
                t2.start();
                // 实例化线程
                Thread t3 = new Thread(two);
                t3.start();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    结果
    在这里插入图片描述

    三.死锁:

    3.1什么是死锁

    1.死锁:
     A占用B的锁对象 B占用A的锁对象 A与B互不谦让 A与B出现同时等待的状态
     2.生活中: 
    卢伟亮与黄自强在一个宿舍 宿舍中只有一双拖鞋 两人都想出去买东西 卢伟亮霸占左拖鞋 黄自强 霸占右拖鞋 互补谦让 都出不去
    
    • 1
    • 2
    • 3
    • 4

    3.2四个必要条件才能产生死锁

    产生死锁的四个必要条件
    (1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
    (2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
    (3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放
    (4)环路等待条件:是指进程发生死锁后,必然存在一个进程–资源之间的环形链
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.3解决死锁的方法

    处理死锁的基本方法
    1.预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
    2.避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁
    3.检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉
    4.解除死锁:该方法与检测死锁配合使用
    
    • 1
    • 2
    • 3
    • 4
    • 5

    四.速记

    4.1Runnable 和 Callable 的 区别?

    =相同点==
    1)两者都是接口
    2)两者都可以用来编写多线程代码
    3)两者都可以调用Thread.start()来启动线程
    不同点==
    1)最大的区别,Runnable没有返回值,而实现Callable接口的任务线程能返回执行结果
    2)Callable接口实现类中的run方法允许异常向上抛出,可以在内部处理,try catch,但是Runnable
    接口实现类中run方法的异常必须在内部处理,不能抛出
    3)Runnable可以作为Thread构造器的参数,通过开启新的线程池来执行,也可以通过线程池来执行;
    而Callable只能通过线程池来执行。

    4.2 synchronized 和 volatile 的区别?

    1)volatile是变量修饰符,而synchronized则可以修饰代码块或方法。
    2)volatile不需要加锁,比synchronized更轻量级,不会阻塞线程;而synchronized加锁,会导致线程
    阻塞(互斥性)
    3)synchronized既能够保证可见性,又能保证原子性,而volatile只能保证可见性,无法保证原子性。
    4)synchronized会执行编译优化(对字节码重新排序),编译优化有可能导致运行过程中异常。而
    volatile禁止字节码重新排序,不会引发编译优化导致的异常。
    原子性:一个操作不能被打断,要么全部执行完毕,要么不执行
    可见性:一个线程对共享变量做了修改之后,其他的线程立即能够看到(感知到)该变量这种修改(变
    化)。
    并发编程三大特征:原子性。可见性,有序性
    volatile作用:修饰变量,这个变量就会满足可见性与有序性,当线程修改这个变量的时候,别的线程
    就会感知到这个变量进行了修改,

    4.3 synchronized 和 Lock 的区别?

    1)语法不同:
    synchronized 是Java的关键字或修饰符,在jvm层面上,修饰方法或代码块。
    Lock不是修饰符,是一个接口(加锁的工具类,该类提供很多方法加锁、释放锁等)tryLock()
    unLock()
    2)释放锁不同():
    synchronized 获取锁的线程执行完同步代码,释放锁,且线程执行发生异常,jvm会自动让线程释
    放锁(不管成功还是失败,都会自动释放锁)
    Lock必须手动在finally中释放锁(必须手动释放锁)
    3)死锁情况不同(
    ):
    synchronized 在发生异常时候会自动释放占有的锁,因此不会出现死锁;
    Lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生
    4)锁判断不同:
    synchronized 无法判断当前线程的上锁状态
    Lock可以判断当前线程的上锁状态 tryLock unLock isLock
    5)锁类型不同:
    synchronized是可重入 不可判断 非公平
    Lock:是可重入 可判断 可公平
    公平性(线程等待时间长,优先获取锁)

  • 相关阅读:
    第 1 章 微信小程序与云开发从入门到实践从零开始做小程序——开发认识微信小程序
    阿里云99元服务器2核2G3M带宽_4年396元_新老用户同享
    gitlab有哪些metrics
    [附源码]计算机毕业设计springboot本地助农产品销售系统
    生成对抗网络(GAN)
    Redis中的慢查询日志(一)
    HTML小游戏15 —— 网页版3D反恐英雄(附完整源码)
    mPEG-Cholesterol mPEG-CLS 甲氧基-聚乙二醇-胆固醇一种膜成分
    C++学习:this指针
    Ajax 实战
  • 原文地址:https://blog.csdn.net/weixin_43189971/article/details/126104427