多线程基础知识(全面):创建线程、线程状态如何变化、wait()、notify()、sleep()、停止线程

文章目录

  • 一、创建线程的四种方式
    • 1.1 继承Thread类
    • 1.2 实现runnable接口
    • 1.3 实现Callable接口
    • 1.4 线程池创建线程
    • 1.5 补充:runnable、callable都可以创建线程,有什么区别;run()和 start()有什么区别
  • 二、线程包括哪些状态、状态之间如何变化
    • 2.1 线程包括哪些状态
    • 2.2 状态之间如何变化
    • 2.3 wait() 与 notify()
    • 2.4 wait()、wait(timeout)、sleep()、join()区别
  • 三、Thread常用方法
  • 四、线程基础知识 常见面试题
    • 4.1 新建T1、T2、T3 三个线程,如何保证它们按顺序执行
    • 4.2 notify() 和 notify() 有什么区别
    • 4.3 java中wait和sleep方法的不同
      • 4.3.1 共同点
      • 4.3.2 不同点
    • 4.4 如何停止一个正在运行的线程
      • 4.4.1 使用退出标志终止线程
      • 4.4 2 使用stop方法强行终止(不推荐)
      • 4.4.3 使用interrupt方法中断线程
  • 五、总结

在这里插入图片描述

一直想着抽时间 学习多线程相关知识,目前整理了多线程的基础部分,特在此记录下,并发安全、线程池等后续再补充。

一、创建线程的四种方式

共有四种方式可以创建线程,分别是:

  • 继承Thread类
  • 实现runnable接口
  • 实现Callable接口
  • 线程池创建线程

1.1 继承Thread类

步骤:

  • 创建一个继承于Thread类的子类
  • 重写Thread类的run(),将此线程要执行的操作声明在run()中
  • 创建Thread类的子类对象,即创建刚才继承Thread类的对象
  • 通过该对象调用start()方法启动线程

继承方式,不建议使用,因为Java是单继承的,继承Thread后便没办法继承其他类了,不够灵活。

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("MyThread run ...");
    }

    public static void main(String[] args) {
        //创建MyThread对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //调用start方法启动线程
        t1.start();
        t2.start();
    }

}

输出

MyThread run …
MyThread run …

1.2 实现runnable接口

步骤:

  • 创建一个类实现Runnable接口
  • 实现类重写run(),将此线程要执行的操作声明在run()中
  • 创建实现类的对象,将该对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 通过Thread对象调用start()方法启动线程
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("MyRunnable run ...");
    }

    public static void main(String[] args) {
        // 创建MyRunnable对象
        MyRunnable mr = new MyRunnable();

        //创建Thread对象
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        //调用start方法启动线程
        t1.start();
        t2.start();
    }
}

输出

MyRunnable run …
MyRunnable run …

1.3 实现Callable接口

步骤:

  • 创建一个实现Callable的实现类
  • 实现call方法,将此线程需要执行的操作声明在call()中
  • 创建Callable接口实现类的对象
  • 将此对象传递到FutureTask构造器中,创建FutureTask的对象
  • 将FutureTask的对象传递到Thread的构造器中,创建Thread对象并调用start()
  • 如果对返回值感兴趣 可以用该Object result = FutureTask的对象.get()
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName());
        return "ok";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable对象
        MyCallable mc = new MyCallable();
        //创建FutureTask
        FutureTask<String> ft = new FutureTask<>(mc);
        //创建Thread对象
        Thread t1 = new Thread(ft);
        Thread t2 = new Thread(ft);
        //调用start方法启动线程
        t1.start();
        //调用ft的get方法获取执行结果
        String result = ft.get();
        //输出
        System.out.println(result);
        
    }
}

输出

Thread-0
ok

1.4 线程池创建线程

步骤:

  • 创建一个类实现Runnable或Callable
  • 实现类中重写run()或call()方法
  • 提供指定的线程池
  • 创建实现类的对象
  • 执行指定的线程操作
  • 关闭线程
public class MyExecutors implements Runnable{

