并发基础(Runnable、Thread、Executor)

简介: 与顺序编程不同,并发使程序可以在“同一时间”执行多个操作。      Java对并发编程提供了语言级别的支持。Java通过线程来实现并发程序。一个线程通常实现一个特定的任务,多个线程一起执行的时候就实现了并发。

     与顺序编程不同,并发使程序可以在“同一时间”执行多个操作。

     Java对并发编程提供了语言级别的支持。Java通过线程来实现并发程序。一个线程通常实现一个特定的任务,多个线程一起执行的时候就实现了并发。

     定义任务的最简单的方式就是实现Runnable接口。

1 public interface Runnable {
2     public abstract void run();
3 }

     Runable只定义了一个run()方法。

     下面是一个监听用户输入的任务。

 1 public class Monitor implements Runnable {
 2 
 3     @Override
 4     public void run() {
 5         BufferedReader reader = new BufferedReader(new InputStreamReader(
 6                 System.in));
 7         while (true) {
 8             String str;
 9             try {
10                 str = reader.readLine();
11                 if (str.equals("quit")) {
12                     return;
13                 } else {
14                     System.out.println(str);
15                 }
16             } catch (IOException e) {
17                 e.printStackTrace();
18             }
19 
20         }
21     }
22 }

     执行一个任务最简单的方式是把它交给一个Thread构造器。

1 public class Test {
2     public static void main(String[] args) {
3         System.out.println("Main Start");
4         Thread task = new Thread(new Monitor());
5         task.start();
6         System.out.println("Main End");
7     }
8 }

     执行上面的程序可以看到类似下面这样的结果:

     

     可以看到Main方法一次执行各语句到最后输出“Main End”,但Monitor依旧在运行,因为它在另一个线程中。

     除了Thread的方式,Java还提供了执行器Executor简化并发编程。

     Executor使用execute(Runnable command)方法执行一个任务,使用shutdown()方法防止新任务被提交给Executor,当前线程将继续执行shutdown()方法调用之前提交的任务。像这样:

1 public static void main(String[] args) {
2     System.out.println("Use Executor...");
3     ExecutorService exec = Executors.newCachedThreadPool();
4     exec.execute(new Monitor());
5     exec.shutdown();
6 }

     Executor的详细内容见《Java Executor框架分析》

 

     Runnable只是执行一个任务,但是并不能获取任务的执行结果(准确的说应该是run方法是一个void的方法,没有返回值)。如果希望获取任务的执行结果,那么可以选择实现Callable接口。

1 public interface Callable<V> {
2     V call() throws Exception;
3 }

     它是一个接收泛型,且具有返回内容的“任务接口”。下面是一个通过Callable执行任务并获取返回结果的例子。

 1 public class Test {
 2     public static void main(String[] args) throws InterruptedException,
 3             ExecutionException {
 4         ExecutorService exec = Executors.newCachedThreadPool();
 5         List<Future<String>> results = new ArrayList<Future<String>>();
 6         for (int i = 0; i < 5; i++) {
 7             results.add(exec.submit(new TaskWithResult(i)));
 8         }
 9         exec.shutdown();
10         for (Future<String> f : results) {
11             System.out.println(f.get());
12         }
13     }
14 }
15 
16 class TaskWithResult implements Callable<String> {
17     private int id;
18 
19     public TaskWithResult(int id) {
20         this.id = id;
21     }
22 
23     @Override
24     public String call() throws Exception {
25         return "result of TaskWithResult#" + id;
26     }
27 }

休眠

    Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。线程休眠是使线程让出CPU的最简单的做法之一。当线程休眠一定时间后,线程会苏醒,进入准备状态等待执行。

    Thread提供了两个sleep方法:Thread.sleep(long millis) 和Thread.sleep(long millis, int nanos)。

    线程可以通过休眠让出CPU的执行权限,但是这也是无法保证线程精确的执行次序的。下面是一个线程休眠的例子。

    枚举TimeUnit中也提供了sleep方法,可以通过它的实例去调用,如TimeUnit.MICROSECONDS.sleep(timeout)。

 

