• 多线程adsfasdfasdf


    1.多线程三组概念

    1.1 进程&线程

    进程:
    一个正在执行的程序,又自己独立的资源分配,是一个独立的资源分配单位。

    线程:
    一个进程中的一条独立的执行路径(一个栈),每一个线程都可以独立的完成自己的任务,没有说明依赖关系,不会互相有影响,可以单独执行。

    进程和线程的关系:

    1. 进程是用来分配资源的单位
    2. 一个进程至少有一个线程(一个or多个)
    3. 线程不会独立分配资源,同一个进程里面的线程是共享的这个进程的资源

    并行和并发:

    1. 并行:
      多个进程or线程同时进行,也就是:在某一个时刻,有多个任务在进行,这时必然有多个CPU,属于多核编程
    2. 并发
      多个进程or线程同时发起,但是没有同时进行,此时只有一个CPU在不同的任务之间进行来回的切换,也就是单核编程,因为计算机的速度快,所以人们会觉得在同时进行,但实际是没有的。

    1.2线程的创建

    1.2.1 继承Thread类

    步骤:

    1. 创建一个extends继承了Thread类的线程类,并且要重写run()方法
    2. 在main方法中去实例化一个线程对象
    3. 使用实例化的线程对象去调用 start()方法,线程就进入了就绪状态
    4. start过后,程序会去自动执行线程类里面的run方法
      创建案例:
    package com.xiaowang.thread;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    /*
    * 1.先实例化一个分支线程对象(这个类对象是必须要继承了Thread类的哦)
    * 2.调用start方法去启动分支线程
    * 注意:
    * 1.start的作用是启动一个分支线程,jvm就会开辟一个新的栈空间,这段代码启动了后,就结束了,然后线程就启动了
    * 2.启动过后,会自动调用run方法
    * */
    public class Test {
        public static void main(String[] args) {
            MyThread myThread = new MyThread();
            //Thread thread3 = new Thread("t1");//实例化一个线程对象的时候,可以直接通过构造器去设置线程对象的名字
            myThread.start();//直接启动
            //myThread.run();
            for (int i = 0; i <1000 ; i++) {
                System.out.println("主线程---->"+i);
            }
        }
    }
    /*
     * 类继承Thread,完成run的重写,
     * 注意:多线程和run要配套使用
     * */
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i <1000 ; i++) {
                System.out.println("分支线程---->"+i);
            }
        }
    }
    
    
    • 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

    注意:一道经典面试题:
    启动一个线程的时候,使用start()不直接调用run()方法的原因:

    解释:
    当一个线程对象去调用start()方法的时候,线程就进入了就绪状态,这时只要有cpu来了,就可以直接进入运行状态,就会去执行相应的操作,就会自动执行到run()方法;而直接调用run()方法,并没有构成多线程状态,会默认觉得是在执行main方法里面的一个普通方法

    通俗理解:
    使用start方法,能个构成多线程,直接调用run不会构成多线程

    1.2.2 实现Runnable接口

    使用Runnable接口创建多线程步骤:

    • 写一个实现了Runnable接口的类
    • 2.在main方法中去实例化一个实例化了Runnable类的对象
    • 3.然后调用start方法去启动线程
    package com.xiaowang.runnable;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    /*
    * 接口实现类完成多线程
    * 1.写一个实现了Runnable接口的类
    * 2.在main方法中去实例化一个实例化了Runnable类的对象
    * 3.然后调用start方法去启动线程
    * */
    public class Test {
        public static void main(String[] args) {
            //1.先实例化一个实现接口Runnable的对象
            MyRunnable myRunnable = new MyRunnable();
            //2.实例化一个Thread对象,传入参数为实例化的Runnable的对象
            Thread thread = new Thread(myRunnable);
            //一句话实现
            Thread thread1 = new Thread(new MyRunnable());
            //3.启动线程
            thread.start();
            for (int i = 0; i <1000 ; i++) {
                System.out.println("分支线程---->"+i);
            }
        }
    }
    class MyRunnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i <1000 ; i++) {
                System.out.println("分支线程---->"+i);
            }
        }
    }
    
    
    • 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

    1.2.3 匿名内部类实现

    匿名内部类去创建一个线程,其实主要感觉还是通过实现了Runnable接口去完成的,因为匿名内部类里面的对象就是一个实例化的Runnable对象。

    案例代码:

    package com.xiaowang.niming;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    /*
    * 通过匿名内部类去创建一个线程对象
    * 解释:
    * 因为一个Thread线程对象在创建的时候,有一个有参的构造器:
    *  public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);
        }
    * 所以我们可以直接通过传入一个Runnable接口的实例化匿名内部类对象去创建
    * */
    public class Test {
        public static void main(String[] args) {
            Thread thread=new Thread(new Runnable() {//通过匿名内部类去创建一个线程对象
                @Override
                public void run() {
                    for (int i = 0; i <100 ; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                    }     
                }
            });
            //启动线程
            thread.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

    在这里插入图片描述

    1.3 线程的生命周期

    1.3.1 线程状态

    • 新建态----->(刚刚创建好一个对象的时候,也就是new的时候没有调用start

    • 就绪态----->(也叫做可运行状态,也就是调用start过后,这时候线程已经拥有了抢夺cpu的权利,当抢夺到了cpu过后就可以进行运行了,就可以执行run方法了)

    • 运行态----->(cpu正在执行线程的状态,当抢夺的资源cpu用完后,又会让线程返回到就绪态,再去进行抢夺,一直进行。当遇到阻塞的时候就会进行阻塞状态

    • 阻塞态----->(线程主动休息sleep方法、或者缺少一些运行的资源的时候,这时候不能再进行cpu的抢夺,除非睡醒了

    • 死亡态----->(线程运行完成、出现异常、调用方法结束,)

    1.3.2 线程转换图

    在这里插入图片描述

    1.3.3 线程所处状态

    获取线程当前状态:
    线程名 . getState()

    描述线程状态的类型:
    在这里插入图片描述
    线程在生命周期中并不是固定处于某一个状态的,而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示:
    在这里插入图片描述

    1. 线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。

    2. 当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,

    3. TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED_WAITING 状态。

    4. 当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。

    5. 当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。

    6. 线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。

    1.4 线程的方法

    1.4.1常用方法

    1. 获取当前线程对象
      thread.currentThread()

    2. 获得线程对象名字
      thread.getname():当还没有设置名字的时候,去调用方法获取得到的名字是:Thread–>0,Thread–>1…Thread–>n

    3. 修改设置线程名字
      thread.setname()
      也可以在实例化对象的时候,通过构造器去设置名字
      在这里插入图片描述

    三者案例:

    package com.xiaowang.method;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    /*
    * 1.获得当前线程对象:thread.currentThread()
    * 2.获得当前线程名:thread.getname()
    * 3.设置当前线程的名:thread.setname()
    * 4.休眠:thread.sleep() 注意:在哪个线程调用,就让哪个线程休眠
    * */
    public class Test {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = Thread.currentThread();//获取当前线程对象
            System.out.println("主方法里面的线程名字叫:"+thread.getName());//调用获取线程对象名的方法
    
            Thread thread1 = new MyThread();//实例化一个线程对象
            thread1.setName("自定义线程对象名t1");
            //Thread thread3 = new Thread("t1");//实例化一个线程对象的时候,可以直接通过构造器去设置线程对象的名字
            System.out.println(thread1.getName());
            Thread thread2 = new MyThread();
            thread2.setName("自定义线程对象名t2");
            System.out.println(thread2.getName());
            //启动两个分支线程
            thread1.start();
            thread2.start();
        }
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
                Thread thread = Thread.currentThread();//获取当前线程对象
                System.out.println(thread.getName()+"-->"+i);
            }
        }
    }
    
    • 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

    1.4.2 休眠中止休眠

    1. 线程休眠状态
      thread.sleep():
    • 里面传的是毫秒为单位的时间数
    • 哪个线程里面调用sleep,就休眠哪一个线程
    1. 中断线程休眠状态
      thread.interrupt():
    • 利用了Java的异常处理机制

    代码案例:

    package com.xiaowang.method;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    /*
    * 1.休眠:thread.sleep(),
    * 注意:
    * * 里面传的是毫秒为单位的时间数
    * * 哪个线程里面调用sleep,就休眠哪一个线程
    * 2.中止休眠:thread.interrupt()
    * */
    public class Test2 {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.setName("t1");
            System.out.println(thread.getName());
            thread.start();
            thread.interrupt();//中止休眠
        }
    }
    class MyRunnable implements Runnable{
        @Override
        public void run() {
                try {
                    Thread.sleep(1000*5);//进行休眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"--->begin");
        }
    }
    
    • 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

    在这里插入图片描述

    1.4.3 守护线程

    意义:
    守护线程主要就是去守护其他线程可以正常运行为其他的核心线程准备良好的运行环境;如果其他线程全部都死亡了,那么守护线程就没有任何作用了,一段时间过后,虚拟机就会一同结束。

    典型守护线程:垃圾回收机制(gc)

    package com.xiaowang.protect;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    public class Test {
        public static void main(String[] args) {
            //1.创建一个核心线程
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 2; i++) {
                        System.out.println(Thread.currentThread().getName()+"在活动");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
    
                }
            },"核心线程");
            //2.创建一个守护线程
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    int i = 0;
                    while (true){
                        System.out.println("守护线程--->"+(++i));
                        try {
                            Thread.sleep(1000);//使其休息一秒,便于观察,防止主线程在最后执行的时候,守护线程那份在干活高抢占了资源也会输出
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }, "守护线程");
            thread1.setDaemon(true);//将thread1设置成守护线程
           // thread1.setDaemon(false);//flag为false的话,那就不会被设置成守护线程
            //thread1.isDaemon();//判断是否是一个守护线程
            thread.start();
            thread1.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
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    输出:
    在这里插入图片描述

    注意:因为守护线程主要是去守护其他线程的,并且守护线程的run方法是为死循环的

    设置守护线程:
    线程名 . setDaemon(Boolean flag)

    每一个线程默认不是守护线程,只有当flag为true的时候,才会将这个线程设置成功,要是为false就不会成守护线程

    thread1.setDaemon(true);//将thread1设置成守护线程
    thread1.setDaemon(false);//flag为false的话,那就不会被设置成守护线程
    
    • 1
    • 2

    判断守护线程:
    thread . isDaemon()

    thread1.isDaemon();//判断是否是一个守护线程,是的话输出就是true
    
    • 1

    1.4.4 终止线程

    1. stop()方法:过时了
    package com.xiaowang.method;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 17:01
     */
    public class StopThread {
        public static void main(String[] args) {
            //创建线程
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i <100 ; i++) {
                        System.out.println(Thread.currentThread().getName()+"-->"+i);
                    }
                }
            });
            //开启线程
            thread.start();
            //终止线程
            thread.stop();//这种方法过时了,强制终止线程,会导致数据丢失,
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    注意:stop()方法过时了,强制终止线程,会导致数据丢失,

    1. 使用一个boolean型的变量去控制
    package com.xiaowang.method;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 17:01
     */
    public class StopThread {
        public static void main(String[] args) {
            MyRunnable1 myRunnable1 = new MyRunnable1();//创建一个MyRunnable1,有值接受,主要是为了用来去设置里面的Boolean值去控制终不终止程序
            Thread thread1 = new Thread(myRunnable1);
            thread1.start();
            myRunnable1.flag=false;//设置为false,这样线程里面的语句就不会执行了
        }
    }
    class MyRunnable1 implements Runnable{
        boolean flag = true;//定义一个Boolean变量去定义执不执行代码
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
                if (flag){//
                    System.out.println(Thread.currentThread().getName()+"-->"+i);
                }else {
                    return;
                }
            }
        }
    }
    
    • 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

    注意:
    这种方法的好处就是,通过控制线程是否执行去终止线程,不会导致数据丢失

    1.4.5 线程让位

    1. 解释:
      暂停当前正在执行的对象,并让给其他线程去执行(从运行态转换到就绪态)

    2. 方法:
      Thread . yield()

    案例:

    package com.xiaowang.method;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 17:24
     */
    /**/
    public class RangWei {
        public static void main(String[] args) {
            Thread myThread1 = new MyThread1();
            myThread1.setName("线程1");
            myThread1.start();
            //main方法的线程的执行语句
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    class MyThread1 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
                if (i%10==0){//每十个让位一次
                    Thread.yield();//当前线程暂停一下,让给主方法去执行
                }
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    
    • 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

    注意:不一定真的达到十个才让出去,最后还是要看线程之间抢夺cpu的能力,但是支线线程每十个还是要给main线程让位置

    1.4.6 线程合并

    1. 概念:
      通过线程的合并,我们可以制定线程的执行顺序,其实本质就是让后面执行的线程受到阻塞,当前面的执行完后,再去执行后面的。

    2.方法:
    线程对象名 . jion()

    1. 案例”
      要求:三个线程,要求先输出线程1,其次线程2,最后main
    package com.xiaowang.method;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 17:48
     */
    //先执行线程1  然后线程2  然后main
    public class HeBing{
        public static void main(String[] args) {
            //创建两个支线线程
            Thread thread1 = new Thread(new MyRunnable3());
            Thread thread2 = new Thread(new MyRunnable4());
            //设置两个支线线程的名字,便于观察输出
            thread1.setName("线程1");
            thread2.setName("线程2");
            thread1.start();//开启线程1
            try {
                thread1.join();//将线程1个合并到main线程中,当前的main线程就会收到阻塞,线程1就回去执行,线程1执行完后,main线程继续执行
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            thread2.start();//开启线程2,线程2在线程1后面开始执行,就可以保证线程2在线程1后面执行,同理线程2 执行完了就再执行main
            try {
                thread2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    class MyRunnable3 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    class MyRunnable4 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i <100 ; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    
    • 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.5 线程优先级

    概念:
    在多个线程在操作的时候,是看他们自己的抢占资源的能力去执行对应的线程,我们可以通过设置线程的优先级,通过线程的优先级可以控制线程的先后执行情况和数量。

    理论:
    优先级高的先运行(在前面的时间段内,高优先级的线程会多运行一些),优先级低的后执行(在后面的时间段中,优先级低的会多执行一些),所以不是说优先级高的会先执行完了再执行优先级低的,

    设置方法:
    线程名 . setPriority(int p
    通过 p 去给线程定优先级大小,数字越大优先级越高最大为10,最小为1,默认是5;

    获取线程的优先级:
    线程名 . getPriority()

    优先级常量:
    MAX_PRIORITY 值为10
    NORM_PRIORITY 值为5
    MIN_PRIORITY 值为1

    案例代码:

    package com.xiaowang.youxianji;
    
    /**
     * @Author 小王
     * @DATE: 2022/8/5
     */
    public class Test {
        public static void main(String[] args) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i <500 ; i++) {
                        System.out.println("我是高优先级");
                    }
                }
            });
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i <500 ; i++) {
                        System.out.println("我是低优先级------");
                    }
                }
            });
            thread.setPriority(10);//将线程1的优先级设置最高
            thread1.setPriority(1);//将线程2的优先级设置最低
            thread.start();
            thread1.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

    1.6 线程安全

    1.6.1 线程安全小讲解

    线程不安全条件:

    1. 多线程的并发
    2. 有共享数据
    3. 有共享数据且有数据修改的行为

    异步:

    • 线程1和线程2,各自执行自己的,两者互不干扰,也不存在等待行为,效率高但是不安全

    同步:

    • 线程1和线程2,在线程1执行的时候,线程2必须等待线程2执行结束再执行,反之一样,两者之间产生了等待关系,也就是线程排队执行

    线程安全案例:
    两个不同的线程,对同一个对象进行操作,这里用两个线程对同一个银行账户进行取钱操作为例:

    • 会出现的问题:当线程1取好了,但是还没有更新余额,线程2就开始取了,这时账户余额还是1000,所以就会导致两次取钱过后,还剩500的情况
    package com.xiaowang.bank;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 19:14
     */
    //银行实体类,使用两个线程,对同一个银行账户进行取钱的操作
    public class Account {
        private String id;//账号
        private double balance;//余额
    
        public Account() {
        }
    
        public Account(String id, double balance) {
            this.id = id;
            this.balance = balance;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
        //取钱的方法
        public void getMoney(double money){
            double before = this.balance;//目前余额
            double after = before - money;//取钱后的余额
            try {
                Thread.sleep(1000);//让其睡会觉,便于看出来账户问题
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //会出现的问题:当线程1取好了,但是还没有更新余额,线程2就开始取了,这时账户余额还是1000,所以就会导致两次取钱过后,还剩500的情况
            //解决方法1: 同步代码块操作:
    
            this.balance=after;//更新余额
        }
    }
    
    
    • 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
    package com.xiaowang.bank;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 19:24
     */
    public class MyRunnable implements Runnable{
        //为了保证是对同一个对象做操作,通过一个构造器来进行限制
        private Account account;
    
        public MyRunnable(Account account) {
            this.account = account;
        }
    
        @Override
        public void run() {
            double money = 500;//每次取500块
            account.getMoney(money);
            System.out.println("账户id:"+account.getId()+"余额:"+account.getBalance());
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    package com.xiaowang.bank;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 19:42
     */
    public class Test {
        public static void main(String[] args) {
            //创建一个账户对象,确保是对同一个对象进行操作
            Account account = new Account("123", 1000);
            //创建来给你个线程
            Thread thread1 = new Thread(new MyRunnable(account));
            Thread thread2 = new Thread(new MyRunnable(account));
          thread1.setName("线程1");
          thread2.setName("线程2");
    
          thread1.start();
          thread2.start();
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    测试结果:
    在这里插入图片描述
    注意:

    • 这就是线程不安全造成的,按道理一共余额1000,取两次500就应该余额为0了,可是由于线程不安全的原因就导致了还有500余额

    线程安全解决方法:

    1. 同步代码块:
    就是将要同步执行的代码用synchronized去包裹起来
    在这里插入图片描述
    注意:代码块括号里面的参数:必须是多线程共享的数据,这样才能达到排队的效果

    2. 实例方法上使用synchronized
    表示共享对象一定是this,并且同步代码块是一个整体,也就是这个方法在那个类中,指的就是那个类

    在这里插入图片描述
    注意:一个对象 一把锁

    3. 静态方法上使用synchronized
    同步的是类的字节码对象,那个类调用这个方法,就指那个类
    在这里插入图片描述

    注意:static 是类锁 不管多少对象,只有一把锁

    1.6.2 线程安全练习题

    A01:判断下列 t1 对象的doOther方法要不要等 t2 对象的doSome方法

    package com.xiaowang.synchronizedTest;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 20:10
     */
    public class Test1 {
    //面试题 doOther方法执行的时候,需要等待doSome方法的结束吗
    
    //不需要 因为doOther方法没有synchronized,没有锁就直接执行啊
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            Thread t1 = new MyThread(mc);//t1线程t2线程共享这个对象mc
            Thread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    /*
    
        }
    }
    
    class  MyThread extends  Thread{
    
        private MyClass mc;
        //这儿有也给构造方法,可以传myclass的参数过来
        public MyThread(MyClass mc) {
            this.mc = mc;
        }
    
        public  void run(){
            if (Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if (Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    class MyClass{
        //synchronized出现在实例方法上,锁this 就是锁了mc
        public synchronized void doSome(){//doSome方法线程安全化
            System.out.println("ds begin");
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ds over");
        }
        public void doOther(){
            System.out.println("do begin");
            System.out.println("do over");
        }
    
    }
    
    • 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
    • 64
    • 65

    输出:
    在这里插入图片描述
    分析:

    • t2的doOther不会等待t1的doSome方法,因为doOther方法并没有安全化,相当于只是MyClass类的一个普通方法而已

    A02:判断下列 t1 对象的doOther方法要不要等 t2 对象的doSome方法

    package com.xiaowang.synchronizedTest;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 20:20
     */
    public class Test2 {
    
    //面试题 doother方法执行的时候,需要等待dosome方法的结束吗
    
    //需要, 因为mc对象是不是一个,需要等 ds结束;
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            Thread t1 = new MyThread(mc);//t1线程t2线程共享这个对象mc
            Thread t2 = new MyThread(mc);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    
    class  MyThread extends  Thread{
    
        private MyClass mc;
    
        public MyThread(MyClass mc) {
            this.mc = mc;
        }
    
        public  void run(){
            if (Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if (Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    class MyClass{
        //synchronized出现在实例方法上,锁this 就是锁了mc
        public synchronized  void doSome(){
            System.out.println("ds begin");
    
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ds over");
    
        }
        public synchronized void doOther(){
    
            System.out.println("do begin");
            System.out.println("do over");
    
        }
    
    }
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    输出:
    在这里插入图片描述
    分析:
    会等待:因为doSome和doOther方法都用synchronized去修饰了,证明都是线程安全的,将实例方法安全化,实际是将类给上锁了,当doSome先将类上锁后,执行到doOther的时候,也会去上锁类,但是这时发现类已经被上锁了,呢么直接用那个类锁就可以了,就不会另外开个锁,并且调用的是同一个对象

    A03:
    判断下列 t1 对象的doOther方法要不要等 t2 对象的doSome方法

    package com.xiaowang.synchronizedTest;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 20:25
     */
    public class Test3 {
    //面试题 doother方法执行的时候,需要等待dosome方法的结束吗
    //不需要的 mc的对象是两个 两个对象两把锁,t1拿走的是mc的对象锁,t2拿走的是mc1的对象锁,线程没有共享对象
    
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            MyClass mc1 = new MyClass();
            Thread t1 = new MyThread(mc);//t1线程t2线程共享这个对象mc
            Thread t2 = new MyThread(mc1);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    
    class  MyThread extends  Thread{
    
        private MyClass mc;
    
        public MyThread(MyClass mc) {
            this.mc = mc;
        }
    
        public  void run(){
            if (Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if (Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    class MyClass{
        //synchronized出现在实例方法上,锁this 就是锁了mc
        public synchronized  void doSome(){
            System.out.println("ds begin");
    
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ds over");
    
        }
        public synchronized void doOther(){
    
            System.out.println("do begin");
            System.out.println("do over");
    
        }
    
    }
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    输出:
    在这里插入图片描述

    分析:
    不会等,因为这时的线程是两个不同的类对象,并且还是普通的实例方法,所以doSome和doOther是对不同的对象上的锁,两者并不会相互影响

    A04:
    判断下列 t1 对象的doOther方法要不要等 t2 对象的doSome方法

    package com.xiaowang.synchronizedTest;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/8 20:28
     */
    public class Test4 {
    //面试题 doother方法执行的时候,需要等待dosome方法的结束吗
    //需要,静态方法是类锁,不管你的这个类创建了多少个对象,类锁只有一把啊,一百个对象还是只有一把锁
        public static void main(String[] args) {
            MyClass mc = new MyClass();
            MyClass mc1 = new MyClass();
            Thread t1 = new MyThread(mc);//t1线程t2线程共享这个对象mc
            Thread t2 = new MyThread(mc1);
    
            t1.setName("t1");
            t2.setName("t2");
    
            t1.start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t2.start();
    
        }
    }
    
    class  MyThread extends  Thread{
    
        private MyClass mc;
    
        public MyThread(MyClass mc) {
            this.mc = mc;
        }
    
        public  void run(){
            if (Thread.currentThread().getName().equals("t1")){
                mc.doSome();
            }
            if (Thread.currentThread().getName().equals("t2")){
                mc.doOther();
            }
        }
    }
    
    class MyClass{
    
        public synchronized static void doSome(){
            System.out.println("ds begin");
    
            try {
                Thread.sleep(1000*10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("ds over");
    
        }
        public synchronized static void doOther(){
    
            System.out.println("do begin");
            System.out.println("do over");
    
        }
    
    }
    
    • 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
    • 64
    • 65
    • 66
    • 67
    • 68

    输出:
    在这里插入图片描述
    分析:
    虽然是不同的类对象,但是上锁的方法是static静态的,所以这时上锁的是类的字节码对象,就相当于static是在类加载的时候就去加载方法的,是其实本质还是同一对象,也就是:静态方法是类锁,不管你的这个类创建了多少个对象,类锁只有一把啊,一百个对象还是只有一把锁

    1.6.3 线程死锁

    1. 概念:
      就是两个不同的线程,对两个相同的对象进行操作,线程1从上到下去锁对象,线程2从下到上去锁对象,就会造成死锁。

    2. 死锁现象:
      这样就会造成两个线程冲突,就进行不下去,程序就一直运行,不会结束

    package com.xiaowang.sisuo;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/9 9:52
     */
    public class Test {
        public static void main(String[] args) {
            Object o1 = new Object();
            Object o2 = new Object();
            Thread thread1 = new Thread1(o1, o2);
            Thread thread2 = new Thread2(o1, o2);
            thread1.start();
            thread2.start();
        }
    }
    class Thread1 extends Thread{
        Object o1;//定义连个对象
        Object o2;
    
        public Thread1(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }
    
        @Override
        public void run() {
            synchronized (o1){//先对o1上锁
                try {
                    Thread.sleep(1000);//睡觉是为了能狗更好的体现出死锁
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o2){//再对o2上锁
    
                }
            }
        }
    }
    class Thread2 extends Thread{
        Object o1;
        Object o2;
    
        public Thread2(Object o1, Object o2) {
            this.o1 = o1;
            this.o2 = o2;
        }
        @Override
        public void run() {
            synchronized (o2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (o1){
    
                }
            }
        }
    }
    
    • 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
    1. 避免死锁:
      static
      不同时运行

    1.7 定时器

    1. 作用:
      间隔特定时间,执行特定程序

    2. 方法:

    • sleep():利用睡眠去达到效果
    • java.util.timer 包的使用
    • Spring框架中提供了Spring Task框架,简单配置即可
    1. timer案例:
      每5秒执行一次:小王唱完了一首歌
    package com.xiaowang.timer_;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Timer;
    import java.util.TimerTask;
    
    
    /**
     * @Author 小王吖
     * @Date 2022/8/9 10:38
     */
    //通过java.util.Timer包去完成
    public class Test {
        public static void main(String[] args) throws ParseException {
            //实例化一个Timer对象
            Timer timer = new Timer();
            //设置时间格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //设置开始时间
                Date firstTime = sdf.parse("2022-08-09 10:50:00");
            //通过timer类对象去调用定时器方法:schedule(定时任务对象,开始时间,间隔多久执行一次)
            timer.schedule(new MyThread(),firstTime,1000*5);//五秒一次
        }
    }
    class MyThread extends TimerTask {//因为TimerTask实现了Runnable接口,所以这里还是一个线程
        //之所以通过继承TimerTask去创建对象,是为了实现timer包,完成定时器的操作,TimerTask:主要是任务的具体内容
        @Override
        public void run() {
            //要执行的任务,显示在什么时间做了一次什么操作
            //设置时间的格式
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //获取时间,并且按照指定格式去接收
            String format = sdf.format(new Date());
            System.out.println(format+":小王唱完了一首歌");
        }
    }
    
    
    • 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

    输出:
    在这里插入图片描述

    1. 注意:
    • 线程是去继承TimerTask 完成的:因为TimerTask实现了Runnable接口,所以这里还是一个线程
    • TimerTask :这个类对象主要就是存放:定时器的具体任务
    • schedule()方法:schedule(定时任务对象,开始时间,间隔多久执行一次),从当前什么时候开始执行任务,经历了设置的间隔时间后,再一次去执行。

    1.8 生产者消费者

    解析:也就是当商品达到

    package com.xiaowang.producepv;
    
    import jdk.nashorn.internal.ir.CallNode;
    
    import javax.swing.*;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * @Author 小王吖
     * @Date 2022/8/9 16:23
     */
    public class Test {
        public static void main(String[] args) {
            List<Object> obj = new ArrayList<>();
            obj.add("商品1");
            obj.add("商品2");
            obj.add("商品3");
            obj.add("商品4");
            obj.add("商品5");
            ProduceThread produceThread = new ProduceThread(obj);
            ConsumeThread consumeThread = new ConsumeThread(obj);
            produceThread.setName("生产者");
            consumeThread.setName("消费者");
            consumeThread.start();
            produceThread.start ();
        }
    }
    class ProduceThread extends Thread{
        private List list;
    
        public ProduceThread(List list) {
            this.list = list;
        }
    
        @Override
        public void run() {//设置成线程安全才能保证生产者消费者线程互不影响,不会抢占cpu
            int size = list.size();
            while (true){
            synchronized (list) {
                if (list.size() <= 1) {//当只有一个的时候就开始生产
                    for (int i = list.size(); i <= 10; i++) {//最多十个,遍历集合
                        Object o = new Object();
                        list.add(o);
                        System.out.println("商品" + i + "正在生产~~~~");
                    }
                    System.out.println("====生产好了可以用了====");
                    list.notify();
                } //多于一个就不用再生产,直接消费即可,所以让其生产线程去处于等待状态,
                    try {
                        list.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
    class ConsumeThread extends Thread{
        private List list;
    
        public ConsumeThread(List list) {
            this.list = list;
        }
        @Override
        public void run() {
            int size = list.size();
            while (true){
            synchronized (list) {
                if (list.size() > 1) {//当不止一个的时候就进行消费
                    for (int i = list.size() - 1; i >= 1; i--) {//最多十个,遍历集合
                        list.remove(i);
                        System.out.println("商品" + i + "正在被消费");
                    }
                    System.out.println("=====不够了麻烦再生产一点来=====");
                    list.notify();
                } //少于一个就不能再生产了,直接提示该生产了,所以让其消费线程去处于等待状态,
                    try {
                        list.wait();
                    } 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
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
  • 相关阅读:
    【Latex】错误类型总结(持更)
    CAPL实现CRC8的几种方式
    BCG ribbon在对话框中使用
    【C++项目】高并发内存池第三讲PageCache框架涉及+核心实现(上)
    Linux操作系统使用及C高级编程-D11-D13结构体
    LeetCode54螺旋矩阵、59螺旋矩阵II【灵活】
    快速教程|如何在 AWS EC2上使用 Walrus 部署 GitLab
    【RocketMQ系列一】初识RocketMQ
    若依RuoYi-Vue分离版—PageHelper分页的坑
    常见的限流算法与实现
  • 原文地址:https://blog.csdn.net/Sunblogy/article/details/126172291