    @Override
    public void run() {
        System.out.println("MyExecutors run ...");
    }

    public static void main(String[] args) {
        //创建线程池对象
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        threadPool.submit(new MyExecutors());
        threadPool.submit(new MyExecutors());
        threadPool.submit(new MyExecutors());
        threadPool.submit(new MyExecutors());

        //关闭线程池
        threadPool.shutdown();
    }
}

输出

MyExecutors run …
MyExecutors run …
MyExecutors run …
MyExecutors run …

1.5 补充:runnable、callable都可以创建线程,有什么区别;run()和 start()有什么区别

runnable、callable都可以创建线程,有什么区别?

  • Runnable 接口run方法没有返回值
  • Callable接口call方法有返回值,是个泛型,和Future、FutureTask配合可以用来获取异步执行的结果
  • Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

在启动线程的时候,可以使用run方法吗?run()和 start()有什么区别?

  • start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次,线程之间无顺序,是按照CPU分配的时间片来回切换的
  • run():就是普通的方法调用。封装了要被线程执行的代码,可以被调用多次。具体要启动一个线程来运行线程体中的代码/run()方法 还是需要通过start()方法来实现,调用run()方法是一种顺序编程而非并发编程
public static void main(String[] args) throws Exception {

    new Thread(()-> {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {}
        }
    }, "Thread-A").start();

    new Thread(()-> {
        for (int j = 0; j < 5; j++) {
            System.out.println(Thread.currentThread().getName() + " " + j);
            try { 
                Thread.sleep(200); 
            } catch (InterruptedException e) { }
        }
    }, "Thread-B").start();
}

在这里插入图片描述

public static void main(String[] args) throws Exception {
    new Thread(()-> {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) { }
        }
    }, "Thread-A").run();

    new Thread(()-> {
        for (int j = 0; j < 5; j++) {
            System.out.println(Thread.currentThread().getName() + " " + j);
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) { }
        }
    }, "Thread-B").run();
}

执行结果都是main主线程

在这里插入图片描述

二、线程包括哪些状态、状态之间如何变化

2.1 线程包括哪些状态

线程的状态可以参考JDK中Thread类里面的枚举State

public enum State {
    //尚未启动的线程的线程状态    
    NEW,
    //可运行线程的线程状态  
    RUNNABLE,
    //线程阻塞等待监视器锁的线程状态。   
    BLOCKED,    
    //等待线程的线程状态  
    WAITING,
    //具有指定等待时间的等待线程的线程状态   
    TIMED_WAITING,
    //已终止线程的线程状态。线程已完成执行  
    TERMINATED;
}

新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待( WAITING )、时间等待(TIMED_WALTING)、终止(TERMINATED)

2.2 状态之间如何变化

在这里插入图片描述

线程状态之间是如何变化的

  • 创建线程对象是新建状态
  • 调用了start()方法转变为可执行状态
  • 线程获取到了CPU的执行权,执行结束是终止状态
  • 在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态
    • 如果没有获取锁(synchronized或lock) 进入阻塞状态,获得锁再切换为可执行状态
    • 如果线程调用了wait()方法 进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
    • 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态
public class StateTransition {
    private final static Logger LOG = LoggerFactory.getLogger(StateTransition.class);

    /*
    t1 running ...
    t2 running ...
     */
    private static void testNewRunnableTerminated() {
        Thread t1 = new Thread(() -> {
            System.out.println("t1 running ...");
        }, "t1");
        Thread t2 = new Thread(() -> {
            System.out.println("t2 running ...");
        }, "t2");

        t1.start();
        t2.start();
    }

    /*
    running ...
    before sync ...
    in sync ...
     */
    private static void testBlocked() {
        Thread t1 = new Thread(() -> {
            System.out.println("before sync ...");
            synchronized(StateTransition.class) {
                System.out.println("in sync ...");
            }
        }, "t1");

        t1.start();
        synchronized(StateTransition.class) {
            System.out.println("running ...");
        }
    }

