Java 中的多线程

这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

今天说多线程,若是你对进程和线程还有点迷糊,可以移步至 说说进程和线程

要想实现多线程,我们要先学会创建线程。创建线程有这么 3 种方式。

1 继承 Thread 类 。声明一个 Thread 的子类,该类需要重写 Thread 类的 run 方法。然后即可通过 start 方法来启动这个线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
java复制代码public class Thread1 extends Thread{
    private String name;
    public Thread1(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        for(int i = 0;i < 5;i++){
            System.out.println(name +"-----"+ i );
            try {
                sleep(1000); // 不睡也行,睡只是为了演示线程的切换效果。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Thread1 t1 = new Thread1("1 号");
        Thread1 t2 = new Thread1("2 号");
        t1.start();
        t2.start();
    }
}

2 实现 Runnable 接口。声明一个实现 Runnable 接口的子类,重写 run 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typescript复制代码public class TaskNoResult implements Runnable{
    private String name;
    public TaskNoResult(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        for(int i = 0;i < 5;i++){
            System.out.println(name +"-----"+ i );
            try {
                Thread.sleep(1000); // 不睡也行,睡只是为了演示线程的切换效果。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new TaskNoResult("任务1")).start();
        new Thread(new TaskNoResult("任务2")).start();
    }

}

本质上来说,继承 Thread 类也是在实现 Runnable 接口,启动线程的方式只有通过 Thread 类的 start 方法,start 方法是一个 native 方法,通过这个方法去执行 run 方法,run 方法里面的内容只是线程的执行体罢了。记住,启动线程的方式就一种,那就是通过 Thread 类的 start 方法。

实现 Runnable 接口的优点如下:

  1. 避免单继承的局限
  2. 线程代码可以被多个线程共享
  3. 适合多个线程处理同一个资源的情况
  4. 使用线程池时,只能放入 Runnable 或 Callable 类型线程。

3 实现 Callable 接口。Callable 接口是在 JDK1.5 中出现的,我们可以通过实现该接口并重写 call 方法来创建线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
arduino复制代码public class TaskWithResult implements Callable<String>{
    private int id;
    public TaskWithResult(int id) {
        this.id = id;
    }
    @Override
    public String call() throws Exception {
        return id+"任务被线程驱动执行!";
    }
--------------------测试如下-----------------------
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        // 使用Executors创建一个线程池来执行任务
        ExecutorService pool = Executors.newCachedThreadPool(); 
        //Future 相当于是用来存放Executor执行的结果的一种容器
        ArrayList<Future<String>> results = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++) {
            results.add(pool.submit(new TaskWithResult(i)));
        }
        for (Future<String> fs : results) {
            if (fs.isDone()) {
                System.out.println(fs.get());// 返回任务执行结果
            } else {
                System.out.println("Future result is not yet complete");
            }
        }
        pool.shutdown();
    }
}

Runnable 和 Callable 的区别:

  1. Runnable 重写 run 方法,而 Callable 重写 call 方法。
  2. Runnable 没有返回值, Callable 有返回值。
  3. run 方法不能抛出异常,call 方法可以抛出异常。
  4. 运行 Callable 任务可以得到一个 Future 对象。
1
2
3
4
5
6
7
8
9
java复制代码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;
}

Future 是一个接口,他提供给我们方法来检测当前任务是否已经结束,还可以等待任务结束并且拿到一个结果。通过调用 Future 的 get 方法可以当任务结束后返回一个结果值,如果工作没有结束,则会阻塞当前线程,直到任务执行完毕,我们可以通过调用 cancel 方法来停止一个任务,如果任务已经停止,则cancel 方法会返回 true。如果任务已经完成或已经停止或这个任务无法停止,则 cancel 会返回一个 false。当一个任务被成功停止后,他无法再次执行。 isDone 和 isCancel 方法可以判断当前工作是否完成和是否取消。

我们可以这样理解,Runnable 和 Callable 都是用来创建任务,而我们用线程去驱动执行这个任务,常规的做法像这样:

1
2
sql复制代码new Thread(new TaskNoResult("任务1")).start();
new Thread(new TaskNoResult("任务2")).start();

但是并不推荐这样使用,推荐使用线程池来创建线程进而驱动任务执行。像这样:

1
2
3
vbscript复制代码ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(new TaskNoResult("任务1"));
pool.execute(new TaskNoResult("任务2"));

下面奉上一张线程生命周期图,这张图值得好好看看。

图片

简单说一下线程中的几个方法。

start() :启动线程的方法,但是并不保证线程会立即执行。

sleep(long) :暂停线程一段时间,参数为毫秒数。

join() :把指定的线程加入到当前线程执行,等待其执行完毕,可用于控制线程的执行顺序。

yield() :线程让步,只会将 CPU 让给同优先级的线程,但是并不保证一定会让步成功。

最后说一个实际不建议使用的知识点,设置线程的优先级。因为优先级的高低不是决定线程执行顺序的决定因素,所以,千万不要指望设置优先级来控制线程的执行顺序。

1
2
3
scss复制代码t.setPriority(Thread.MIN_PRIORITY); // 最低 1
t.setPriority(Thread.NORM_PRIORITY); // 默认值 5 
t.setPriority(Thread.MAX_PRIORITY); //最高 10

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%