多线程基础运用

Posted 烟锁迷城

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程基础运用相关的知识,希望对你有一定的参考价值。

1、进程与线程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程是进程的最小单位,一个进程有多个线程组成,它至少包含一个线程。

  1. 对于单核CPU而言,多线程就是不断分配时间片来切换线程。
  2. 对于多核CPU而言,多线程就是多核心并行执行,每一个核心处理一个线程。

多线程可以有效的提升程序运行的效率,在JAVA中是一种非常常用的编程方法。

2、具体实现

2.1、thread类

线程可以通过继承thread类,重写run方法,用start方法来进行调用。

public class ThreadDemo extends Thread{

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread show");
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
        System.out.println("main show");
    }
}

2.1、runnable接口

实现Runnable接口后,重写run方法,用tread类的start方法进行调用

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread show");
    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        new Thread(runnableDemo).start();
        System.out.println("main show");
    }
}

2.3、Callable接口

实现Callable接口,重写call方法,可以实现有返回值的线程,但是要注意,这里future的get方法是阻塞的,因为需要返回值。

import java.util.concurrent.*;

public class CallableDemo implements Callable<String> {

    @Override
    public String call() throws Exception {
        Thread.sleep(2000);
        return "callable show";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        CallableDemo callableDemo = new CallableDemo();
        Future<String> future = executorService.submit(callableDemo);
        System.out.println(future.get());
        System.out.println("main show");
    }
}

3、基础知识

3.1、生命周期

java线程有六种状态,分别是new(创建),runnable(运行),time_waiting(超时等待),waiting(等待),blocked(阻塞),terminated(死亡)。
系统线程的五种状态,分别是新生,就绪(ready),运行(running),等待(waiting),死亡。

  1. 一个线程先被创建,然后进入运行(分为就绪,运行两种)状态
  2. 加锁(synchronized修饰)会进入阻塞状态
  3. 使用不带等待时间的等待函数sleep(0),wait(),join(),LockSupport.park()会进入等待状态
  4. 使用加入唤醒时间的等待函数sleep(time),wait(time),join(time),LockSupport.parkUntil(time)会进入超时等待状态
  5. notify(),notifyall(),LockSupport.unpark()可以唤醒沉睡中的线程,使之进入运行(分为就绪,运行两种)状态
  6. run函数运行结束后,线程进入死亡状态。

在这里插入图片描述

3.2、线程结束

在线程运行中,可能会因为某些原因导致线程持续运行,无法停止。

3.2.1、线程终止

使用函数stop()可以将线程强制终止。

public class StopDemo extends Thread{

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread = new StopDemo();
        System.out.println("main show");
        thread.stop();
    }
}

使用stop()终止线程是有风险的。在java程序本身没有线程,它需要借助jvm调用操作系统的线程,所以java程序强制终止线程时,实际上终止的是操作系统的线程,这会带来未知的风险,因此终止线程还是建议使用中断标志。

3.2.2、中断标志

使用线程的中断标志,可以安全的中断线程。但实际上中断标志是逻辑中断,并不会真的终止线程,所以当线程处于类似死循环的状态时,中断标志无法中断线程。

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        thread.interrupt();
    }
}

中断标志Thread.currentThread().isInterrupted()的默认值是false,当执行thread.isInterrupted()方法后,中断标志变为true,这样就可以从线程外对线程逻辑进行控制。

3.2.3、线程复位

假如在上一个例子中的主程序增加一行代码,sleep(1000),会发生什么呢?

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

执行结果为:
在这里插入图片描述
从执行结果中可以发现,线程没有被中断,而是在抛出异常后继续执行,这意味着,线程的中断标志被恢复了,线程继续运行。
发生这个结果的原因是线程内部sleep(2000)这个语句开始生效,因为之前的中断标志在线程还未进入TIME_WAITING状态就已经被结束了,在设置延时1秒中断之后,线程成功进入超时等待状态,此刻线程中断标志变更,正在等待的线程被唤醒,抛出异常,但是没有任何响应,所以线程被复位,中断标志恢复成false,线程继续执行。
假如在异常处理中,再次执行线程中断呢?

public class Demo implements Runnable {

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                System.out.println("thread show");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
        }
        System.out.println("processor stop");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Demo());
        System.out.println("main show");
        thread.start();
        Thread.sleep(1000);
        thread.interrupt();
    }
}

执行结果为:
在这里插入图片描述
此时可以看到,线程被成功中断。
这里推测,在interrupt()方法中,会做两件事情

  1. 将一个共享变量设置为true
  2. 将进入等待或超市等待状态下的线程唤醒

可以发现在所有的等待或是超时等待函数中,都要求必须被捕捉异常,这就是因为线程进入等待状态后用中断标志进行线程中断时,需要进行对应的执行操作,线程可以选择是继续执行,还是就此中断,这就将线程的执行权力交给当前线程而不是外部操作,这样的执行方式更加友好。
除了程序自动复位,还可以手动复位,使用静态方法interrupted方法即可复位。

Thread.interrupted();

3.2.4、阻塞状态中断与复位

既然等待与超时等待的线程有中断与复位,那么阻塞状态的线程是否可以用中断标志中断呢?

public class Demo implements Runnable {

    @Override
    public void run() {
        synchronized (Demo.class){
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println(Thread.currentThread().getName()+" thread show");
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println(Thread.currentThread().getName()+" processor stop");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Demo(),"thread1");
        Thread thread2 = new Thread(new Demo(),"thread2");
        System.out.println("main start");
        thread1.start();
        thread2.start();
        Thread.sleep(2000);
        thread2.interrupt();
        System.out.println("main thread2 end");
        Thread.sleep(2000);
        thread1.interrupt();
        System.out.println("main thread1 end");
        System.out.println("main stop");
    }
}

执行结果为:
在这里插入图片描述

由此可见,虽然线程2早已经执行中断,但是因为处于阻塞状态,无法进行中断,只有线程1执行结束,放开锁,线程2开始执行,中断标志直接生效,甚至没有进入等待状态,也就没有复位发生,直接结束了线程。
对于阻塞状态的线程,中断和复位都没有意义。

以上是关于多线程基础运用的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发多线程在实际项目中的运用

求解答以下的java源代码,详细点,说明这个程序的设计思路,还有比如运用了多线程的话运用了多线程的啥

线程理论及其运用

多线程 - 多线程基础

多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

在 Java 程序中怎么保证多线程的运行安全?