zoukankan      html  css  js  c++  java
  • 基本线程机制

    基本线程机制

    一个程序可以被划分为多个独立的任务,每个独立的任务可以由线程来驱动执行;

    一个进程可以包含若干个线程,即拥有若干个并发执行的任务,在程序运行时,CPU时间被划分成片段分配给所有的线程;

    在单处理器的机器上使用多线程可以提高性能的原因在于任务阻塞;

    为机器增加处理器可以显著加快使用多线程程序的运行速度;

    使用线程机制使程序更加透明、可扩展,代码不需要知道它是运行在单处理器还是多处理器上;

    创建线程方式

    方式一、创建一个任务类实现Runnable接口,并将其具体对象提交给Thread构造器

    创建一个发射类LiftOff实现Runnable接口:

    package concurrency;
    
    public class LiftOff implements Runnable {
        protected int countDown = 10; // Default
        private static int taskCount = 0;
        private final int id = taskCount++;
    
        public LiftOff() {
        }
    
        public LiftOff(int countDown) {
            this.countDown = countDown;
        }
    
        public String status() {
            return Thread.currentThread() + "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + "), ";
        }
    
        public void run() {
            while (countDown-- > 0) {
                System.out.println(status());
                Thread.yield();
            }
        }
    } 
    View Code

    以上代码中调用了Thread.yield()方法,该方法的作用是建议线程调度器切换到其它线程执行任务,注意,只是建议,不保证采纳;

    创建完任务类之后,可以在Main函数中使用LiftOff对象创建一个Thread对象,并调用其start方法启动该线程,如下:

    package concurrency;
    
    public class BasicThreads {
        public static void main(String[] args) {
            Thread t = new Thread(new LiftOff());
            t.start();
            System.out.println(Thread.currentThread() + "Waiting for LiftOff");
        }
    } 

    打印结果如下,注意该程序中是同时存在两个线程(main和Thread-0)在运行的;

    另外关于Thread对象的打印形式为[Thread-0,5,main],其中依次代表[线程名,线程优先级、线程组名], 具体可查看Thread类的toString方法;

    Thread[main,5,main]Waiting for LiftOff
    Thread[Thread-0,5,main]#0(9), 
    Thread[Thread-0,5,main]#0(8), 
    Thread[Thread-0,5,main]#0(7), 
    Thread[Thread-0,5,main]#0(6), 
    Thread[Thread-0,5,main]#0(5), 
    Thread[Thread-0,5,main]#0(4), 
    Thread[Thread-0,5,main]#0(3), 
    Thread[Thread-0,5,main]#0(2), 
    Thread[Thread-0,5,main]#0(1), 
    Thread[Thread-0,5,main]#0(Liftoff!), 

    最后,提个醒,有些人在创建完任务类后,直接在main函数中新建一个任务类对象,并调用其run方法,如下代码,运行正常,也看到了run方法中的运行结果,以为创建了线程,其实这种使用方式是错误的,并没有创建任何新线程,只是在main线程里调用执行了一个普通对象的方法而已;

    package concurrency;
    
    public class MainThread {
        public static void main(String[] args) {
            LiftOff launch = new LiftOff();
            launch.run();
        }
    } 

    方式二、继承Thread类,调用其具体对象的start方法

    package concurrency;
    
    public class SimpleThread extends Thread {
        private int countDown = 5;
        private static int threadCount = 0;
    
        public SimpleThread() {
            // Store the thread name:
            super(Integer.toString(++threadCount));
            start();
        }
    
        public String toString() {
            return "#" + getName() + "(" + countDown + "), ";
        }
    
        public void run() {
            while (true) {
                System.out.println(this);
                if (--countDown == 0)
                    return;
            }
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++)
                new SimpleThread();
        }
    }
    View Code

    对比通过实现Runnable接口的方式,该方式不建议使用,因为java的单继承机制,通常通过实现接口比继承会更好点;

    另外还可以通过内部内部类将线程代码隐藏在类中,如下写法;

    class InnerThread1 {
        private int countDown = 5;
        private Inner inner;
    
        private class Inner extends Thread {
            Inner(String name) {
                super(name);
                start();
            }
    
            public void run() {
                try {
                    while (true) {
                        print(this);
                        if (--countDown == 0)
                            return;
                        sleep(10);
                    }
                } catch (InterruptedException e) {
                    print("interrupted");
                }
            }
    
            public String toString() {
                return getName() + ": " + countDown;
            }
        }
    
        public InnerThread1(String name) {
            inner = new Inner(name);
        }
    }
    View Code

    方式三、创建一个任务类实现Runnable接口,并将其具体对象提交给Executors【推荐】

    java.util.concurrent包中的执行器Executors可以帮助我们管理Thread对象,简化并发编程,如下,可以使用Executors类中的newCachedThreadPool静态方法创建一个可缓存的线程池,并用其执行相关任务;

    package concurrency;
    
    import java.util.concurrent.*;
    
    public class CachedThreadPool {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++)
                exec.execute(new LiftOff());
            exec.shutdown();
        }
    }

    在Executors类中,除了通过newCachedThreadPool创建线程池外,还可以创建通过以下方法创建其它种类的线程池:

    newFixedThreadPool:固定大小度的线程池

    newSingleThreadExecutor:单线程线程池

    newScheduledThreadPool:执行定时和周期性任务

    方式四、创建一个任务类实现Callable接口,并将其具体对象提交给Executors【推荐】

    实现Callable接口的类同样是一个任务类,与实现Runnable接口的区别是该方式可以有返回值;

    在实现Callable接口的类中,线程执行的方法是call方法(有返回值),而不是run方法;

    在main方法中可以通过调用ExecutorService的submit方法,返回一个Future对象,通过该对象可以获取线程运行的返回值,注意需要等Future完成后才能取得结果,可以通过isDone方法来查询Future是否已完成,或者直接调用get方法来获取(会阻塞,直到结果准备就绪)。

    package concurrency;
    
    import java.util.concurrent.*;
    import java.util.*;
    
    class TaskWithResult implements Callable<String> {
        private int id;
    
        public TaskWithResult(int id) {
            this.id = id;
        }
    
        public String call() {
            return "result of TaskWithResult " + id;
        }
    }
    
    public class CallableDemo {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            ArrayList<Future<String>> results = new ArrayList<Future<String>>();
            for (int i = 0; i < 10; i++)
                results.add(exec.submit(new TaskWithResult(i)));
            for (Future<String> fs : results)
                try {
                    System.out.println(fs.get());
                } catch (InterruptedException e) {
                    System.out.println(e);
                    return;
                } catch (ExecutionException e) {
                    System.out.println(e);
                } finally {
                    exec.shutdown();
                }
        }
    } 

    小结

    其实,更普遍的,我觉得创建线程就两种形式:

    • 直接通过new Thread创建线程(可传入任务对象);
    • 创建任务对象提交给Executors去创建(其实内部的线程工厂也是通过new Thread创建);

    另外,这里的任务对象也有两种方式创建,通过实现Runnable接口和实现Callable接口;

     守护线程(后台线程)

    daemon线程是指在程序运行的时候,在后台提供一种通用服务的线程,这种线程的优先级非常低;

    当所有其他线程结束时,会杀死进程中的所有守护线程;

    可以在线程启动之前通过setDaemon(true)方法将线程设置为守护线程,注意只能在启动之前设置;

    通过守护线程创建的线程会被自动设置为守护线程;

    可以通过isDaemon方法来判断一个线程是否是守护线程;

    举个守护线程的例子,代码如下,当main线程运行结束后,所有的守护线程也被终止:

    package concurrency;
    
    import java.util.concurrent.*;
    
    public class SimpleDaemons implements Runnable {
        public void run() {
            try {
                while (true) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread() + " " + this);
                }
            } catch (InterruptedException e) {
                System.out.println("sleep() interrupted");
            }
        }
    
        public static void main(String[] args) throws Exception {
            for (int i = 0; i < 10; i++) {
                Thread daemon = new Thread(new SimpleDaemons());
                daemon.setDaemon(true); // Must call before start()
                daemon.start();
                
            }
            System.out.println("All daemons started");
            TimeUnit.MILLISECONDS.sleep(175);
        }
    }

    加入一个线程Thread.join方法

    一个线程(T1)可以在其它线程(T2)之上调用join方法,结果是T1线程被挂起,等待T2线程执行完毕(T2.isAlive()==false),然后继续执行T1线程;

    也可以在join方法上加一个超时参数,保证join方法在指定时间内总能返回;

    join方法可以被中断,如调用T2.interrupt()方法,中断后,join方法可以立即返回;

    代码实例:

    package concurrency;
    
    class Sleeper extends Thread {
        private int duration;
    
        public Sleeper(String name, int sleepTime) {
            super(name);
            duration = sleepTime;
            start();
        }
    
        public void run() {
            try {
                sleep(duration);
            } catch (InterruptedException e) {
                System.out.println(getName() + " was interrupted. "
                        + "isInterrupted(): " + isInterrupted());
                return;
            }
            System.out.println(getName() + " has awakened");
        }
    }
    
    class Joiner extends Thread {
        private Sleeper sleeper;
    
        public Joiner(String name, Sleeper sleeper) {
            super(name);
            this.sleeper = sleeper;
            start();
        }
    
        public void run() {
            try {
                sleeper.join();
            } catch (InterruptedException e) {
                System.out.println("Interrupted");
            }
            System.out.println(getName() + " join completed");
        }
    }
    
    public class Joining {
        public static void main(String[] args) {
            Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper(
                    "Grumpy", 1500);
            Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc",
                    grumpy);
            grumpy.interrupt();
            
            try {
                sleepy.join();
            } catch (InterruptedException e) {
                
                e.printStackTrace();
            }
            System.out.println("main thread continue until sleepy thread over");
        }
    } 

    在该示例中,我们把dopey、main线程加入到sleepy线程,doc线程加入到grumpy线程,结果如下:

    grumpy线程被中断,然后join方法立即返回,打印Doc join completed,在grumpy线程中,isInterrupted()之所以打印false是因为异常捕获时把该标志清理了;

    sleepy线程执行完毕后,join方法返回,继续执行dopey线程和main线程未完成部分,打印“main thread continue until sleepy thread over”和“Dopey join completed”;

    捕获线程异常

    在main方法中使用try-catch不能捕获其它线程产生的异常,如下示例,RuntimeException未被处理:

    package concurrency;
    
    import java.util.concurrent.*;
    
    public class ExceptionThread implements Runnable {
        public void run() {
            throw new RuntimeException();
        }
    
        public static void main(String[] args) {
            try {
                ExecutorService exec = Executors.newCachedThreadPool();
                exec.execute(new ExceptionThread());
            } catch (RuntimeException ue) {
    
                System.out.println("Exception has been handled!");
            }
        }
    }

    在JAVA SE5之前,可以使用线程组捕获异常,在JAVA SE5之后可以用Executor来解决这个问题;

    只需要写一个异常处理类并实现Thread.UncaughtExceptionHandler接口,然后在创建线程的时候,设置该线程的未捕获异常处理器为该类实例,通过setUncaughtExceptionHandler方法设置,如下代码;

    package concurrency;
    
    import java.util.concurrent.*;
    
    class ExceptionThread2 implements Runnable {
        public void run() {
            Thread t = Thread.currentThread();
            System.out.println("run() by " + t);
            System.out.println("eh = " + t.getUncaughtExceptionHandler());
            throw new RuntimeException();
        }
    }
    
    class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("caught " + e);
        }
    }
    
    class HandlerThreadFactory implements ThreadFactory {
        public Thread newThread(Runnable r) {
            System.out.println(this + " creating new Thread");
            Thread t = new Thread(r);
            System.out.println("created " + t);
            t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
            System.out.println("eh = " + t.getUncaughtExceptionHandler());
            return t;
        }
    }
    
    public class CaptureUncaughtException {
        public static void main(String[] args) {
            ExecutorService exec = Executors
                    .newCachedThreadPool(new HandlerThreadFactory());
            exec.execute(new ExceptionThread2());
        }
    }
    View Code

    除了为每个线程设置专门的未捕获异常处理器外,还可以设置默认的未捕获异常处理器,当系统检查到某个线程没有专门的未捕获异常处理器的时候,会使用默认的未捕获异常处理器;

    package concurrency;
    
    import java.util.concurrent.*;
    
    public class SettingDefaultHandler {
      public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ExceptionThread());
      }
    } 

    参考资料:JAVA编程思想-4

  • 相关阅读:
    739. Daily Temperatures
    556. Next Greater Element III
    1078. Occurrences After Bigram
    1053. Previous Permutation With One Swap
    565. Array Nesting
    1052. Grumpy Bookstore Owner
    1051. Height Checker
    数据库入门及SQL基本语法
    ISCSI的概念
    配置一个IP SAN 存储服务器
  • 原文地址:https://www.cnblogs.com/chenpi/p/5306710.html
Copyright © 2011-2022 走看看