JAVA 多线程与高并发学习笔记(一)——线程创建

好久没写笔记了,重新回归Java,打好基础。

Java 进程中每一个线程都对应着一个 Thread 实例,其中保存着线程的描述信息。

Thread

Java 使用 Thread 类表示线程,首先看一个简单的示例。

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread();
        System.out.println("线程名称: " + thread.getName());
        System.out.println("线程ID: " + thread.getId());
        System.out.println("线程状态: " + thread.getState());
        System.out.println("线程优先级: " + thread.getPriority());
        thread.start();
    }

运行示例,可以看到如下输出:

thread1.png

Thread 类中比较重要的属性包括:

  • tid,线程ID,通过 getId() 方法可以获取线程ID,线程 ID 在进程中是唯一的。
  • name,线程名称,通过 getName() 方法可以获取线程名称,通过 setName(String name) 方法可以设置线程名称。
  • priority, 线程优先级,通过 getPriority() 方法可以获取线程优先级,通过 setPriority(int priority) 方法可以设置线程优先级。Java线程最小值为1,最大值为10,默认为5。
  • daemon,标记线程是否为守护线程,setDaemon(boolean on) 方法可以设置线程是否为守护线程。默认值为 false,表示为普通的用户线程,不是守护线程。
  • threadState,线程状态,以整数形式表示,通过 getState() 方法可以返回当前线程的状态。返回值为如下枚举:
public static enum State {
    NEW,                // 新建
    RUNNABLE,           // 就绪、运行
    BLOCKED,            // 阻塞
    WAITING,            // 等待
    TIMED_WAITING,      // 休时等得
    TERMINATED;         // 结束
}

Thread 类中还有几个常用的方法:

  • start() 方法,用来启动一个线程。
  • run() 方法,作为线程代码逻辑的入口方法。当调用 start() 方法启动一个线程后,只要线程获取了 CPU 执行时间,就会进入 run() 方法执行用户代码。

另外,通过静态方法 currentThread() 可以获取当前线程的实例对象。

通过继承 Thread 类创建线程

通过继承 Thread 类创建线程包含两个步骤:

  1. 编写一个继承 Thread 类的新线程类。
  2. 重写 run() 方法,将要执行的业务代码添加到其中。

下面看一个简单的示例:

public class createThread1 {

    static int threadNo = 1;
    static class MyThread extends Thread {
        public MyThread() {
            super("MyThread-" + threadNo);
            threadNo++;
        }

        public void run() {
            for(int i = 0; i< 3; i++) {
                System.out.println(getName() + ",轮次: " + i);
            }
            System.out.println(getName() + "运行结束.");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        for(int i = 0; i < 2; i++) {
            thread = new MyThread();
            thread.start();
        }
    }

}

运行代码,可以得到输出:

thread2.png

通过实现 Runnable 接口创建线程

如果看下 Thread 类的源码,我们会发现它实现了一个接口 RunnableRunnable 源码如下,其中只包含一个抽象方法 run()

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Thread 类中包含着一个拥有 Runnable 参数的构造器,利用这个构造器我们就可以生成 Thread 类。

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

我们可以通过实现 Runnable 接口来创建一个线程。具体步骤如下:

  1. 定义一个实现了 Runnable 接口的类。
  2. 实现 Runnable 接口的 run() 方法,将代码逻辑放入其中。
  3. 通过 Thread 类创建线程对象,将现了 Runnable 接口的类实例传入其中。

看一个例子:

public class createThread2 {
    static int threadNo = 1;

    static class RunTarget implements Runnable {
        public void run() {
            for(int i = 0; i < 3; i++) {
                System.out.println(Thread.currentThread().getName() + ", 轮次: " + i);
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        for(int i = 0; i < 2; i++) {
            Runnable target = new RunTarget();
            thread = new Thread(target, "MyRunnableThread-" + threadNo);
            threadNo++;
            thread.start();
        }
    }
}

运行代码,输出如下:

thread3.png

我们还可以利用匿名类和 lambda 表达式,更加优雅的使用 Runnbale 接口创建线程。

将上面的例子改造成匿名类:

public class createThread2 {
    static int threadNo = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        for(int i = 0; i < 2; i++) {
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for(int j = 0; j < 3; j++) {
                        System.out.println(Thread.currentThread().getName() + ", 轮次:" + j);
                    }
                }
            }, "MyRunnableThread-" + threadNo);
            threadNo++;
            thread.start();
        }
    }
}

将上面的例子改造成 Lambda 表达式:

public class createThread2 {
    static int threadNo = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = null;
        for(int i = 0; i < 2; i++) {
            thread = new Thread(() -> {
                for(int j = 0; j < 3; j++) {
                    System.out.println(Thread.currentThread().getName() + ", 轮次:" + j);
                }

            }, "MyRunnableThread-" + threadNo);
            threadNo++;
            thread.start();
        }
    }
}

使用 Runnable 接口创建现成的方式优点是可以更好地分离逻辑和数据,多个线程能够并行处理同一个资源。

通过 CallableFutureTask 创建线程

通过继承 Thread 类或者实现 Runnable 接口创建线程的缺陷是无法获取异步结果。

如果要使用异步执行,那么就要用 Callable 接口和 FutureTask 类结合创建线程。

Callable 接口

Callable 接口源码如下,它是一个泛型接口,也是一个函数式接口。其唯一的抽象方法 call() 有返回值,该方法还有一个 Exception 的异常声明,允许方法的实现版本的内部异常直接抛出,并且允许不予捕获。

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

RunnableFuture 接口

