• 并发基础(Runnable、Thread、Executor)


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

         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方法判断一个线程是否是后台线程。由后台线程创建的任何线程都将自动设置为后台线程。

      

  • 相关阅读:
    Unity 3(一):简介与示例
    MongoDB以Windows Service运行
    动态SQL中变量赋值
    网站发布IIS后堆栈追踪无法获取出错的行号
    GridView Postback后出错Operation is not valid due to the current state of the object.
    Visual Studio 2010 SP1 在线安装后,找到缓存在本地的临时文件以便下次离线安装
    SQL Server 问题之 排序规则(collation)冲突
    IIS 问题集锦
    linux下安装mysql(ubuntu0.16.04.1)
    apt-get update 系列作用
  • 原文地址:https://www.cnblogs.com/hzmark/p/ConcurrentBase.html
Copyright © 2020-2023  润新知