seeker2012

导航

[原创]Java并发编程(1):线程基础

(原创内容,转载请注明出处)

这篇博文整理了《think in Java 4th》这本书中的"Concurrency"一章的内容。

第一部分:线程基础

前言

  1. 并发编程是一种相对复杂的技术,下面的内容只是入门,不能认为理解了这里的所有内容,自己就成为了并发编程的高手;
  2. 并发的问题在于并行执行的任务可能会互相干扰,而这种干扰只有在微妙的特定情形下才会发生。这种情形发生的概率很小,而且有可能你都无法写出测试代码来模拟失败的条件。往往是由于客户投诉了某个问题,这时候才发现这种并发的错误;
  3. 并发编程充满了陷阱,只有兼具勇气和谨慎两种优良品质,才能够编写出可靠的并发程序;
  4. 了解并发编程的必要性在哪呢?j2ee的基础web库,以及GUI库都必须使用并发的知识才能更好的理解,因为它们是天然的多线程应用;了解并发可以帮助你辨别一些故障是不是由于没有处理好并发而导致的。

并发的好处

  1. 更高效。并发编程可以提高多处理器机器的吞吐能力,这是很显然的。在任务可能会阻塞的程序中,并发编程也可以提高单处理器机器的处理能力,在一个任务阻塞的时候,切换到另外一个任务来执行,这就提高了效率;如果没有任务会阻塞,那么并发编程不会对单处理器机器的处理速度有任何帮助,反而会带来切换执行环境的开销(overhead of switch context)。顺便补充一句,切换线程的执行环境需要的代码量(100行左右)远小于切换进程的执行环境需要的代码量(1000行左右)。并发编程提高单处理器系统的处理能力的一个例子就是事件驱动的程序。大部分时间里,等待事件的线程会阻塞,其它线程继续执行。等到有事件到来,等待事件的线程可以立即响应。这就是GUI应用程序的工作机制。
    1.  多进程与多线程:多进程实际上是在操作系统级别实现了并发,每一个进程运行在自己独立的地址空间,操作系统通过切换不同的进程来实现多任务运行。进程之间是完全隔离的,所以不会相互干扰。而多线程则不同, 它们运行在同一个进程的地址空间里,共享内存资源,会相互影响。 
  2. 代码设计更合理。对于仿真系统(比如游戏),往往有多个实体同时独立运行,只有并发才能解决这种问题。

Runnable接口

学习线程,从Runnable接口开始吧,看这个例子:

例子:Runnable接口的使用

Runnable接口的使用
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 "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!")
                + "), ";
    }

    public void run() {
        while (countDown-- > 0) {
            System.out.print(status());
            Thread.yield();
        }
    }
}

public class MoreBasicThreads {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++)
            new Thread(new LiftOff()).start();
        System.out.println("Waiting for LiftOff");
    }
}

 输出:

输出
Waiting for LiftOff
#2(9), 
#1(9), 
#0(9), 
#2(8), 
#1(8), 
#0(8), 
#2(7), 
#1(7), 
#0(7), 
#2(6), 
#1(6), 
#0(6), 
#2(5), 
#1(5), 
#0(5), 
#2(4), 
#1(4), 
#0(4), 
#2(3), 
#1(3), 
#0(3), 
#2(2), 
#1(2), 
#0(2), 
#2(1), 
#1(1), 
#0(1), 
#2(Liftoff!), 
#1(Liftoff!), 
#0(Liftoff!), 
#3(9), 
#3(8), 
#3(7), 
#3(6), 
#3(5), 
#3(4), 
#3(3), 
#3(2), 
#3(1), 
#3(Liftoff!), 
#4(9), 
#4(8), 
#4(7), 
#4(6), 
#4(5), 
#4(4), 
#4(3), 
#4(2), 
#4(1), 
#4(Liftoff!),

 说明:

  1. 例子中定义了一个实现runnable接口的类。在main方法中,把这个类的对象传递给Thread类的构造函数,并调用了Thread类的start方法。这是Thread的基本用法。
  2. 可以看到,main方法中首先输出了"Waiting for LiftOff",说明Thread类的start()方法是立即返回的,实际上它启动了和主线程(main方法所在的线程)并行执行的线程。
  3. 主线程执行完之后,整个程序并没有退出,而是等到所有线程都执行完成之后才退出。
  4. Thread.yield()方法告诉JVM的线程调度器(Thread Scheduler),当前线程已经完成主要任务,可以把时间片切换给其它线程了。它只是给JVM的一个建议,并不保证JVM会立即切换线程。

使用Executor类

  在Java SE 5/6中,推荐使用Executor类,它帮你管理Thread对象,简化了并发编程。ExecutorService提供了一个中间层,开发者只需要编写任务(task)的代码(实现Runnable接口的类)即可,不再需要管理执行任务的环境,这都由Executor来负责。