    /*
1.synchronized (StateTransition.class)  报如下错。synchronized (st)  正常输出before waiting ...,且线程为停止
before waiting ...
Exception in thread "main" Exception in thread "t1" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.ywj.interview.thread.StateTransition.lambda$testWaiting$3(StateTransition.java:65)
	at java.lang.Thread.run(Thread.java:750)
java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.ywj.interview.thread.StateTransition.testWaiting(StateTransition.java:74)
	at com.ywj.interview.thread.StateTransition.main(StateTransition.java:98)

     */
    private static void testWaiting() {
        Object locker = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("before waiting ...");
                try {
                    locker.wait();
                } catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t1");

        t1.start();
        synchronized (locker) {
            locker.notify();
        }
    }

    /* 输出以下,且会正常退出
    running ...
     */
    private static void testTimedWaiting() {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("running ...");
        }, "t1");
        t1.start();
    }

    public static void main(String[] args) {
//        testNewRunnableTerminated();

//        testBlocked();

//        testWaiting();

        testTimedWaiting();
    }
}

2.3 wait() 与 notify()

wait() 可以理解为让某个线程先暂停下来等一等,notify() 则是把该线程唤醒、让它能够继续执行。wait()和notify()是Object的方法,只要是个类对象,都可以调用。

wait() 的作用、结束等待的条件与使用条件

wait 做的事情

  • 释放当前的锁。
  • 使当前执行代码的线程进入阻塞等待(把线程放到等待队列中)。
  • 满足一定条件时(收到通知时)被唤醒,同时重新尝试获取这个锁。

wait 结束等待的条件

  • 其他线程调用该对象的 notify 方法。
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本,来指定等待时间)。
  • 其他线程调用该等待线程的 interrupted 方法,导致 wait 抛出 InterruptedException 异常。

注意wait()的使用条件:wait() 必须搭配 synchronized 来使用,wait()必须写到synchronized代码块里面(notify 方法也必须在synchronized代码块中使用)。脱离 synchronized 使用 wait() 会直接抛出异常。

wait()方法内部有一步重要的操作:先解锁,再阻塞等待。因此,在使用 wait() 前,必须先加锁,把wait()写到synchronized代码块内部。同时,Java也规定调用 notify() 也必须在synchronized代码块中。

并且,加锁的锁对象必须要与调用wait()的锁对象是同一个。如果加锁对象与调用wait()的对象不是同一个,也会抛出 IllegalMonitorStateException 异常。

wait() 和 notify() 方法都必须搭配 synchronized 和同一个锁对象,如果wait()和notify()作用于不同的锁对象,是没有任何作用的。如果一个线程调用对象的notify()方法,但该线程并不处于wait的状态中,notify()不会产生作用(也没有副作用)。

2.4 wait()、wait(timeout)、sleep()、join()区别

1)wait() 和 sleep()

这两个方法最大的区别在于被设计出来的初心(即要解决的问题)不同。wait() 要解决的是线程之间的顺序控制问题,而sleep()只是单纯地让当前线程休眠一会儿。

正因如此,进一步地在使用上也有明显的区别,如wait()必须搭配锁来使用,而sleep()不需要。sleep() 是让程序暂停执行指定的时间并让出CPU给其它线程,当时间到了又会自动恢复运行状态;而wait()只有被唤醒之后,线程才会重新尝试获取锁,获取到了锁才能继续执行。

2)wait() 和 join()

join()方法是让线程t1等待线程t2线程完全结束(完成其执行)再执行。它主要起同步的作用,使线程之间的执行从“并行”变成“串行”。也就是说,当我们在线程t1中调用了线程t2的join()方法时,线程执行过程发生改变:线程t1必须等待线程t2执行完毕后,才可以继续执行下去。

  • 二者存在于不同的java包中(wait()方法在java.lang.Object中声明,而join()方法在java.lang.Thread中声明),wait()方法用于线程间通信(notify() 唤醒正在wait()的线程,这样的交互看成是一种通信),而join()方法用于在多个线程之间添加排序:第二个线程需要在第一个线程完全执行完成后才能开始执行。

  • 此外,我们可以通过使用notify()或notifyAll()方法唤醒wait()【如果有多个线程在wait(),notify()是只随机唤醒一个,而notifyAll()则是唤醒所有】,但是我们不能中途打破join()所施加的等待。

  • 最后,wait()需要在synchronized代码块中使用,而join()不需要。

