java多线程入门类和接口

进程和线程基本概念

进程

进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间互不干扰。同时进程保存着程序每一个时刻运行的状态。是操作系统分配资源的基本单位。

线程

一个进程至少包含一个线程,每个线程负责一个单独的子任务,是CPU分配时间片的基本单位。

进程与线程的区别

进程是一个独立的运行环境,而线程是在进程中执行的一个任务。他们两个本质的区别是是否单独占用内存空间及其它系统资源(比如I/O)。

  • 进程单独占有一定的内存地址空间,所以进程间存在内存隔离,数据是分开的,数据共享复杂但是同步简单,各个进程之间互不干扰;而线程共享所属进程占有的内存地址空间和资源,数据共享简单,但是同步复杂。
  • 进程单独占有一定的内存地址空间,一个进程出现问题不会影响其他进程,不影响主程序的稳定性,可靠性高;一个线程崩溃可能影响整个程序的稳定性,可靠性较低。
  • 进程单独占有一定的内存地址空间,进程的创建和销毁不仅需要保存寄存器和栈信息,还需要资源的分配回收以及页调度,开销较大;线程只需要保存寄存器和栈信息,开销较小。
  • 进程是操作系统进行资源分配的基本单位;而线程是操作系统进行调度的基本单位,即CPU分配时间片的基本单位。

多进程的方式也可以实现并发,为什么要使用多线程?

  • 进程间的通信比较复杂,而线程间的通信比较简单,通常情况下,我们需要使用共享资源,这些资源在线程间的通信比较容易。
  • 进程是重量级的,而线程是轻量级的,故多线程方式的系统开销更小。

多线程入门类和接口

在java中,JdK提供了Thread类和Runable接口来让我们实现自己的"线程"类。

  • 继承Thread类,并重写run方法
  • 实现Runable接口的run方法

继承Thread类

public class MyThread extends Thread{

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

    public static void main(String[] args) {
        MyThread thread = new MyThread();
        thread.start();
    }

}

调用start()方法后,线程才算启动。

调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再次调用run()方法。

不能多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出IllegalThreadStateException异常。

实现Runnable接口

Runnable接口源码

image-20210809173029321

Runnable是一个函数式接口,可以使用Java 8的函数式编程来简化代码。

示列代码:

public class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println("Hello world");
    }
    
    public static void main(String[] args) {

        new Thread(new MyThread()).start();

        //java8函数式编程创建线程
        new Thread(()->{
            System.out.println("你好吗!");
        }).start();
    }

}

Thread类构造方法

Thread类是一个Runnble接口的实现类,如下为Thread类的部分源码

// 片段1 - init方法
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals)
    
// 片段2 - 构造函数调用init方法
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

// 片段3 - 使用在init方法里初始化AccessControlContext类型的私有属性
this.inheritedAccessControlContext = 
    acc != null ? acc : AccessController.getContext();

// 片段4 - 两个对用于支持ThreadLocal的私有属性
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

init方法参数:

  • g:线程组,指定这个线程是在哪个线程组下

  • target:指定要执行的任务

  • name:线程的名字,多个线程的名字是可以重复的。如果不知道名字,见片段2

  • acc:见片段3,用于初始化私有变量inheritedAccessControlContext。

    它是一个私有变量,但是在Thread类里只有init方法对它进行初始化,在exit方法把它设为null。其它没有任何地方使用它。一般是不会使用它的,那什么时候会用到这个变量呢?可以参考这个stackoverflow的问题:Restrict permissions to threads which execute third party software

  • inheritThreadLocals:可以继承ThreadLocal,见片段4,Thread类里面有两个私有属性来支持ThreadLocal

实际情况下,大多数是直接调用下面两个构造方法:

  • Thread(Runnable target)
  • Thread(Runnable target, String name)

Thread类的几个常用方法

  • currentThread():静态方法,返回对当前正在执行的线程对象的引用

    image-20210809180507079

  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法

  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的,比如在线程B中调用了线程A的join方法,直到线程A执行完成后,才会继续执行线程B

  • sleep():静态方法,使当前线程睡眠一段时间

  • yield():静态方法,yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的

Thread类与Runnable接口的比较

  • 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活
  • Runnable接口更符合面向对象,将线程单独进行对象的封装
  • Runnable接口降低了线程对象和线程任务的耦合性
  • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量

