• Java之多线程基础


    基本介绍(程序,进程,线程)

    程序

    想为完成任务,用指定的语言编写的一组指令的集合
    简单的讲就是我们写的代码

    进程

    进程指的是运行中的程序
    比如我们使用qq,就启动了一个进程
    系统会为该进程分配内存空间
    进程是程序一次执行的过程,或者正在运行的一个程序。是动态过程:有他自身的产出存在消亡的过程

    线程

    在这里插入图片描述
    迅雷同时下载多个文件
    窗口是一个进程
    一个文件下载就是一个线程

    单线程和多线程

    在这里插入图片描述

    并发和并行

    在这里插入图片描述
    并行
    只是来回切换程序,并不是真正的同时进行
    比如你打电话开车
    只是来回切换状态,只是切换太快,没有感觉

    并发
    是同一个时刻多个程序同时执行
    一个cpu运行一个程序
    如果说并发总也出现了
    一个cpu执行多个任务
    你就是并发中的并行喽
    在这里插入图片描述

    继承Thread类创建线程

    Thread类继承图

    在这里插入图片描述

    创建线程的方法

    1.实现Runnable接口,重写run方法

    package com.hansp.thread_;
    
    public class Demo01 {
        public static void main(String[] args) {
            A a = new A();
            //a.start();这里不能调用start,因为没有该方法
            Thread thread = new Thread(a);
            thread.start();//创建一个thread然后调用他的start要把我们实现接口的对象当做形参
        }
    }
    class A implements Runnable{//通过实现Runnable接口,开发线程
    
    int count=0;
        @Override
        public void run() {
        while (true)
        {
            System.out.println("小狗汪汪叫。。hi"+(++count)+"线程名称"+Thread.currentThread().getName());
            //休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(count==10)
            break;
        }
        }
    
    }
    
    
    • 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

    在这里插入图片描述

    这里需要用一个构造器
    传入实现接口对象
    取得Thread对象
    最后才可以调用
    start()创建线程
    至于这个构造器的细节看底层那里

    2.继承Thread类,重写run方法

    下面看代码

    package com.hansp.thread_;
    
    public class CPUNumber {
        public static void main(String[] args) {
    
            cat cat = new cat();
            cat.start();//启动线程,会自动调用run
            //具体看的曾分析吧
        }
    }
    //1.当一个类继承了Thread类,该类就可以当做线程使用
    //2.重写run方法,协商自己的业务代码
    //3.run Thread类实现了Runnable接口的run方法
    /*Thread里面的run的源码
    @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
     */
    
    class cat extends Thread{
        @Override
        public void run() {
            int times=0;
            while (true) {
                //该线程每隔一秒。在控制台输出“喵喵,我是小猫咪”
                System.out.println("喵喵,我是小猫咪"+(++times));
                //让该线程休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
    
                    }
                if(times==80){//当输出80次时退出
                    break;
                }
            }
        }
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述

    3.两种方法区别

    在这里插入图片描述
    在这里插入图片描述

    建议多用Runnable接口来创建线程

    底层分析(重要)

    进程到线程
    线程全部执行完毕后进程才会完毕
    多线程编程中,不是主线程结束了,进程就结束了,需要所有的线程都运行完成进程才会结束

    进程和线程关系,线程间关系

    System.out.println("喵喵,我是小猫咪"+(++times)+"线程名"+Thread.currentThread().getName());
    
    • 1

    Thread.currentThread().getName()
    这行代码可以获取到所在位置的线程的线程名
    在这里插入图片描述
    可以发现cat这个类启动的线程名叫Thread-0

    但是此时main线程还是继续执行的
    在这里插入图片描述
    不会说卡在start方法
    等着run的语句执行完,他们两个是一起执行的
    在执行的效果可以看出
    主线程和子线程交替执行

    相当于在mian线程里面开了个Thread-0线程
    用一个图来表示
    在这里插入图片描述
    但是主线程执行完,也不会停止其子线程运行完
    只有运行完然后子线程会退出

    多线程编程中,不是主线程结束了,进程就结束了,需要所有的线程都运行完成进程才会结束

    然后进程就无了

    为什么是start不是run

    如果把
    cat.start()
    改为
    cat.run()
    相当于是没有开辟线程的
    就是普通的创建对象->调用方法
    要把run方法执行完后才执行主程序的代码
    那就不是多线程了,这叫串行化
    而且线程名还是会输出main,没有开辟Thread-0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    start源码+解读

     if (threadStatus != 0)
                throw new IllegalThreadStateException();
            group.add(this);
    
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    start实现多线程的关键方法start0()
    start0()是一个本地(native)方法底层是由JVM调用的
    我们不能调用

    一些小细节
    在这里插入图片描述

    实现Runnable接口的类通过构造器转变为Thread内部细节

    这里用了一个设计模式(代理模式)

    这里用代码来模拟
    Runnable接口开发线程的机制

    //线程代理类,模拟了一个极简的Thread类
    class Proxy implements Runnable{//你可以把Proxy(代理)类当做Thread类对待
    private Runnable target = null;
    
        @Override
        public void run() {
            if (target!=null){
                target.run();
            }
    
        }
        public Proxy (Runnable target){//构造器
            this.target=target;
        }
        public void start(){
            start0();
        }
        public void start0(){
            run();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    差不多就是把我们的
    Runnable接口实现的对象赋给那个Thread的一个Target(也是Runnable类型)
    然后调用Thread的的start的话
    会调用Thread的start0()
    新建线程
    新建完线程以后
    start0()调用target.run(),然后动态绑定到最开始那个实现Runnable的类型的run
    相当于只用了
    我们传进来的那个类型的run方法
    其余都是Thread类操作还是调用的Thread的start()和start0()方法
    这叫代理模式

    多子线程案例

    package com.hansp.thread_;
    @SuppressWarnings({"all"})
    public class Demo02 {
        public static void main(String[] args) {
            M m = new M();
            M m1 = new M();
            Thread thread = new Thread(m);
            Thread thread1 = new Thread(m1);
            thread.start();//第一个线程
            thread1.start();//第二个线程
        }
    }
    class M implements Runnable{
    
        @Override
        public void run() {
            int count=0;
            while (true){
    
                System.out.println("hi"+(++count)+"线程名"+Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                if (count==10){
                    break;
                }
            }
        }
    }
    
    • 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

    在这里插入图片描述

    相当于一个main线程开了两个线程
    一个Thread-0
    一个Thread-1

    图像理解
    在这里插入图片描述

    多线程售票系统(小应用)

    继承Thread版本

    package com.hansp.thread_;
    @SuppressWarnings({"all"})
    public class Demo03 {
    
        public static void main(String[] args) {
            C c = new C();
            C c1 = new C();
            C c2 = new C();
            c.start();
            c1.start();
            c2.start();
        }
    
    }
    
    
    @SuppressWarnings({"all"})
    class C extends Thread {
    
        private static int times=100;
        @Override
        public void run() {
            while (true){
                if(times<=0){
                    System.out.println("售票结束...");
                    break;
                }
    
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                +"剩余"+(--times)+"票");
            }
    
        }
    }
    
    • 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
    • 39

    在这里插入图片描述
    这种方式会出现线程不安全的弊端

    什么导致这种情况的发生呢

    分析
    线程经过判断,然后对数据进行操作
    在这里插入图片描述

    当times==1时
    可能第一个线程使time–还没有发生(中间也有sleep)
    第二个线程判断就已经完了,相当于,判断没有生效
    导致time又被减1
    同理第三个线程也可能导致,在前两个没执行完也判断完,也使times–
    最后1-3=-2

    实现Runnable接口方式

    package com.hansp.thread_;
    @SuppressWarnings({"all"})
    public class Demo03 {
    
        public static void main(String[] args) {
            C c = new C();
            C c1 = new C();
            C c2 = new C();
            Thread thread1 = new Thread(c);
            Thread thread2 = new Thread(c1);
            Thread thread3 = new Thread(c2);
            thread1.start();
            thread2.start();
            thread3.start();
        }
    
    }
    
    
    @SuppressWarnings({"all"})
    class C implements Runnable {
    
        private static int times=100;
        @Override
        public void run() {
            while (true){
                if(times<=0){
                    System.out.println("售票结束...");
                    break;
                }
    
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                +"剩余"+(--times)+"票");
            }
    
        }
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42

    在这里插入图片描述
    也是会出现之前那种情况

    超卖和一票多卖
    都是有可能的

    具体结局办法在下面的Synchronzied

    线程终止

    在这里插入图片描述
    1.正常线程运行完后就会自动终止

    2.主线程通过子线程的控制run方法来实现
    终止子线程的运行

    package com.hansp.thread_;
    
    public class exit_ {
    
        public static void main(String[] args) {
            T t = new T();
            t.start();
    
            //如果希望主线程控制t线程的终止,必须可以修改loop变量
            //让t退出run方法从而终止t线程
    
            System.out.println("主线程休眠十秒钟");
            try {
                Thread.sleep(10*1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    
            t.setLoop(false);
        }
    }
    @SuppressWarnings({"all"})
    class T extends Thread{
        private int count=0;
        private boolean loop=true;
        @Override
        public void run() {
            while (loop){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("运行中");
            }
        }
    
        public void setLoop(boolean loop) {
            this.loop = loop;
        }
    }
    
    
    • 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
    • 39
    • 40
    • 41
    • 42

    线程常用方法

    在这里插入图片描述
    1.setName(String name)
    2.getName(Thread a)
    3.开辟线程
    4.普通调用对应线程类的run方法
    5和6放下面单独讲
    7.sleep(毫秒数)使调用的线程休眠对应的毫秒数
    8.注意,中断线程不是停止线程,此方法一般用于中断正在休眠线程

     try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {//InterruptedException e就是一个中断异常
                //当中断正在休眠的线程就会执行下面的代码
                    throw new RuntimeException(e);
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    在这里插入图片描述
    1.线程.yield()

    线程插队

    package com.hansp.thread_;
    
    public class Demo04 {
        public static void main(String[] args) throws InterruptedException {
            Q q = new Q();
            Thread thread = new Thread(q);
        thread.start();
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("hi");
            }
            thread.join();//也可以在循环中用if(i=5)join
            //如果想达到5时候插入
            //可以
            //if(i==5){
            //thread.start();thread.join();
            //}
            for (int i = 0; i < 5; i++) {
                Thread.sleep(1000);
                System.out.println("hi");
            }
        }
    }
    class Q implements Runnable{
        int i=0;
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
    
                System.out.println("hi"+(++i));
                if (i==10){
                    break;
                }
            }
    
        }
    }
    输出
    hi
    hi1
    hi
    hi2
    hi
    hi3
    hi
    hi4
    hi5
    hi
    hi6
    hi7
    hi8
    hi9
    hi10
    hi
    hi
    hi
    hi
    hi
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    比如说两个线程一起执行
    t1和t2
    在t1线程里调用t2.join()
    会先执行t2
    然后指向t1

    注意只有你在线程a调用b.join()
    只有线程a会等你执行完再执行如果线程a里面还有线程c
    c会和b一起执行
    如图
    在这里插入图片描述

    线程中断sleep

    package com.hansp.thread_;
    @SuppressWarnings({"all"})
    public class Demo03 {
    
        public static void main(String[] args) throws InterruptedException {
            C c = new C();
            Thread thread = new Thread(c);
            thread.start();
            for (int i = 0; i < 5; i++) {
                Thread.sleep(10);
                System.out.println("hi"+i);
            }
            thread.interrupt();
        }
    
    }
    
    
    @SuppressWarnings({"all"})
    class C implements Runnable {
    
        private static int times=100;
        @Override
        public void run() {
            while (true){
                if(times<=0){
                    System.out.println("售票结束...");
                    break;
                }
    
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    System.out.println("被中断了" );
                }
                System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                +"剩余"+(--times)+"票");
            }
    
        }
    }
    
    • 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
    • 39
    • 40
    • 41

    在这里插入图片描述
    thread.interrupt();
    运行到
    中断语句时会中断thread线程的睡眠,然后运行catch的语句
    如果catch语句没有导致程序停止运行
    然后继续执行

    就是打断线程休眠,让他提前干活

    线程优先级

    线程的优先级:
    1.MAX_PRIORITY :10
    MIN_PRIORITY :1
    NORM_PRIORITY :5 -->默认优先级
    2.如何获取和设置当前线程的优先级:
    getPriority():获取线程的优先级
    setPriority(Thread p):设置线程的优先级
    说明:高优先级的线程要抢占低优先级线程的cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

    用户线程和守护线程

    在这里插入图片描述

    这么说
    main线程启动一个thread-0线程
    如果mian线程结束的话
    thread-0也随之结束
    这个thread-0线程就叫做main线程的守护线程

    下面展示如何吧一个线程设置成守护线程
    正常情况
    主线程运行完后
    子线程还在跑

    package com.hansp.thread_;
    import java.lang.Thread;
    @SuppressWarnings({"all"})
    public class Demo03 {
    
        public static void main(String[] args) throws InterruptedException {
            C c = new C();
            Thread thread = new Thread(c);//设置成守护线程之需要在这加thread.setDaemon(true);一定要在start()前
            thread.start();
            for (int i = 0; i < 10; i++) {//main线程
                System.out.println("工作");
                Thread.sleep(1000);
            }
    
        }
    
    }
    
    
    @SuppressWarnings({"all"})
    class C implements Runnable {
    
    private static int times=0;
        @Override
        public void run() {
            while (true){
    
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("被中断了" );
                }
                System.out.println("窗口"+Thread.currentThread().getName()+"售出第"+(++times)+"票");
            }
    
        }
    }
    
    
    • 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
    • 39

    如果我们想主线程结束后thread-0线程也退出
    需改为守护线程
    只需要加一句

    thread.setDaemon(true);
    
    • 1

    注意这句话要写在thread.start()前面

    线程的生命周期(7大状态)

    官方的六种状态,至于为什么说7种,看下面的流程图
    在这里插入图片描述

    线程生命周期的转换图
    在这里插入图片描述
    所说的7大状态实则
    就是把Runnable状态细分为Running和Ready状态
    一种是准备好运行不过还没运行
    一种是正在运行

    Runnable只是代表可以运行

    接下来一个个讲
    1.Runnable
    运行状态
    内分为Ready和Running状态
    准备运行和正在运行状态
    2.Terminated
    终止状态
    当一个线程运行完后就会进入终止状态
    3.Blocked
    就是等着拿到锁
    然后线程进入对应代码
    执行
    4.Waiting
    当这个线程比如说调用
    别的线程join
    或者用wait方法
    会进入等待状态
    5.TimedWaiting
    超时等待状态
    sleep
    或者join也可能使线程进入该状态
    6.NEW状态
    只有new一个线程的时候
    才会进入这个状态

    可以通过 线程.getState() 查看对应线程状态
    在这里插入图片描述
    第六行代码
    只要
    t线程不没有结束
    就一直循环
    在这里插入图片描述

    线程同步机制

    在这里插入图片描述
    一个数据被多个线程访问
    可能会导致线程安全问题的发生
    如上面的例子
    我们用关键字
    Synchronized
    可以防止多个线程同时操控同一个数据
    就可以等一个线程操作完数据后
    再让另一个线程进入操作该数据(内存)

    具体操作方法(售票问题改进)

    在这里插入图片描述
    1.代码块内的代码
    同一时间内只有一个线程能够操作

    传入的对象有对应的对象锁
    只有一个线程得到对应的对象锁
    才能进入该同步代码块
    一个线程操作完,返回对象锁
    然后下一个线程得到对象锁,再来运行同步代码块的运行

    2.同步方法
    不管有多少个线程
    同一时间只有一个线程能操作
    m(同步)方法

    3.是1和2生动的比喻

    改编售票代码

    package com.hansp.thread_;
    @SuppressWarnings({"all"})
    public class Demo03 {
    
        public static void main(String[] args) {
            C c = new C();
            Thread thread = new Thread(c);
            Thread thread01 = new Thread(c);
            Thread thread02 = new Thread(c);
            thread.start();
            thread01.start();
            thread02.start();
        }
    
    }
    
    
    @SuppressWarnings({"all"})
    class C implements Runnable {
    
        private static int times=100;
        private  boolean loop=true;
        public synchronized void sell(){//同步方法在同一时刻只能有一个线程执行sell方法
    
            if(times<=0){
                System.out.println("售票结束...");
                loop=false;
                return;
            }
    
    
            System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                    +"剩余"+(--times)+"票");
        }
        @Override
        public void run() {
            while (loop){
    
                sell();
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
    
            }
    
        }
    }
    
    • 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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在这里插入图片描述
    这样就不会超卖了

    注意
    1.

    if(times<=0){
                System.out.println("售票结束...");
                loop=false;
                return;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这里一定要加return;
    因为只有方法是同步锁
    如果线程1进入
    正好卖完times==0
    此时loop=false
    但是卡线程2和线程3的地方不是循环
    是方法
    线程1输出完后
    线程2已经在等着进入了,不会在while(loop)判断等很久
    所以一定要return
    防止times已经变为0
    但是还是会执行

            System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                    +"剩余"+(--times)+"票");
    
    • 1
    • 2

    这条语句导致超卖

    2.sleep最好放外面
    可能有只有一个线程在卖票
    然后卖完的情况
    把sleep从同步方法移到不同步的
    run方法
    或者把票数改大一点
    就能看见多个窗口卖票的情况了

    互斥锁

    操作流程

    在这里插入图片描述
    t1,t2和t3三个线程抢锁
    谁抢到谁先进去操作相关代码
    比如t1先进去
    在这里插入图片描述
    操作完后t1返回
    把锁还回去然后继续和t2t3争抢

    当然可能还是t1又抢夺到了
    因为Synchronized这是个非公平锁,谁抢到就是谁的

    基本介绍

    在这里插入图片描述
    关于5和6
    5.是加在同一个对象上的

    必须是同一个线程对象开启的线程互相之间才会收到锁的影响
    不同对象的话不会

    同步方法比如上面的售票改版
    就是把锁加到this上了,即调用该方法的对象
    如果想把锁加到别的对象上,可以用同步代码块

     public synchronized void sell(){//同步方法在同一时刻只能有一个线程执行sell方法
    
            if(times<=0){
                System.out.println("售票结束...");
                loop=false;
                return;
            }
    
    
            System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                    +"剩余"+(--times)+"票");
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    改为

     public  void sell(){//同步方法在同一时刻只能有一个线程执行sell方法
    
       synchronized(this) {if(times<=0){
                System.out.println("售票结束...");
                loop=false;
                return;
            }
    
    
            System.out.println("窗口"+Thread.currentThread().getName()+"售出一张票"
                    +"剩余"+(--times)+"票");
                    }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    两种效果是相同的
    都是把互斥锁加到this对象上

    当然可以换成别的对象(不过要保证对象是同一个)
    比如Object a=new Object();
    synchronized(a){
    }
    也可以
    因为,都是同一个线程对象开辟的线程
    使用这个对象的a肯定都是同一个(同一个object)

    也一static new一个对象
    这样的话就算是不同的对象a只要是该类的对象
    那么这个对象锁也是同一个

    6.是只要是这个类创建的对象都可以受到对应锁的影响

    对应的static方法里用
    synchronized代码块
    不能这样写
    在这里插入图片描述

    需要这样写
    就是
    类名.class
    在这里插入图片描述

    注意事项

    在这里插入图片描述

    线程的死锁

    基本介绍

    在这里插入图片描述

    举例

    在这里插入图片描述
    首先
    run方法不是同步方法
    可以好几个线程一起进入
    里面有两个同步代码块
    第一个同步代码块需要先获得o1然后获得o2才可以完成代码块执行
    但是比如说线程1进入第一个代码块拿到了o1
    此时,可能flag恰好改变
    线程2也进入到了第二个代码块拿到了o2
    这时线程1拿不到o2
    线程2拿不到o1
    形参了线程死锁的状态
    在这里插入图片描述
    在这里插入图片描述
    这就是典型的线程死锁现象
    后面的true和false是赋值给flag的

    注意:o1和o2都是static的,是属于类的,所以A和B两个不同的对象用的却是同一把锁

    释放锁

    释放锁的操作

    在这里插入图片描述

    不会释放锁的操作

    在这里插入图片描述

  • 相关阅读:
    RSAUtil 前端 JavaScript JSEncrypt 实现 RSA (长文本)加密解密
    分析级羟甲基壬市场现状及未来发展趋势分析
    java开发IP 属地功能
    UNet详细解读(二)pytorch从头开始搭建UNet
    ​EtherNet/IP 库卡机器人和EtherCAT倍福PLC总线协议连接案例​
    java毕业设计——基于java+Jsp+Mysql的机场航班起降与协调管理系统设计与实现——机场航班起降与协调管理系统
    C - Minimize The Integer
    MATLAB程序设计与应用 3.2 矩阵变换
    不允许你还不会OSS文件操作,看完一篇就够了
    新型移动互联技术要点
  • 原文地址:https://blog.csdn.net/y_k_j_c/article/details/126216393