使用效果上有区别:join() 只能是让t2线程先执行完,再继续执行t1,此时t1与t2之间一定是串行的。而通过wait()和notify(),可以让t2执行完一部分,再让t1执行……t1执行一部分,再让t2执行……t2再执行一部分,再让t1执行……

3)带参数的wait方法:wait(timeout)

wait() 方法存在一种重载的可以带参数方法。带参数的方法 wait(long timeout) 可以指定一个等待的超时时间timeout。如果线程的wait的时长大于等于超时时间timeout后还没有别的线程notify它,它就会自己唤醒自己。

三、Thread常用方法

public class Thread implements Runnable {
    // 线程名字
    private volatile String name;
    // 线程优先级(1~10)
    private int priority;
    // 守护线程
    private boolean daemon = false;
    // 线程id
    private long tid;
    // 线程组
    private ThreadGroup group;
    
    // 预定义3个优先级
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    
    
    // 构造函数
    public Thread();
    public Thread(String name);
    public Thread(Runnable target);
    public Thread(Runnable target, String name);
    // 线程组
    public Thread(ThreadGroup group, Runnable target);
    
    
    // 返回当前正在执行线程对象的引用
    public static native Thread currentThread();
    
    // 启动一个新线程
    public synchronized void start();
    // 线程的方法体,和启动线程没毛关系
    public void run();
    
    // 让线程睡眠一会,由活跃状态改为挂起状态
    public static native void sleep(long millis) throws InterruptedException;
    public static void sleep(long millis, int nanos) throws InterruptedException;
    
    // 打断线程 中断线程 用于停止线程
    // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
    public void interrupt();
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    // 线程是否处于活动状态
    public final native boolean isAlive();
    
    // 交出CPU的使用权,从运行状态改为挂起状态
    public static native void yield();
    
    public final void join() throws InterruptedException
    public final synchronized void join(long millis)
    public final synchronized void join(long millis, int nanos) throws InterruptedException
    
    
    // 设置线程优先级
    public final void setPriority(int newPriority);
    // 设置是否守护线程
    public final void setDaemon(boolean on);
    // 线程id
    public long getId() { return this.tid; }
    
    
    // 线程状态
    public enum State {
        // new 创建
        NEW,

        // runnable 就绪
        RUNNABLE,

        // blocked 阻塞
        BLOCKED,

        // waiting 等待
        WAITING,

        // timed_waiting
        TIMED_WAITING,

        // terminated 结束
        TERMINATED;
    }
}

四、线程基础知识 常见面试题

4.1 新建T1、T2、T3 三个线程,如何保证它们按顺序执行

可以使用线程中的join方法解决。join() 等待线程运行结束

t.join(); //阻塞调用此方法的线程进入timed_waiting,直到线程t执行完成后,此线程再继续执行

private static void testJoin() {
    Thread t1 = new Thread(() -> {
        System.out.println("t1");
    }) ;
    Thread t2 = new Thread(() -> {
        try {
            t1.join();   加入线程t1,只有t1线程执行完毕以后,再次执行该线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t2");
    });
    Thread t3 = new Thread(() -> {
        try {
            t2.join();   加入线程t2,只有t2线程执行完毕以后,再次执行该线程
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t3");
    });
    //启动线程
    t1.start();
    t2.start();
    t3.start();
}

输出

t1
t2
t3

把t1.join()、t2.join()注释掉,输出内容可能有多种 213、132、312。因为不加join(),线程之间竞争CPU,执行顺序有多种

t2
t1
t3

4.2 notify() 和 notify() 有什么区别

  • notifyAll:唤醒所有wait的线程
  • notify:只随机唤醒一个 wait 线程

4.3 java中wait和sleep方法的不同

4.3.1 共同点

wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态

4.3.2 不同点

1)方法归属不同

  • sleep(long) 是 Thread 的静态方法
  • 而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有

2)醒来时机不同

  • 执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来
  • wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去
  • 它们都可以被打断唤醒

