Java并发基础之线程

82 阅读4分钟

1. 进程和线程

进程是运行着的程序,是程序在操作系统下的一次执行过程,是一个程序的动态概念。进程是操作系统分配资源的基本单位

线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享进程的资源(如内存、文件句柄),但有各自的栈空间和寄存器。线程是CPU执行的基本单位

从Java程序理解:

  1. 启动一个java程序,操作系统就会创建一个java进程
  2. 在一个线程里可以创建多个线程,所以在一个java程序里,可以自定义创建多个线程,这些线程拥有各自独立的计数器,堆栈和局部变量等属性,并且能够访问共享的内存变量。

2. 创建线程

创建线程有三种方法:

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

Thread

继承Thread类并重写run()方法。

public class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类实现线程");
    }
    public static void main(String[] args){
        MyThread1 myThread1 = new MyThread1();
        myThread1.start();
    }
}

Runnable

实现 Runnable 接口优于继承 Thread 类,因为:

  • Java 不支持多重继承,所有的类都只允许继承一个父类,但可以实现多个接口。如果继承了 Thread 类就无法继承其它类,这不利于扩展。
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。
public class MyThread2 implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"实现Runnable接口实现线程");
    }

    public static void main(String[] args) {
        Thread thread=new Thread(new MyThread2(),
                "线程1");
        thread.start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"匿名内部类实现Runnable接口");
        },"线程2").start();
    }
}

Callable

继承 Thread 类和实现 Runnable 接口这两种创建线程的方式都没有返回值。所以,线程执行完后,无法得到执行结果。但如果期望得到执行结果该怎么做?

为了解决这个问题,Java 1.5 后,提供了 Callable 接口和 Future 接口,通过它们,可以在线程执行结束后,返回执行结果。

Callable 接口只声明了一个方法,这个方法叫做 call():

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Future 就是对于具体的 Callable 任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过 get 方法获取执行结果,该方法会阻塞直到任务返回结果。

public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

FutureTask 类实现了 RunnableFuture 接口,RunnableFuture 继承了 Runnable 接口和 Future 接口。所以,FutureTask 既可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值。

public class FutureTask<V> implements RunnableFuture<V> {
    // ...
    public FutureTask(Callable<V> callable) {}
    public FutureTask(Runnable runnable, V result) {}
}

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

事实上,FutureTask 是 Future 接口的一个唯一实现类。

#Callable + Future + FutureTask 示例

通过实现 Callable 接口创建线程的步骤:

  1. 创建 Callable 接口的实现类,并实现 call 方法。该 call 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get 方法来获得线程执行结束后的返回值。
public class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 123;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> ft1 = new FutureTask<>(new MyThread3());
        new Thread(ft1, "实现Callable接口的线程").start();
        System.out.println(ft1.get());
        FutureTask<String> ft2 = new FutureTask<>(() -> "匿名内部类实现callable接口");
        new Thread(ft2).start();
        System.out.println(ft2.get());
    }
}

3. 线程常用方法

方法描述
run线程的执行实体
start线程的启动方法
Thread.currentThread返回当前线程的引用
setName设置线程名称
getName获取线程名称
setPriority设置线程优先级,范围[1,10],一般说,高优先级的线程在运行时具有优先权。默认优先级为5
setDaemon设置线程为守护线程
isDaemon判断线程是否为守护线程
isAlive判断线程是否启动
interrupt中断另一个线程的运行状态,并不是立即中断,而是设置线程的中断状态为 true(默认是 flase)
isInterrupted测试当前线程是否被中断
Thread.interrupted检测当前线程是否被中断,与 isInterrupted() 方法不同的是,这个方法如果发现当前线程被中断,会清除线程的中断状态
join使一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待该线程执行完才继续执行
Thread.sleep静态方法。将当前正在执行的线程休眠
Thread.yield静态方法。将当前正在执行的线程暂停,让其他线程执行

4. 线程的状态

图像12-01-25 下午8.21.jpeg