sleep例子
 1 package com.skyjoo.test;
 2 
 3 public class SleepingTask {
 4 
 5     static class SimpleThread extends Thread {
 6         @Override
 7         public void run() {
 8             for (int i = 0; i < 5; i++) {
 9                 System.out.println("Thread run" + i);
10                 try {
11                     Thread.sleep(100);
12                 } catch (InterruptedException e) {
13                 }
14             }
15         }
16     }
17 
18     static class SimpleRunnable implements Runnable {
19         @Override
20         public void run() {
21             for (int i = 0; i < 5; i++) {
22                 System.out.println("Runnable run" + i);
23                 try {
24                     Thread.sleep(100);
25                 } catch (InterruptedException e) {
26                 }
27             }
28         }
29 
30     }
31     public static void main(String[] args) {
32         Thread simpleThread = new SimpleThread();
33         Thread thread = new Thread(new SimpleRunnable());
34         simpleThread.start();
35         thread.start();
36     }
37 }

Thread run0
Runnable run0
Runnable run1
Thread run1
Thread run2
Runnable run2
Thread run3
Runnable run3
Runnable run4
Thread run4   

    上面是运行结果。从运行结果中可以看出休眠可以让出执行权限,但是不能保证精确的执行顺序。
    枚举TimeUnit中也提供了sleep方法,可以通过它的实例去调用,如TimeUnit.MICROSECONDS.sleep(timeout)。

让步

    如果已经完成了run()方法的循环和一次迭代过程中所需的工作,就可以给线程调度机制一个暗示:工作已经做的差不多了,可以让别的线程占用CPU。这个暗示是通过调用yield()方法实现的。这只是一个暗示,未必会被采用。当调用yield()时,也是在建议具有相同优先级的线程可以运行。

    任何重要的调度都不能依赖于yield()。

 优先级

    线程的优先级将该线程的重要性传递给调度器。尽管CPU处理线程的顺序是不确定的,但是调度器将倾向于让优先级高的线程先执行。优先级较低的线程执行的频率较低。

    可以通过setPriority和getPriority来设置和获取线程的优先级。

    线程的优先级从1~10.超出这个值将报异常。

join()

    Waits for this thread to die. 

    这是Thread中对join()方法的注释。

    线程可以在其他线程之上调用join()方法,效果等同于等待一段时间直到第二个线程结束才继续执行。如在t1中调用t2.join()则需要t2线程执行完后继续执行t1线程。

    join()方法还有其他形式,如带上超时时间,这样目前线程在这段时间内没有结束join方法也能返回。(其实join()方法调用的是join(0))

    join(long millis) 

    join(long millis, int nanos) 

    join()

守护进程

    所谓守护线程,也可以叫后台线程,是指在程序运行的时候后台提供一种通用服务的线程,并且这种线程不属于程序中不可或缺的部分(只要有非后台线程还在运行,程序就不会终止。main就是一个非后台线程)。

SimpleDaemons
 1 public class SimpleDaemons implements Runnable {
 2 
 3     @Override
 4     public void run() {
 5         try {
 6             while (true) {
 7                 TimeUnit.MILLISECONDS.sleep(100);
 8                 System.out.println(Thread.currentThread() + " " + this);
 9             }
10         } catch (Exception e) {
11             // TODO: handle exception
12         }
13     }
14 
15     public static void main(String[] args) throws InterruptedException {
16         for(int i=0;i<10;i++){
17             Thread daemon = new Thread(new SimpleDaemons());
18                         // 必须在线程启动之前调用setDaemon方法
19             daemon.setDaemon(true);
20             daemon.start();
21         }
22         System.out.println("All daemons started");
23         TimeUnit.MILLISECONDS.sleep(175);
24     }
25 }