3)锁特性不同(重点)

  • wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制
  • wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)
  • 而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)

4.4 如何停止一个正在运行的线程

通常情况下我们是不会去手动去停止的,而是等待线程自然运行至结束停止,但是在我们实际开发中,会有很多情况中我们是需要提前去手动来停止线程,比如程序中出现异常错误,比如使用者关闭程序等情况中。在这些场景下如果不能很好地停止线程那么就会导致各种问题,所以正确地停止程序十分重要。

有四种方式可以停止线程

  • run()方法运行完毕,线程自动结束
  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,因为stop和suspend、resume一样,可能发生不可预料的结果,方法已作废)
  • 使用interrupt方法中断线程
    • 打断阻塞的线程( sleep,wait,join ),线程会抛出InterruptedException异常
    • 打断正常的线程,可以根据打断状态来标记是否退出线程

第一种方式无需额外操作,下面我们重点介绍后三种方式

4.4.1 使用退出标志终止线程

一般run()方法执行完,线程就会正常结束。但有时我们需要创建一些长时间运行的线程、run方法是永远不会结束的,如在服务端程序中使用线程监听客户端请求、数据入库线程、或者其他需要循环处理的任务。在这种情况下,一般会将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可使用 while(true){…};但若想使while循环在某一特定条件下退出,可设置一个boolean类型的标志,来控制线程是否继续执行

定义一个boolean类型的标志,在线程run方法中根据该标志判断是否终止线程,多用于while循环中

public class ThreadFlag extends Thread{
    //volatile关键字的目的是使 exit 同步,即在同一时刻只能由一个线程来修改exit的值、且对其他线程可见
    public volatile boolean exit = false;

    @Override
    public void run() {
        while (!exit) {
            System.out.println("ThreadFlag running ...");
            //do something
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadFlag t1 = new ThreadFlag();
        t1.start();
        sleep(2000);   //主线程延迟2秒
        t1.exit = true; //终止线程t1
        t1.join();
        System.out.println("线程退出");
    }
}

输出

ThreadFlag running …
ThreadFlag running …

ThreadFlag running …
线程退出

4.4 2 使用stop方法强行终止(不推荐)

可以直接使用 thread.stop() 强行终止线程,但stop方法非常危险,类似于突然强行关闭计算机电源键,而非正常程序关机,可能会产生不可预料的结果。已弃用

  • 调用stop()方法会立即停止run()方法剩余的全部任务,包括catch、finally块中的语句,并抛出ThreadDeath异常,可能会导致任务执行失败
  • 调用stop()方法会立即释放该线程所持有的所有锁,一般任何加锁的代码块,都是为了保护数据的一致性,突然释放所有锁,导致数据得不到同步,因而出现数据不一致的问题。拿银行转账为例,A账户向B账户转账1000元,这一过程分为三步,第一步是从A账户中减去1000元,假如此时线程就被stop了,那么该线程会释放它所有取得的锁,其他线程在获取到它所释放的锁以后会继续执行,这样A账户少了100元、但B账户并未收到这100,导致数据不一致,因此stop()方法不安全

4.4.3 使用interrupt方法中断线程

使用interrupt()中断线程时 并不会立即终止线程,而是通知目标线程:有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。理解这一点很重要,如果中断后,线程立即无条件退出,那么就会和 stop() 方法没有任何区别,会导致线程不安全问题

调用interrupt()方法仅仅是在当前线程中打了一个停止的标记,并不是真的停止线程。接着调用Thread.currentThread().isInterrupted()方法,可以用来判断当前线程是否被终止,通过该判断我们可以做一些业务逻辑处理

具体来说,使用interrupt()中断线程 有两种情况

  • 打断阻塞的线程( sleep,wait,join ),线程会抛出InterruptedException异常
  • 打断正常的线程,可以调用isInterrupted()方法判断中断状态、决定是否退出线程

1)线程处于阻塞状态:使用interrupt()方法打断阻塞的线程( sleep,wait,join ),线程会抛出InterruptedException异常