RunnableFuture 接口的源码如下,它也是一个泛型接口,并且继承了 RunnableFuture 接口,这样它一方面可以作为 Thread 线程实例的 target 实例,另一方面可以异步执行。

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

下面看一下为什么继承了 Future 接口就可以异步执行了。

Future 接口

Future 接口是用来处理异步任务的,它主要提供了三大功能:

  1. 能够取消异步执行中的任务。
  2. 判断异步任务是否执行完成。
  3. 获取异步任务完成后的执行结果。
public interface Future {
    boolean cancel(boolean mayInterruptRunning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

各个方法说明:

  • V get():获取异步任务执行的结果。注意,这个方法的调用是阻塞性的,如果异步任务没有执行完成,异步结果获取线程会一直阻塞,直到异步任务执行完成,将结果返回给调用线程。
  • V get(long timeout, TimeUnit unit):设置时限并获取异步任务执行的结果,如果阻塞时间超过设定的时限,该方法就抛出异常。
  • boolean isDone():获取异步任务是否执行完成,执行完成则返回 true
  • boolean isCancelled():获取异步任务是否取消,任务完成前取消则返回 true
  • boolean cancel(boolean mayInterruptRunning):取消异步任务的执行。

FutureTask

Future 只是个接口,而 FutureTask 类是它的一个默认实现,更准确的说, FutureTask 类实现了 RunnableTask 接口。

FutureTask 类中有一个 Callable 类型的成员,用来保存任务。FutureTaskrun() 方法会执行 call() 方法。

创建线程

步骤如下:

  1. 创建一个 Callable 接口的实现类,并实现其 call() 方法,在其中编写异步执行逻辑,可以有返回值。
  2. 使用 Callable 实现类的实例构造一个 FutureTask 实例。
  3. 使用 FutureTask 实例作为 Thread 构造器 target 的入参,构造 Thread 线程实例。

这样线程就构造好了,线程启动后,我们还可以调用 FutureTask 对象的 get() 方法阻塞性地获得并发线程的执行结果。

public class createThread3 {

    static class MyTask implements Callable {
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + " 线程开始运行.");
            Thread.sleep(500);

            for(int i = 0; i < 100000000; i++) {
                int j = i * 10000;
            }

            long used = System.currentTimeMillis() - startTime;
            System.out.println(Thread.currentThread().getName() + " 线程结束运行.");
            return used;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyTask task = new MyTask();
        FutureTask futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask, "MyThread");
        thread.start();
        Thread.sleep(500);
        System.out.println(Thread.currentThread().getName() + " 先歇一会.");
        for(int i = 0; i < 100000000 / 2; i++) {
            int j = i * 10000;
        }
        System.out.println(Thread.currentThread().getName() + " 获取并发任务的执行结果.");
        try {
            System.out.println(thread.getName() + " 线程占用时间: " + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 运行结束.");
    }
}

通过 FutureTaskget 方法获取结果,有 2 种情况:

  • 异步任务执行完成,直接返回结果。
  • 异步任务没有执行完成,一直阻塞到执行完成返回结果。

通过线程池创建线程

前面创建的线程在使用完之后就销毁了,比较浪费时间和资源,为了不频繁创建和销毁线程,需要对线程进行复用,这时候就要用到线程池技术。

Java 种提供了一个静态工厂 Executors 类来创建不同的线程池。简单示例如下:

private static ExecutorService pool = Executors.newFixedThreadPool(3);

ExecutorService 是 Java 提供的线程池接口,可以通过它的实例提交或者执行任务。ExecutorService 实例负责对池中的线程进行管理和调度,并且可以控制最大并发线程数,同时提供了定时执行、定频执行、单线程、并发数控制等功能。

ExecutorService 线程池提交异步执行 target 目标任务的常用方法:

// 执行一个 Runnable 类型的目标实例,无返回
void execute(Runnable command);
// 提交一个 Callable 类型的目标实例,返回一个 Future 异步任务实例
 Future submit(Callable task);
// 提交一个 Runnable 类型的目标实例,返回一个 Future 异步任务实例
Future submit(Runnable task);

下面看一个实战例子。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class createThread4 {
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    static class MyThread implements Runnable {
        @Override
        public void run() {
            for(int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ", 轮次: " + i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class MyTask implements Callable {
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + " 线程开始运行.");
            Thread.sleep(500);

            for(int i = 0; i < 100000000; i++) {
                int j = i * 10000;
            }

            long used = System.currentTimeMillis() - startTime;
            System.out.println(Thread.currentThread().getName() + " 线程结束运行.");
            return used;
        }
    }

    public static void main(String[] args) throws Exception{
        pool.execute(new MyThread());
        pool.execute(new Runnable() {
            @Override
            public void run() {
                for(int j = 0; j < 5; j++) {
                    System.out.println(Thread.currentThread().getName() + ", 轮次: " + j);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Future future = pool.submit(new MyTask());
        Long result = (Long)future.get();
        System.out.println("异步任务的执行结果为: " + result);
    }
}

运行结果如下:

thread4.png

可以看到线程池中的线程默认名称和普通线程也有所不同。

注意其中 ExecutorService 线程池的 executesubmit 方法有如下区别:

  1. 接收参数不一样。 submit 方法可以接收无返回值的 Runnable 类型和有返回值的 Callable 类型,execute仅接收无返回值的 Runnable 类型或者 Thread 实例。

  2. submit 方法有返回值,而 execute 方法没有。

你可能感兴趣的:(JAVA 多线程与高并发学习笔记(一)——线程创建)