All daemons started
Thread[Thread-8,5,main] com.skyjoo.test.SimpleDaemons@47b480
Thread[Thread-1,5,main] com.skyjoo.test.SimpleDaemons@1bf216a
Thread[Thread-2,5,main] com.skyjoo.test.SimpleDaemons@10d448
Thread[Thread-3,5,main] com.skyjoo.test.SimpleDaemons@6ca1c
Thread[Thread-6,5,main] com.skyjoo.test.SimpleDaemons@6ca1c
Thread[Thread-9,5,main] com.skyjoo.test.SimpleDaemons@e0e1c6
Thread[Thread-7,5,main] com.skyjoo.test.SimpleDaemons@19b49e6
Thread[Thread-4,5,main] com.skyjoo.test.SimpleDaemons@47b480
Thread[Thread-0,5,main] com.skyjoo.test.SimpleDaemons@156ee8e
Thread[Thread-5,5,main] com.skyjoo.test.SimpleDaemons@156ee8e

    注意观察main中的sleep(175),如果设置成更长的时间将看到更多的输出结果,因为每个线程都在不断的输出结果。一旦main结束了,就没有非后台线程了,所以程序就终止了,所以就不会在有输出了。如果main中设置的sleep之间为0将看不到线程输出的结果,因为程序会马上结束掉。

    注意:可以通过isDaemon方法判断一个线程是否是后台线程。由后台线程创建的任何线程都将自动设置为后台线程。

  

如果本文对您有帮助,点一下右下角的“推荐”
目录
相关文章
|
5月前
|
存储 缓存 安全
(八)深入并发之Runnable、Callable、FutureTask及CompletableFuture原理分析
关于Runnable、Callable接口大家可能在最开始学习Java多线程编程时,都曾学习过一个概念:在Java中创建多线程的方式有三种:继承Thread类、实现Runnable接口以及实现Callable接口。但是实则不然,真正创建多线程的方式只有一种:继承Thread类,因为只有`new Thread().start()`这种方式才能真正的映射一条OS的内核线程执行,而关于实现Runnable接口以及实现Callable接口创建出的Runnable、Callable对象在我看来只能姑且被称为“多线程任务”,因为无论是Runnable对象还是Callable对象,最终执行都要交由Threa
107 1
Thread、Runnable、线程池
Thread是Java中的一个类,用于表示一个线程,它实现了Runnable接口。 通过创建Thread对象,可以创建并启动一个新的线程,执行指定的代码。
|
安全 Java 程序员
线程(Thread)
🌼什么是线程 🌼Java 线程在代码中的体现 🌷线程对象 🌷在 Java 代码中创建线程 🌷启动线程 🌷代码演示创建线程 🌼多线程下各个线程之间执行先后的随机性 🌷什么情况下,子线程会被先执行 🌷什么情况下,会出现线程调度 🌼线程安全 🌷线程之间的数据共享 🌷演示什么是线程不安全 🌷线程不安全的原因 🌷原子性 🌷系统角度分析线程不安全的原因
88 0
ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
ThreadPoolExecutor的中的submit和FutureTask || 通过Executors 创建线程池的一些实例(Callable和Runnable的在其中的体现)
217 0
|
设计模式 Java 开发者
Thread 与 Runnable 关系 | 学习笔记
快速学习 Thread 与 Runnable 关系。
Thread 与 Runnable 关系 | 学习笔记
Java——多线程高并发系列之创建多线程的四种方式(Thread、Runnable、Callable、线程池)
Java——多线程高并发系列之创建多线程的四种方式(Thread、Runnable、Callable、线程池)
Java——多线程高并发系列之创建多线程的四种方式(Thread、Runnable、Callable、线程池)
除了Thread和Runnable,你还知道第三种创建线程的方式Callable吗
相信大多数学过多线程的同学都知道创建线程常见的有三种方式,一种是继承Thread类,一种是实现Runnable接口,最后一种就是Callable,今天主要是对最后不常见的Callable方式进行介绍。
127 0
Thread和Runnable实现多线程(一)下
Thread和Runnable实现多线程(一)
129 0
Thread和Runnable实现多线程(一)下
Thread和Runnable实现多线程(一)上
实现多线程,有两种方式,一种是继承 Thread 类,一种是实现 Runnable接口。
199 0
Thread和Runnable实现多线程(一)上
|
Java
Java多线程之Executor框架:Callable、Future、Executor和ExecutorService
Executor框架是指JDK 1.5中引入的一系列并发库中与Executor相关的功能类,包括Executor、Executors、ExecutorService、Future、Callable等。
1499 0