使用 sleep、wait、join等方法时, 线程会处于阻塞状态。此时若使用interrupt()方法打断阻塞的线程,会抛出 InterruptException 异常,阻塞中的那个方法抛出该异常。

public class ThreadInterrupt extends Thread{

    public void run() {
        try {
            sleep(40000);   //延迟40s
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args) throws Exception{
        ThreadInterrupt t1 = new ThreadInterrupt();
        t1.start();
        System.out.println("在40s之内按任意键中断线程!");
        System.in.read();
        t1.interrupt();  //打断阻塞的线程
        t1.join();
        System.out.println("线程已经退出!");
    }
}

在这里插入图片描述

在调用interrupt方法后, sleep方法抛出InterruptException异常,输出错误信息为:sleep interrupted。

2)线程未阻塞,处于正常状态

使用 isInterrupted()判断线程的中断标志来退出循环。当使用 interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。

public class NormalThreadInterrupt extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println("i=" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NormalThreadInterrupt t1 = new NormalThreadInterrupt();
        t1.start();
        Thread.sleep(30);
        t1.interrupt();
    }
}

在这里插入图片描述

根据运行结果,发现调用interrupt()方法并没有停掉线程NormalThreadInterrupt中的处理逻辑,也就是说 即使线程被设置为中断状态,但这个中断并没有起到任何作用,那么怎样才能停止线程呢?此时需要用到 isInterrupted() 方法

public boolean Thread.isInterrupted() //判断是否被中断

如果希望线程在中断后停止,就应该先判断线程是否被中断,然后再执行中断处理代码

public class NormalThreadInterrupt extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            //使用 Thread.isInterrupted(),判断当前线程是否被中断。若线程被中断,退出for循环、结束线程
            if (Thread.currentThread().isInterrupted()) {
                //处理中断
                break;
            }
            System.out.println("i=" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        NormalThreadInterrupt t1 = new NormalThreadInterrupt();
        t1.start();
        Thread.sleep(30);
        t1.interrupt();
    }
}

在这里插入图片描述

五、总结

1)创建线程的方式有哪些

  • 继承Thread类实现
  • runnable接口
  • 实现Callable接口
  • 线程池创建线程(项目中使用方式)

2)runnable 和 callable 有什么区别

  • Runnable 接口run方法没有返回值
  • Callable接口call方法有返回值,需要FutureTask获取结果
  • Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛

3)run()和 start()有什么区别?

  • start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次,线程之间无顺序,是按照CPU分配的时间片来回切换的
  • run():就是普通的方法调用。封装了要被线程执行的代码,可以被调用多次。具体要启动一个线程来运行线程体中的代码/run()方法 还是需要通过start()方法来实现,调用run()方法是一种顺序编程而非并发编程

4)线程包括哪些状态

新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待( WAITING )、时间等待(TIMED_WALTING)、终止(TERMINATED)

5)线程状态之间是如何变化的

  • 创建线程对象是新建状态
  • 调用了start()方法转变为可执行状态
  • 线程获取到了CPU的执行权,执行结束是终止状态
  • 在可执行状态的过程中,如果没有获取CPU的执行权,可能会切换其他状态
    • 如果没有获取锁(synchronized或lock) 进入阻塞状态,获得锁再切换为可执行状态
    • 如果线程调用了wait()方法 进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
    • 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态

6)wait() 和 notify()

wait() 和 notify() 方法都必须搭配 synchronized 和同一个锁对象,如果wait()和notify()作用于不同的锁对象,是没有任何作用的。如果一个线程调用对象的notify()方法,但该线程并不处于wait的状态中,notify()不会产生作用(也没有副作用)

7)新建T1、T2、T3 三个线程,如何保证它们按顺序执行——可以使用线程中的join方法

8)wait与sleep有何不同

  • 都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
  • 但方法归属、醒来时机、锁特性不同

9)如何停止一个正在运行的线程

