• Java之线程详解(三)——多线程常用API、七种状态、优先级、Lock锁


    一、多线程常用API

    1. join() 方法

    多线程环境下,如果需要确保某一线程执行完毕后才可继续执行后续的代码,就可以通过使用 join 方法完成这一需求设计。

    eg:

     public class Thread01 {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> System.out.println(Thread.currentThread().getName()+",线程执行"), "t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        t1.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",线程执行");
                }
            }, "t2");
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        t2.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",线程执行");
                }
            }, "t3");
            t1.start();
            t2.start();
            t3.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

    除了无参的 join 方法以外, Thread 类还提供了有参 join 方法,public final synchronized void join(long millis)。
    该方法的参数 long millis 代表的是毫秒时间。
    方法作用:等待 millis 毫秒终止线程,假如这段时间内该线程还没执行完,也不会再继续等待。

    1. sleep()方法

    sleep()可以让当前正在执行的线程进入休眠(暂时停止执行)指定的毫秒数,即当前线程会从“运行状态”进入到“超时等待状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“超时等待状态”变成“就绪状态”,从而等待cpu的调度执行。

    在服务器中,死循环while(true)方法会导致服务器死循环一直运行着,如果没有添加sleep()方法那么下面的代码会直接占用100%的cpu,导致别的应用没有执行权。这种情况叫做CPU空转如果加了sleep(),cpu的占用率会明显减低。

    eg:

    while(true){
     try {
           Thread.sleep(20);
          } catch (InterruptedException e) {
           e.printStackTrace();
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    1. yield() 方法

    yield():主动释放cpu执行权。

    eg:

    public class Thread07 extends Thread{
        public Thread07(String name){
            super(name);
        }
    
        @Override
        public void run() {
            for (int i=0; i<50; i++){
                if(i == 30){
                    System.out.println(Thread.currentThread().getName()+",释放cpu执行权");
                    this.yield();//主动释放cpu执行权
                }
                System.out.println(Thread.currentThread().getName()+","+i);
            }
        }
    
        public static void main(String[] args) {
            new Thread07("t1").start();
            new Thread07("t2").start();
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 守护线程与用户线程

    (1)守护线程:是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT 线程都是守护线程。
    (2)用户线程:可以理解为是系统的工作线程,它会完成这个程序需要完成的业务操作。如我们使用 Thread 创建的线程在默认情况下都属于用户线程。

    通过 Thread.setDaemon(false) 方法可以设置为用户线程;
    通过 Thread.setDaemon(true) 方法可以设置为守护线程;
    如果不设置线程的属性,那么默认为用户线程,线程属性的设置要在线程启动之前。

    守护线程是为用户线程服务的,当一个程序中的所有用户线程都执行完成之后程序就会结束运行,程序结束运行时不会管守护线程是否正在运行。
    用户线程是独立存在的,不会因为其他用户线程退出而退出。

    用户线程:
    当主线程执行完毕后,用户线程还会继续执行。

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println("我是用户线程......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();
    
        Thread.sleep(3000);
        System.out.println("主线程执行完毕......");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    守护线程:
    当主线程执行完毕后,用户线程不会继续执行。

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    System.out.println("我是用户线程......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 设置为守护线程
        thread.setDaemon(true);
        thread.start();
    
        Thread.sleep(3000);
        System.out.println("主线程执行完毕......");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    1. 如果停止一个线程

    (1)可以使用Thread.stop()方法,但不推荐使用此方法,虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且是被弃用作废的。因为Thread.stop()方法会直接把线程停止,这样就没有给线程足够的时间来处理想要在停止前保存数据的逻辑,任务戛然而止,会导致出现数据完整性等问题。
    (2)停止一个线程还可以使用Thread.interrupt()方法,但这个方法不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
    (3)使用退出标志使线程正常退出。

    eg:

    Thread.interrupt()方法停止:

    public class Thread05 {
        static class MyThread extends Thread {
            public void run() {
                try {
                    while (true) {
                        if (isInterrupted()) {
                            throw new InterruptedException();
                        }
                    }
                } catch (InterruptedException e) {
                    System.out.println("由于异常退出线程");
                    e.printStackTrace();
                } finally {
                    System.out.println("线程的finally");
                }
            }
        }
    
        public static void main(String[] args) {
            try {
                MyThread thread = new MyThread();
                thread.start();
                Thread.sleep(2000);
                thread.interrupt();
                while(true){   //主线程将一直运行
                }
            } catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    
    • 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

    使用退出标志使线程正常退出:

    public class Thread06 implements Runnable{
        private static volatile boolean canceled = false;
    
        @Override
        public void run() {
            int i = 0;
            while (!canceled&&i<=1000){
                i++;
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" "+i+" 线程执行中...");
            }
            System.out.println(Thread.currentThread().getName()+" 线程关闭...");
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Thread06());
            thread.start();
            Thread.sleep(10);
            canceled = true;
        }
    }
    
    • 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
    1. join()、wait()、sleep()方法之间的区别

    Thread.sleep(long):

    sleep(long)是Thread的静态方法;
    使当前所在线程进入阻塞;
    只是让出CPU,并没有释放锁;
    由于睡眠时间结束后不一定立即被CPU调度,因此线程休眠的时间可能大于传入的参数;
    如果被终端则抛出InterruptedException。

    Object.wait():

    wait()是Ojbect的静态方法;
    让出CPU,释放对象锁;
    在调用前需要先拥有对象锁,所以一般在synchronized中同步块使用;
    使该线程进入该对象的监视器的等待队列;
    如果不指定等待时长,需要手动notify/notifyAll唤醒。

    join():

    join()是Thread的非静态方法;
    join等于synchronized+Object.wait();
    线程合并,调用线程会进入阻塞状态,需要等待被调用线程结束后才可以执行;
    应用场景:当一个线程必须等待其他线程执行完毕才能继续执行。

    二、多线程的七种状态

    1、初始(NEW):刚被创建,还没运行(未执行线程的start()方法)。
    2、就绪状态(READY):线程在可运行线程池中,但未获得CPU执行权,和RUNNING并称运行。
    3、运行中状态(RUNNING):线程执行并获得CPU执行权,和READY并称运行。
    4、阻塞(BLOCK):等待其他线程释放锁的状态。
    5、等待(WAITING):需要其他线程做出一些约定好的动作,或被唤醒(通知或中断)。
    6、超时等待(TIME_WAITING):和等待的不同点在于可以在指定的时间自行醒来。
    7、终止(TERMENATED):线程已经执行完毕。

    1、new Thread(),该线程为初始状态;
    2、调用start()方法会使得该线程开始执行,进入就绪状态;
    3、当CPU调度执行该线程,则该线程进入运行中状态;
    4、当run代码执行完毕,则该线程进入终止状态;
    5、当在运行状态时,调用yield()方法,则该线程暂停执行并释放CPU执行权,进入就绪状态;
    6、当在运行状态时,调用 wait()、join() 等方法,则该线程进入等待状态,并释放CPU执行权,当调用notify()、notifyAll() 等方法,重新进入就绪状态;
    7、当在运行状态时,执行时一直没有获取到锁,则该线程进入阻塞状态,获取到锁重新进入就绪状态;
    8、当在运行状态时,调用sleep(long)、wait(long)、join(long)等方法,则该线程进入超时等待状态,当调用notify()、notifyAll() 等方法,重新进入就绪状态。

    三、多线程的优先级

    线程优先级用数字表示,范围从1~10:
    Thread.MIN_PRIORITY = 1;
    Thread.MAX_PRIORITY = 10;
    Thread.NORM_PRIORITY = 5;
    默认为5

    设置优先级:setPriority(int)
    获取优先级:getPriority()

    public class Thread08 {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                int t1Count = 0;
                for (; ;) {
                    System.out.println(Thread.currentThread().getName() + "--" + t1Count++);
                }
            },"t1");
            Thread t2 = new Thread(() -> {
                int t2Count = 0;
                for (; ;) {
                    System.out.println(Thread.currentThread().getName() + "--" + t2Count++);
                }
            },"t2");
    
            t1.setPriority(Thread.MAX_PRIORITY);
            t2.setPriority(Thread.MIN_PRIORITY);
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    四、Lock锁

    1. 常用方法
    方法描述
    void lock()获得锁。如果锁不可用,则当前线程将被禁用以进行线程调度,并处于休眠状态,直到获取锁。
    void unlock()释放锁。
    void lockInterruptibly()获取锁,如果可用则立即返回。如果锁不可用,那么当前线程将被禁用以进行线程调度,并且处于休眠状态,和lock()方法不同的是在锁的获取中可以中断当前线程。
    boolean tryLock()只有在调用时才可以获得锁。如果可用,则获取锁,并立即返回值为true;如果锁不可用,则此方法将立即返回值为false 。
    boolean tryLock(long time, TimeUnit unit)超时获取锁,当前线程在以下三种情况下会返回: 1. 当前线程在超时时间内获得了锁;2.当前线程在超时时间内被中断;3.超时时间结束,返回false

    注意:

    如果采用Lock,必须主动去释放锁,在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
    tryLock()方法表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
    tryLock(long time, TimeUnit
    unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。

    eg:

    public class Lock01 implements Runnable{
        private  int count = 100;
        private final ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(30);
                    lock.lock();//开启锁
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + ",--" + count--);
                    }else{
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//关闭锁,一般都将unlock写入到finally块中
                }
            }
        }
    
    	public static void main(String[] args) {
            Lock01 lock01 = new Lock01();
            Thread t1 = new Thread(lock01,"a");
            Thread t2 = new Thread(lock01,"b");
            t1.start();
            t2.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. Condition接口
      Condition接口的常见方法:
      | 方法 | 描述 |
      |–|–|
      | void await() | 相当于Object类的wait方法 |
      | boolean await(long time, TimeUnit unit) | 相当于Object类的wait(long timeout)方法 |
      | signal() | 相当于Object类的notify方法 |
      | signalAll() | 相当于Object类的notifyAll方法 |

    eg:

    public class Lock01 implements Runnable{
        private  int count = 100;
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        @Override
        public void run() {
            while (true) {
                try {
                    lock.lock();//开启锁
                    condition.signal();//主动唤醒线程
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + ",--" + count--);
                        condition.await();//主动释放锁  同时当前线程变为阻塞状态
                    }else{
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();//关闭锁,一般都将unlock写入到finally块中
                }
            }
        }
    
        public static void main(String[] args) {
            Lock01 lock01 = new Lock01();
            Thread t1 = new Thread(lock01,"a");
            Thread t2 = new Thread(lock01,"b");
            t1.start();
            t2.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
  • 相关阅读:
    CS144 计算机网络 Lab2:TCP Receiver
    Angular-01:基本架构
    vue axios封装并发请求
    [附源码]Python计算机毕业设计Django甜品购物网站
    Zookeeper安装
    虾皮shopee获得店铺的所有商品 API 返回值说明
    短视频搭建矩阵源码--短视频矩阵源码搭建
    springboot+vue+nodejs的校园短期闲置资源置换系统java
    dubbo-admin 无法显示元数据
    数据结构与算法之二叉树、二叉搜索树、平衡二叉树、红黑树、B - 树、哈夫曼树等详细教程(更新中)
  • 原文地址:https://blog.csdn.net/qq_36216193/article/details/128139538