Callable、Future与FutureTask

通常来说,使用Runnable和Thread来创建一个新的线程,但是它们有一个弊端,就是run方法是没有返回值。而有的时候我们希望开启一个线程去执行一个任务,并且这个任务执行完后有一个返回值。JDK提供了Callable接口与Future接口为我们解决这个问题,这也是所谓的“异步”模型。

Callable接口

CallableRunnable类似,同样是一个抽象方法的函数式接口。不同的是,Callable提供的方式是有返回值的,而且支持泛型

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable一般配合线程池工具ExecutorService来使用的。ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,可以通过Futureget方法得到返回值。

示列代码:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {

        return 10;
    }

    public static void main(String[] args) throws Exception {
        
        ExecutorService executor = Executors.newCachedThreadPool();
        Future<Integer> future = executor.submit(new MyCallable());
        //注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法
        System.out.println(future.get());

    }

}
Future接口

Future接口的几个比较简单的方法:

  • boolean cancel(boolean mayInterruptIfRunning)

    试图取消一个线程的执行。但并不一定能取消成功,因为任务可能已完成、已取消、或者一些其它因素不能取消,存在取消失败的可能。boolean类型的返回值是“是否取消成功”的意思,参数表示是否采用中断的方式取消线程执行。所有有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable。如果为了可取消性而使用Future但又不提供可用的结果,可以申明Future<?>形式类型、并返回null作为底层任务的结果。

  • boolean isCancelled()

    判断当前线程是否取消

  • boolean isDone()

    判断当前线程是否完成

  • V get() throws InterruptedException, ExecutionException

    当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕

  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException

    最多等待timeout的时间返回结果

FutureTask类

Future接口还有个实现类叫FutureTaskFutureTask是实现的RunnableFuture接口,而RunnableFuture接口同时继承了Runnable接口和Future接口

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

FutureTask类有什么用?为什么要有一个FutureTask类?前面说到了Future只是一个接口,而它里面的cancelgetisDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

示列代码:

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        Thread.sleep(1000);
        return 10;
    }

    public static void main(String[] args) throws Exception {

        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask futureTask = new FutureTask(new MyCallable());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
        executor.shutdown();

    }

}

在很多高并发的环境下,有可能CallableFutureTask会创建多次,FutureTask能够在高并发环境下确保任务只执行一次

线程的几种状态

java线程的生命周期,存在几种状态。在Thread类里有一个枚举类型State,定义了线程的几种状态。

  • NEW:初始状态

    线程创建之后,但是还没有启动,这时它的状态就是NEW

  • RUNNABLE:运行状态

    正在java虚拟机下跑任务的线程的状态,在RUNNABLE状态下的线程可能会处于等待状态,因为它正在等待一些系统资源的释放,比如IO

  • BLOCKED:阻塞状态

    等待锁的释放,比如线程A进入了一个synchronized方法,线程B也想进入这个方法,但是这个方法的锁已经被线程A获取了,这个时候线程B就处于BLOCKED状态

  • WAITING:等待状态

    处于等待状态的线程是由于执行了一下三个方法中的任意方法。

    1.Object的wait方法,并且没有使用timeout参数

    2.Thread的join方法,没有使用timeout参数

    3.LockSupport的park方法

    处于waiting状态的线程会等待另一个线程处理特殊的行为。比如,如果一个线程调用了一个对象的wait方法,name这个想吃就会处于waiting状态直到另外一个线程调用这个对象的notify或者notifyAll方法后才会接触这个状态

  • TIMED_WAITING:有等待时间的等待状态

    1.Object的wait方法,带有时间参数

    2.Thread的join方法,带有时间参数

    3.LockSupport的parkNanos方法,带时间参数

    4.Thread的sleep方法,带时间参数

    5.LockSupport的parkUntil方法,带有时间

  • TIMED_WAITING:有等待时间的等待状态

    1.Object的wait方法,带有时间参数

    2.Thread的join方法,带有时间参数

    3.LockSupport的parkNanos方法,带时间参数

    4.Thread的sleep方法,带时间参数

    5.LockSupport的parkUntil方法,带有时间

  • TERMINATED:终止状态

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值