有四种方式可以停止线程

  • run()方法运行完毕,线程自动结束
  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止(不推荐,因为stop和suspend、resume一样,可能发生不可预料的结果,方法已作废)
  • 使用interrupt方法中断线程
    • 打断阻塞的线程( sleep,wait,join ),线程会抛出InterruptedException异常
    • 打断正常的线程,可以根据打断状态来标记是否退出线程

参考 https://blog.csdn.net/Mr_wxc/article/details/105696018、https://blog.csdn.net/wyd_333/article/details/130351906、黑马程序员B站视频

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/589076.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

40.WEB渗透测试-信息收集-域名、指纹收集(2)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;39.WEB渗透测试-信息收集-域名、指纹收集&#xff08;1&#xff09; oneforall的安装前置…

《深入解析Windows操作系统》第5章节学习笔记

1、每个Windows进程都是由一个执行体进程EPROCESS结构来表示的&#xff0c;EPROCESS和相关数据结构位于系统空间&#xff0c;但是进程环境控制块PEB是个例外&#xff0c;它位于进程空间地址中&#xff08;因为它包含了一些需要由用户模式代码来修改的信息&#xff09;。对于每一…

『跨端框架』Flutter环境搭建

『跨端框架』Flutter环境搭建 资源网站简介跨平台高性能发展历程跨平台框架的比较成功案例 环境搭建&#xff08;windows&#xff09;基础环境搭建Windows下的安卓环境搭建Mac下的安卓环境配置资源镜像JDKAndroid StudioFlutter SDK问题一问题二问题三修改项目中的Flutter版本 …

Java中的字符流

