2. Java中的线程 与 线程的4中创建方式

2. Java中的线程

1. Thread类

Java中的线程主要是线程类:Thread类。

以JDK1.8为例,Thread中主要属性内容如下:

public class Thread implements Runnable {
    // 线程名称
    private volatile String name;
    // 线程优先级,最小1,最大10,默认是5
    private int            priority;
    // 是否是守护线程,默认不是
    private boolean     daemon = false;
    // 将要运行什么代码
    private Runnable target;
    // 线程id
    private long tid;
    // 线程状态,是数字类型,是状态枚举类的一个转换
    private volatile int threadStatus = 0;

    // 状态枚举
    public enum State {
        NEW, // 新建状态
        RUNNABLE, // 就绪、运行 就绪:已经可以运行,等待CPU执行  运行:正在被CPU运行
        BLOCKED,  // 阻塞
        WAITING, // 等待 
        TIMED_WAITING, // 计时等待
        TERMINATED; // 结束}

主要方法内容如下:

// 使线程开始执行,JVM将会执行run方法,将会开启一个新的线程执行用户run方法中的逻辑。start过程中会分配线程需要的资源
public synchronized void start() {
    ...
}

// 代码逻辑的入口,run方法不是由用户程序调用的,当start方法执行之后,只有线程获得了CPU执行时间,才会执行run方法
public void run() {
    if (target != null) {
        target.run();
    }
}

start方法用于线程启动,run方法是用户代码逻辑的入口。

2.线程的创建方式

1. 继承Thread类

继承Thread类,重写run方法,在run方法中写用户代码逻辑,例如:

public class ExtendThread {
    public static void main(String[] args) {
        DemoThread demoThread = new DemoThread();
        demoThread.start(); // 创建线程对象,并启动线程
    }
}

class DemoThread extends Thread {
    // 可以有其他的业务逻辑

    @Override
    public void run() {
        // 写用户逻辑,这里输出了一些线程信息
        System.out.println("Thread Name:: " + getName());
        System.out.println("Thread Id:: " + getId());
        System.out.println("Thread State:: " + getState());
        System.out.println("Thread Priority:: " + getPriority());
    }
}

输出结果如下:

Thread Name:: Thread-0
Thread Id:: 11
Thread State:: RUNNABLE
Thread Priority:: 5

2. 实现Runnable接口

public class ImplementRunnable {
    public static void main(String[] args) {
        // 借助Thread类启动线程执行
        Thread thread = new Thread(new DemoRunnable());
        thread.start();
    }
}

class DemoRunnable implements Runnable {
    @Override
    public void run() {
        // 写用户逻辑
        // 因为不是继承Thread类,所以Thread中的方法不能直接使用,需要先获取对当前线程对象,然后调用对象的方法
        Thread currentThread = Thread.currentThread();
        System.out.println("Thread Name:: " + currentThread.getName());
        System.out.println("Thread Id:: " + currentThread.getId());
        System.out.println("Thread State:: " + currentThread.getState());
        System.out.println("Thread Priority:: " + currentThread.getPriority());
    }
}

执行结果:

Thread Name:: Thread-0
Thread Id:: 11
Thread State:: RUNNABLE
Thread Priority:: 5

还记得前边的Thread类的属性和方法吗?Thread类中的run方法,如果target不为null,就执行target中的run方法。 传入target之后,启动线程的时候,就会转调传入的Runnable对象的run方法

如果run方法中的代码只使用一次,可以考虑使用匿名内部类Lambda表达式实现Runnable接口,这样写会稍稍简单一些。

使用匿名内部类改写代码,如下所示:

public class InnerClassImplementRunnable {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                Thread currentThread = Thread.currentThread();
                System.out.println("Thread Name:: " + currentThread.getName());
                System.out.println("Thread Id:: " + currentThread.getId());
                System.out.println("Thread State:: " + currentThread.getState());
                System.out.println("Thread Priority:: " + currentThread.getPriority());
            }
        });
        thread.start();
    }
}

使用Lambda表达式实现如下所示:

public class LambdaImplementRunnable {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            Thread currentThread = Thread.currentThread();
            System.out.println("Thread Name:: " + currentThread.getName());
            System.out.println("Thread Id:: " + currentThread.getId());
            System.out.println("Thread State:: " + currentThread.getState());
            System.out.println("Thread Priority:: " + currentThread.getPriority());
        });
        thread.start();
    }
}

继承Thread的方法和实现Runnable接口方法的区别

  1. 继承Thread类创建的对象直接就是线程对象
    实现Runnable接口,并不能直接创建出线程对象,需要把Runnable对象传给Thread类,然后使用Thread类创建线程对象
  2. 继承Thread类想要访问线程的属性,可以直接调用线程的方法
    实现Runnable接口要想访问线程的属性,就需要先获取到当前线程,然后再调用线程对象的方法获取
  3. 继承Thread类之后不能再继承其他类,因为Java是单继承的
    实现Runnable接口并不影响继承或实现其他类
  4. 实现Runnable接口更容易实现数据和逻辑的分离,使用Runnable更容易访问共享的数据资源

3. 使用Callable和FutureTask创建线程

继承Thread类或实现Runnable接口都不能获取到线程的执行结果,但是很多场景都需要获取异步的执行结果。为了解决这个问题,Java在1.5之后出现了Callable接口和FutureTask相结合创建线程。