例子:使用ExecutorService

使用Executor
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();
    }
}

说明:

  1. shutdown()的含义是阻止向Executor提交新的任务。已提交的任务会执行完成。Executor中的所有任务都执行完成之后,整个程序才会退出。
  2. 下面介绍几种线程池:  

 

  • CachedThreadPool:只要需要,它就会申请新的线程。它也会重用已有的线程。这种线程池是开发中的首选。
  • FixedThreadPool: 它会预先分配固定数量的线程,这样提高了效率,同时可用的线程数量也是固定的。
  • SingleThreadExecutor: 它就像是数量为1的FixedThreadPool。同一个时刻只有一个任务在其中执行,任务按照提交的顺序来执行,它实际上管理了一个任务队列。这可用于顺序执行的任务。SingleThreadExecutor可以避免线程之间协作带来的共享资源冲突的各种问题。

 

获取任务的返回值

  只需要对前面的例子做一下修改,就能够获取任务执行的返回值。首先,实现Callable接口,而不是Runnable接口。其次,调用Executors的submit方法,它返回Future对象,从Future对象中可以获取任务的返回值。Future对象有下面的方法可以用于获取返回值:

  • isDone(): 判断任务是否已经执行完;
  • get(): 获取结果,如果任务还未执行完,该调用会阻塞;

例子:获取任务的返回值

获取任务的返回值
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 {
                // get() blocks until completion:
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                System.out.println(e);
                return;
            } catch (ExecutionException e) {
                System.out.println(e);
            } finally {
                exec.shutdown();
            }
    }
}

Sleep()

sleep()方法不推荐再使用Thread.sleep()这种方式,而是使用新的专用类TimeUnit:

TimeUnit.MILLISECONDS.sleep(100);

Sleep()方法会被打断,它会抛出异常:InterruptedException

线程优先级

先看下面的例子:

线程优先级
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimplePriorities implements Runnable {
    private int countDown = 5;
    private volatile double d; // No optimization
    private int priority;

    public SimplePriorities(int priority) {
        this.priority = priority;
    }

    public String toString() {
        return Thread.currentThread() + ": " + countDown;
    }

    public void run() {
        Thread.currentThread().setPriority(priority);
        while (true) {
            // An expensive, interruptable operation:
            for (int i = 1; i < 10000000; i++) {
                d += (Math.PI + Math.E) / (double) i;
                if (i % 1000 == 0)
                    Thread.yield();
            }
            System.out.println(this);
            if (--countDown == 0)
                return;
        }
    }

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++)
            exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
        exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
        exec.shutdown();
    }
}
输出
Thread[pool-1-thread-1,1,main]: 5
Thread[pool-1-thread-5,1,main]: 5
Thread[pool-1-thread-2,1,main]: 5
Thread[pool-1-thread-6,10,main]: 5
Thread[pool-1-thread-6,10,main]: 4
Thread[pool-1-thread-2,1,main]: 4
Thread[pool-1-thread-1,1,main]: 4
Thread[pool-1-thread-5,1,main]: 4
Thread[pool-1-thread-3,1,main]: 5
Thread[pool-1-thread-4,1,main]: 5
Thread[pool-1-thread-6,10,main]: 3
Thread[pool-1-thread-2,1,main]: 3
Thread[pool-1-thread-6,10,main]: 2
Thread[pool-1-thread-2,1,main]: 2
Thread[pool-1-thread-1,1,main]: 3
Thread[pool-1-thread-5,1,main]: 3
Thread[pool-1-thread-6,10,main]: 1
Thread[pool-1-thread-5,1,main]: 2
Thread[pool-1-thread-1,1,main]: 2
Thread[pool-1-thread-2,1,main]: 1
Thread[pool-1-thread-3,1,main]: 4
Thread[pool-1-thread-4,1,main]: 4
Thread[pool-1-thread-5,1,main]: 1
Thread[pool-1-thread-1,1,main]: 1
Thread[pool-1-thread-3,1,main]: 3
Thread[pool-1-thread-4,1,main]: 3
Thread[pool-1-thread-3,1,main]: 2
Thread[pool-1-thread-4,1,main]: 2
Thread[pool-1-thread-3,1,main]: 1
Thread[pool-1-thread-4,1,main]: 1

主要看一下上面的输出。第六个线程,优先级为MAX_PRIORITY,它最后一个执行,但是从结果上看,它要比其他线程更早执行完。其实,线程调度器在调度线程的时候,会更倾向于优先级更高的线程,优先级低的线程也会以相对较低的几率执行。在上面的代码中,还有一点需要说明:

Thread对象的toString方法输出的内容是:线程的名称,优先级,线程所属的线程组:

pool-1-thread-3,1,main

其中线程的名称就是pool-1-thread-3

守护线程

调用thread对象的setDaemon(true)方法,可以将线程设置为守护线程。守护线程被认为是非关键线程,它的生存原则是:

  •  只要程序中只剩下守护线程,则程序会终止,守护线程会被杀死
  •  相反,只要程序中存在非守护线程,则程序不会终止

看下面的例子:

例子:守护线程

守护线程
package com.quanquan.concurrency.thread;

import java.util.concurrent.*;
import static net.mindview.util.Print.*;

public class SimpleDaemons implements Runnable {
    public void run() {
        try {
            while (true) {
                TimeUnit.MILLISECONDS.sleep(100);
                print(Thread.currentThread() + " " + this);
            }
        } catch (InterruptedException e) {
            print("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();
        }
        print("All daemons started");
        TimeUnit.MILLISECONDS.sleep(175);
        print("over");
    }
}
输出
All daemons started
Thread[Thread-1,5,main] com.quanquan.concurrency.thread.SimpleDaemons@34a8a271
Thread[Thread-4,5,main] com.quanquan.concurrency.thread.SimpleDaemons@28f1bcde
Thread[Thread-9,5,main] com.quanquan.concurrency.thread.SimpleDaemons@1753d79c
Thread[Thread-6,5,main] com.quanquan.concurrency.thread.SimpleDaemons@2b40c3b9
Thread[Thread-7,5,main] com.quanquan.concurrency.thread.SimpleDaemons@2b40c3b9
Thread[Thread-8,5,main] com.quanquan.concurrency.thread.SimpleDaemons@15dbac11
Thread[Thread-2,5,main] com.quanquan.concurrency.thread.SimpleDaemons@4d12ee4f
Thread[Thread-3,5,main] com.quanquan.concurrency.thread.SimpleDaemons@7440d7b9
Thread[Thread-0,5,main] com.quanquan.concurrency.thread.SimpleDaemons@1704ebb
Thread[Thread-5,5,main] com.quanquan.concurrency.thread.SimpleDaemons@16a6a7d2
over

说明:

可以看到,只要主程序一结束,守护线程就会马上停止。守护线程的终止是一种”突然“的行为,你不会有任何机会来做一些善后工作。

Join

一个线程调用t.join的含义是,调用线程会挂起,等待t线程执行完,然后再继续执行。

捕获异常

如果一个异常跑出了任务的run()方法,就无法再捕获到它,它会一直传递到控制台。

看下面的例子:

例子:无法捕获的异常

捕获异常
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExceptionThread implements Runnable {
    public void run() {
        throw new RuntimeException();
    }

} 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NaiveExceptionHandling {
    public static void main(String[] args) {
        try {
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
        } catch (RuntimeException ue) {
            // This statement will NOT execute!
            System.out.println("Exception has been handled!");
        }
    }
}
输出
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
    at com.quanquan.concurrency.exception.ExceptionThread.run(ExceptionThread.java:8)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
    at java.lang.Thread.run(Thread.java:722)

上面的例子中,首先定义了一个Runnable实例,它抛出RuntimeException,main方法中的try-catch语句试图捕获异常,但是从结果上看,异常看是输出到了控制台,并没有被捕获上。

在Java SE5中,可以使用Executor来捕获异常。通过给线程附加一个Thread.UncaughtExceptionHandler对象,可以在发生异常时,调用其uncaughtException( )方法。如下面的例子:

View Code
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

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());
    }
}
输出
com.quanquan.concurrency.exception.HandlerThreadFactory@57f68d7c creating new Thread
created Thread[Thread-0,5,main]
eh = com.quanquan.concurrency.exception.MyUncaughtExceptionHandler@1df0a2a0
run() by Thread[Thread-0,5,main]
eh = com.quanquan.concurrency.exception.MyUncaughtExceptionHandler@1df0a2a0
com.quanquan.concurrency.exception.HandlerThreadFactory@57f68d7c creating new Thread
created Thread[Thread-1,5,main]
eh = com.quanquan.concurrency.exception.MyUncaughtExceptionHandler@79f5910e
caught java.lang.RuntimeException

上面的例子中,需要注意的就是MyUncaughtExceptionHandler类,它实现了Thread.UncaughtExceptionHandler接口。HandlerThreadFactory每生成一个Thread对象,都会为该对象绑定一个异常处理器:

t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());

从上面的输出中,还可以看到,Executor创建了2个线程,第二个线程不知道是做什么用的。我猜测它是用于捕获异常的,也就是捕获异常在单独的线程中运行。

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2013-03-01 23:27  seeker2012  阅读(252)  评论(0)    收藏  举报