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

  • 相关阅读:
    字符串样式效果
    javaScript 继承原型链
    javaScript 的继承,call()
    二维数组中的查找
    如何证明一个映射是混沌的。
    netty如何知道连接已经关闭,socket心跳,双工?异步?
    java中线程安全的map是ConcurrentHashMap
    failed to load class "org.sl4j.impl.StaticLoggerBinder"
    Tcp端口以及端口相关协议详解
    Tcp的三次握手,以及原理详解
  • 原文地址:https://www.cnblogs.com/chenpi/p/5306710.html
Copyright © 2011-2022 走看看