Callable接口

package java.util.concurrent;

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

可以看到,Callable接口是java.util.concurrent包下的,call方法可以抛出异常,且有返回值,返回值类型为Callable接口的泛型类型。

但是因为Callable接口和Runnable接口以及Thread类没有关联,要想使用Callable接口创建线程,需要借助RunnableFuture接口。

RunnableFuture接口

package java.util.concurrent;

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture接口也位于java.util.concurrent包下,继承了Runnable接口,保证可以作为Thread类中的target目标。同时继承了Future接口,保证可以获取未来的异步执行结果。


Future接口介绍

public interface Future<V> {
	// 取消异步任务的执行
    boolean cancel(boolean mayInterruptIfRunning);
	// 任务是否被取消。如果在完成之前被取消,则返回true
    boolean isCancelled();
	// 任务是否已完成,不管是正常完成、被取消、抛出异常终止,都会返回true
	boolean isDone();
	// 获取任务执行结果
	V get() throws InterruptedException, ExecutionException;
	// 在规定时间内获取任务完成的执行结果
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

注意:
get()方法是阻塞的,如果异步线程没有完成执行,调用线程会一直被阻塞到异步线程执行完成,然后将结果返回给调用线程
get(long timeout, TimeUnit unit)方法也是阻塞的,但是有一个阻塞时长,如果超过设置的阻塞时间仍没有执行完成,方法会抛出异常,调用线程可以捕获到异常。

总体来说Future是一个与异步线程进行交互、操作的接口。
JDK提供了一个默认的实现类:FutureTask

FutureTask类介绍

FutureTask部分代码如下:

package java.util.concurrent;

public class FutureTask<V> implements RunnableFuture<V> {
	// 存放要运行的callable对象,运行一次之后会被清空
	private Callable<V> callable;
	// 线程运行的结果或抛出的异常将会存放到这个属性,可以通过get方法获取
	private Object outcome;
	// 接收Callable对象的构造方法
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
}

使用Callable接口+FutureTask类创建线程大致需要以下几步:

  1. 创建Callable对象
  2. 使用Callable对象构建FutureTask对象
  3. 使用FutureTask对象创建Thread对象
  4. 使用Thread对象启动线程
  5. 使用FutureTask对象获取结果

示例代码:

public class TestFutureTask {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 使用Callable接口构建FutureTask对象
        FutureTask<String> task = new FutureTask<String>(() -> {
            long start = System.currentTimeMillis();
            String name = Thread.currentThread().getName();
            System.out.println(name + ":线程开始执行~");
            Thread.sleep(1000);
            System.out.println(name + ":线程执行结束~");
            long end = System.currentTimeMillis();
            return "线程" + name + "执行耗时:" + (end - start) + "毫秒";
        });
        // 使用FutureTask对象构建线程对象
        Thread thread = new Thread(task);
        // 启动线程
        thread.start();
        // 会等待异步线程执行完毕
        String result = task.get();
        System.out.println("获取到的结果是:" + result);
    }
}

执行结果如下:

Thread-0:线程开始执行~
Thread-0:线程执行结束~
获取到的结果是:线程Thread-0执行耗时:1016毫秒

4. 使用线程池创建线程

前边的示例中,所创建的线程在执行完成后就都被销毁了,这些线程都是不可复用的。
但是其实创建一个线程的时间成本、资源耗费都是很高的,在并发场景下,不能进行频繁的线程实例的创建与销毁,而是需要把已经建好的线程实例进行复用,于是就出现了线程池技术。
Java提供了Executors工厂类,可以快捷的创建线程池。
创建出来的线程池为ExecutorService类型,使用ExecutorService线程池执行线程任务的方法主要有submit方法 和 execute方法

public interface ExecutorService extends Executor {
	<T> Future<T> submit(Callable<T> task);
	<T> Future<T> submit(Runnable task, T result);
	Future<?> submit(Runnable task);
	/**
	 * 调用之后,不会再接收新的任务请求,会执行完再队列中的任务然后关闭线程池
	 */
	void shutdown();
}

public interface Executor {
	void execute(Runnable command);
}

submit(…)方法与execte(…)方法的区别:

  1. 接收的参数不同
    submit方法可以接收Runnable接口类型和Callable接口类型的参数
    execute方法只能接收Runnable接口类型的参数
  2. 返回值不同
    submit会返回Future类型的返回值,所以可以获取线程的返回值
    execute没有返回值
  3. 所属接口不同
    submit方法属于ExecutorService接口
    execute方法属于Executor接口

使用线程池创建线程示例:

public class TestExecutors {
    public static void main(String[] args) throws InterruptedException {
        // 创建线程池对象
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 创建runnable对象
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程:" + Thread.currentThread().getName() + " 正在执行");
        };
        for (int i = 0; i < 10; i++) {
            // 使用线程池执行runnable对象的任务
            executorService.submit(runnable);
        }
        // 执行完毕之后,线程池并不会关闭,所以JVM也不会关闭。
        // 想要关闭线程池对象,需要使用手动关闭
        executorService.shutdown();
    }
}

注意:
这里使用Executors创建线程池只是一个演示,实际生产环境不允许使用Executors创建线程池。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值