字符流字节流编码表 Java为什么可以区分字母和汉字 package day3; ​ import java.io.UnsupportedEncodingException; import java.lang.reflect.Array; import java.util.Arrays; ​ public class Test {public static void main(String[] args) throws UnsupportedEncoding…

【Mybatis 】什么是mybatis?如何在普通项目中使用?(超详细建议收藏)

文章目录 mybatis第一章1、什么是mybatis2、idea中配置环境3、创建一个普通工程 第二章1、mybatis基本步骤2、导入log4j日志3、使用lombok注解4、mapper.xml文件详情1、parameterType属性2、resultType属性 5、对实体包进行扫描6、SQL语句中的占位符及转义符7、接口方法包含多个…

Flutter笔记:Widgets Easier组件库(5)使用加减器

Flutter笔记 Widgets Easier组件库&#xff08;5&#xff09;&#xff1a;使用加减器 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress…

【校招】校园招聘中的签约环节,面完HR后的流程(意向书,offer选择与三方协议)

【校招】校园招聘中的签约环节&#xff0c;面完HR后的流程&#xff08;意向书&#xff0c;offer选择与三方协议&#xff09; 文章目录 一、面完HR后的流程1、口头oc、谈薪&#xff08;两个电话&#xff09;2、邮件意向书、带薪offer&#xff08;两封邮件&#xff09;3、签三方&…

算法训练营第十三天 | LeetCode 239 滑动窗口最大值、LeetCode 347 前K个高频元素

LeetCode 239 滑动窗口最大值 本体初始思路是这样的&#xff0c;首先看下给定数组长度和维持一个滑动窗口所需要花费的时间复杂度之间的关系。初步判断是还行的&#xff0c;当然后面被样例打脸了。需要更新成优先队列的解法。原本的解法能通过37/51和46/51的测试用例。但这还不…

基于Spring Boot的校园疫情防控系统设计与实现

基于Spring Boot的校园疫情防控系统设计与实现 开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 管理员登录首页界面图&#xff0c;管理员进入校园疫…

AI大模型探索之路-训练篇10:大语言模型Transformer库-Tokenizer组件实践

系列篇章&#x1f4a5; AI大模型探索之路-训练篇1&#xff1a;大语言模型微调基础认知 AI大模型探索之路-训练篇2&#xff1a;大语言模型预训练基础认知 AI大模型探索之路-训练篇3&#xff1a;大语言模型全景解读 AI大模型探索之路-训练篇4&#xff1a;大语言模型训练数据集概…

msmpi 高性能并行计算 移植并行细胞自动机报错

报错情况如图 代码来源 元胞自动机生命游戏C语言并行实现 – OmegaXYZ 稍微修改&#xff0c;因为相对路径在 msmpi 10.1.1 中失效 Microsoft Windows [版本 10.0.22000.2538] (c) Microsoft Corporation。保留所有权利。C:\Users\ASUS>mpiexec -n 9 "C:\Users\ASUS\D…

四信数字孪生水库解决方案,加快构建现代化水库运行管理矩阵

近年&#xff0c;水利部先后出台《关于加快构建现代化水库运行管理矩阵的指导意见》与《构建现代化水库运行管理矩阵先行先试工作方案》等文件&#xff0c;明确总体要求及试点水库、先行区域建设技术要求等&#xff0c;为全面推进现代化水库运行管理矩阵建设工作提供依据。 《2…

自定义Maven项目模板Archetype,快速创建模板项目。

自定义Archetype 创建好模板项目&#xff0c;在项目根目录执行命令对模板做出响应调整将模板安装到本地、远程仓库使用自定义模板 创建好模板项目&#xff0c;在项目根目录执行命令 mvn archetype:create-from-project对模板做出响应调整 如果是多模块项目&#xff0c;可能需…

【数据结构】:链表的带环问题

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;数据结构 &#x1f337;追光的人&#xff0c;终会万丈光芒 前言&#xff1a; 链表的带环问题在链表中是一类比较难的问题&#xff0c;它对我们的思维有一个比较高的要求&#xff0c;但是这一类…

【模板】前缀和

原题链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 前缀和模板题。 前缀和中数组下标为1~n。 前缀和&#xff1a;pre[i]pre[i-1]a[i]; 某段区间 [l,r]的和&#xff1a;pre[r]-pre[l-1] 3.…

【C语言】atoi和atof函数的使用

人生应该树立目标&#xff0c;否则你的精力会白白浪费。&#x1f493;&#x1f493;&#x1f493; 目录 •&#x1f319;知识回顾 &#x1f34b;知识点一&#xff1a;atoi函数的使用和实现 • &#x1f330;1.函数介绍 • &#x1f330;2.代码演示 • &#x1f330;3.atoi函数的…

【高校科研前沿】云南大学陈峰研究员联合多家单位在Sci. Bull发文揭示了明末特大干旱背景下北京降水变化及其以太平洋海温变化为主导的驱动新机制

文章简介 论文名称&#xff1a;Coupled Pacific Rim megadroughts contributed to the fall of the Ming Dynasty’s capital in 1644 CE&#xff08;环太平洋地区的特大干旱影响了公元 1644 年明朝的灭亡&#xff09; 第一作者及通讯作者&#xff1a;陈峰研究员&王涛研究…

38-4 Web应用防火墙 - WAF的使用及规则

准备:38-3 Web应用防火墙 - 安装配置WAF-CSDN博客 WAF的使用 启动 Nginx /usr/local/nginx/sbin/nginx 为了测试未启动 ModSecurity 时的访问效果,我们可以模拟攻击。要查看当前虚拟机的 IP 地址,可以使用命令 ifconfig 浏览器中访问ip,如果要在真实机中访问就需要关闭…

Linux 学习 --- 编辑 vi 命令

1、vi 基本概念&#xff08;了解&#xff09; 基本上 vi 可以分为三种状态&#xff0c;分别是命令模式 (command mode)、插入模式 (Insert mode) 和底行模式 (last line mode)&#xff0c;各模式的功能区分如下: 命令行模式 command mode&#xff09;  控制屏幕光标的移动&a…

c3 笔记7 css基本语法

相关内容&#xff1a;字体、段落、词间距、文字效果&#xff08;对齐、上下标、阴影&#xff09;、背景图、背景渐变、…… 单位pt与px的差别pt是印刷使用的字号单位&#xff0c;不管屏幕分辨率是多少&#xff0c;打印到纸上看起来都是相同的&#xff0c;lot的长度是0.01384英寸…