并发之一

本系列文章的目标就是让读者对并发的基础知识打下坚实的基础,从而理解其概念并编写出合理的多线程程序。

1 并发的多面性

并发的主要难点是:使用并发时需要解决的问题有多个,而实现并发的方式有多种,并且两者之间没有明显的映射关系。
使用并发解决的问题大体上分为“速度”和“设计可管理性”

2 基本的线程机制

2.1 定义线程任务

线程可以驱动任务,因此你需要一个驱动任务的方式,这个可以求Runnable接口来提供。要想定义任务只需要实现Runnable接口并编写run()方法,使得该任务可以执行你的命令。例如下面的LiftOff任务将显示发射之前的倒计时:

public class LiftOff implements Runnable{
    protected int countDwon = 10;
    protected static int taskCount = 0;
    private final int id = taskCount++;
    public LiftOff(){this.countDwon = countDwon;}
    public String status(){
        return "#"+id+"("+(countDwon > 0? countDwon:"LiftOff!")+"),";
    }
    @Override
    public void run() {
        while(countDwon-->0){
            System.out.println(status());
            Thread.yield();
        }
    }
}

标识符id可以用来区分任务的多个实例,是final的,创建后不会被修改。
run()方法通常是在一个循环体里,除非有某个条件终止run(),否则它将永久运行下去。
静态方法Thread.yield();是对调度器的一种声明:“现在是切换任务的时候了”。这样可以看到任务换进换出的证据。
下面的这个例子中的run()不是由单独的线程驱动的,它是有main方法直接调用的(main也是用了线程)。

public class MainThre {
    public static void main(String[] args) {
        new LiftOff().run();
    }
}
out#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(LiftOff!),

当从Runnable导出一个类时就必须使用run方法,但是这个方法没有特殊之处,它不会产生任务线程能力。要实现线程行为,你必须显示的将一个任务附着到线程上。

2.2 Thread类

将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器,下面的示例展示了如何使用Thread来驱动LiftOff对象。

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println("Waiting for LiftOff");
    }
}
out:(90% match)
Waiting for LiftOff
#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(LiftOff!),

Thread构造器接收一个Runnable对象。调用Thread对象start()方法后线程执行必要的初始化操作,然后Runnable调用run()方法,以便在这个新线程中启动该任务。start()调用后就返回了,因为Waiting for LiftOff先输出了,实质上一个线程调用了Runnable.run()方法,和main()同时执行。
我们可以很简单的添加更多的线程去驱动更多的任务。

public class MoreBasicThreads {


    public static void main(String[] args) {
        // TODO Auto-generated method stub
        for(int i = 0;i < 5;i++){
            new Thread(new LiftOff()).start();
        }
        System.out.println("Waiting for LiftOff");
    }

}
out:(60% match )
#0(9),#2(9),#4(9),#3(9),Waiting for LiftOff
#1(9),#1(8),#1(7),#1(6),#1(5),#1(4),#3(8),#4(8),#2(8),#0(8),#2(7),#2(6),#2(5),#2(4),#2(3),#2(2),#2(1),#2(LiftOff!),#4(7),#3(7),#3(6),#3(5),#3(4),#3(3),#3(2),#3(1),#3(LiftOff!),#1(3),#1(2),#1(1),#1(LiftOff!),#4(6),#0(7),#4(5),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(LiftOff!),#4(4),#4(3),#4(2),#4(1),#4(LiftOff!),

输出说明不同任务的执行在线程环进环出时混在一起。在main中创建Thread对象,它并没有捕获这些对象的引用,所以在调用start后这些线程任然存在。

2.3 使用Executor

java.util.concurrent中的执行器(executor)将为你管理Thread对象,来简化并发操作。executor在客户端和任务执行之间提供了一层间接层,这个中介对象将执行任务。
我们可以使用executor来代替MoreBasicThreads.java中显示的创建Thread对象。LiftOff对象知道如何运行具体的任务,他暴露了要执行的单一方法。ExecutorService(具有服务生命周期的executor,例如关闭)知道如何恰当的来执行Runnable对象。在下面的示例中CachedThreadPool将为每个任务创建一个线程。注意ExecutorService是使用静态的Executor方法创建的,这个方法可以确定Executor类型。

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

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();
    }
}
out#2(9),#2(8),#0(9),#3(9),#3(8),#3(7),#0(8),#0(7),#0(6),#0(5),#0(4),#1(9),#0(3),#0(2),#0(1),#0(LiftOff!),#3(6),#4(9),#2(7),#4(8),#3(5),#4(7),#1(8),#4(6),#3(4),#2(6),#3(3),#4(5),#1(7),#4(4),#3(2),#3(1),#3(LiftOff!),#2(5),#2(4),#2(3),#2(2),#2(1),#2(LiftOff!),#1(6),#4(3),#4(2),#4(1),#4(LiftOff!),#1(5),#1(4),#1(3),#1(2),#1(1),#1(LiftOff!),

对shutdown方法的调用可以防止新任务提交给这个Executor。这个程序将在Executor中的所有任务完成之后尽快退出。我们可以替换前面CachedThreadPool 中的Executor。FixedThreadPool使用了有限的线程集来完成提交的任务。

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

public class FixedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newFixedThreadPool(5);
        for(int i = 0;i <5;i++){
            exec.execute(new LiftOff());
        }
        exec.shutdown();
    }
}
out#1(9),#4(9),#4(8),#4(7),#4(6),#2(9),#1(8),#2(8),#1(7),#2(7),#1(6),#2(6),#1(5),#2(5),#1(4),#1(3),#1(2),#1(1),#1(LiftOff!),#2(4),#2(3),#2(2),#2(1),#2(LiftOff!),#3(9),#3(8),#0(9),#3(7),#3(6),#3(5),#3(4),#3(3),#3(2),#3(1),#3(LiftOff!),#4(5),#4(4),#4(3),#4(2),#4(1),#4(LiftOff!),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(LiftOff!),

有了FixedThreadPool你就可以一次性预先执行代价高昂的线程分配。
SingleThreadExecutor就像是线程数量为1的FixedThreadPool。

2.4 从任务中产生返回值

Runnable是执行工作的独立任务,但是它返回任何值。如果你希望在任务完成是返回一个值 ,那么可以实现Callable接口。Callable是一个具有类型参数的泛型,他的类型参数表示的是从方法call中返回的值。并且必须使用ExecutorService.submit()方法调用它。

class TaskWithResult implements Callable<String>{
    private int id;
    public TaskWithResult(int id){
        this.id = id;
    }
    @Override
    public String call() throws Exception {
        return "Result of TaskWithResult"+id;
    }   
}
public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> result = new ArrayList<Future<String>>();
        for(int i = 0;i < 10;i++){
            result.add(exec.submit(new TaskWithResult(i)));
        }
        for(Future<String> fs :result)
            try{
                System.out.println(fs.get());
            }catch(InterruptedException e){
                System.out.println(e);
            } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                System.out.println(e);
            }finally{
                exec.shutdown();
            }
    }
}
out:
Result of TaskWithResult0
Result of TaskWithResult1
Result of TaskWithResult2
Result of TaskWithResult3
Result of TaskWithResult4
Result of TaskWithResult5
Result of TaskWithResult6
Result of TaskWithResult7
Result of TaskWithResult8
Result of TaskWithResult9

可以使用isDone()方法查询Futura是否已经完成。也可以直接使用get()方法来获取结果。

2.5 休眠

影响任务行为的一种简单方法是调用sleep(),使任务终止给定的时间。我们使用LiftOff做实验,吧yield()换成sleep(),得到如下结果。

public class SleepingTask extends LiftOff{
    public void run(){
        try{
            while(countDwon-->0){
                System.out.print(status());
                TimeUnit.MILLISECONDS.sleep(100);
            }   
        }catch(InterruptedException e){
            System.err.println(e);
        }       
    }
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0;i<5;i++){
            exec.execute(new SleepingTask());
        }
        exec.shutdown();
    }
}
out#1(9),#3(9),#2(9),#4(9),#0(9),#4(8),#2(8),#0(8),#3(8),#1(8),#4(7),#2(7),#0(7),#3(7),#1(7),#4(6),#2(6),#3(6),#0(6),#1(6),#4(5),#2(5),#3(5),#0(5),#1(5),#0(4),#2(4),#4(4),#3(4),#1(4),#0(3),#4(3),#2(3),#3(3),#1(3),#0(2),#2(2),#4(2),#3(2),#1(2),#2(1),#4(1),#0(1),#3(1),#1(1),#0(LiftOff!),#4(LiftOff!),#2(LiftOff!),#3(LiftOff!),#1(LiftOff!),

对sleep()调用会产生InterruptedException异常所以在run()中捕获,因为异常不能夸线程传播给main()。所以必须在本地处理所有异常。

2.6 线程的优先级

线程的优先级将该线程的重要性传递给调度器。在大多数时间里,所有的线程都以默认的优先级运行,试图操作线程优先级似乎是种错误。下面是一个示例,你可以使用getPriority()来读取现有线程的优先级,使用setPriority()修改它。

public class SimplePriorities implements Runnable{
    private int countDown = 5;
    private volatile double d;
    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)
        {
            for(int i = 0;i < 100000;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();
    }
}
out:
Thread[pool-1-thread-3,1,main]:5
Thread[pool-1-thread-2,1,main]:5
Thread[pool-1-thread-1,1,main]:5
Thread[pool-1-thread-5,1,main]:5
Thread[pool-1-thread-6,10,main]:5
Thread[pool-1-thread-4,1,main]:5
Thread[pool-1-thread-6,10,main]:4
Thread[pool-1-thread-3,1,main]:4
Thread[pool-1-thread-6,10,main]:3
Thread[pool-1-thread-3,1,main]:3
Thread[pool-1-thread-2,1,main]:4
Thread[pool-1-thread-4,1,main]:4
Thread[pool-1-thread-1,1,main]:4
Thread[pool-1-thread-6,10,main]:2
Thread[pool-1-thread-5,1,main]:4
Thread[pool-1-thread-3,1,main]:2
Thread[pool-1-thread-6,10,main]:1
Thread[pool-1-thread-3,1,main]:1
Thread[pool-1-thread-1,1,main]:3
Thread[pool-1-thread-5,1,main]:3
Thread[pool-1-thread-2,1,main]:3
Thread[pool-1-thread-4,1,main]:3
Thread[pool-1-thread-5,1,main]:2
Thread[pool-1-thread-4,1,main]:2
Thread[pool-1-thread-2,1,main]:2
Thread[pool-1-thread-1,1,main]:2
Thread[pool-1-thread-4,1,main]:1
Thread[pool-1-thread-5,1,main]:1
Thread[pool-1-thread-2,1,main]:1
Thread[pool-1-thread-1,1,main]:1

toString()被覆盖用来打印当前线程的名称,线程的优先级以及线程所属的线程组。如pool-1-thread-1,1;
volatile的修饰是防止编译器的优化。JDK有10个优先级但是与操作系统的映射不是太好,如Windows有7个优先级且不固定,唯一可移植的方法是使用Thread.MIN_PRIORITY,Thread.MAX_PRIORITY,Thread.NORM_PRIORITY。

2.7 让步

使用yield()方法让出线程,但是对于任何重要的控制或在调整应用时,都不能依赖于yield()方法。实际上yield()经常被误用。

2.8 后台线程

后台线程:在程序运行的时候在后台提供一种通用服务的线程,并且这个线程并不是不可或缺的。当非后台线程结束了程序也就终止了,后台线程也就终止了。

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(e);
        }       
    }   
    public static void main(String[] args)throws Exception {
        for(int i = 0;i<10;i++)
        {
            Thread daemon = new Thread(new SimpleDaemons());
            daemon.setDaemon(true);
            daemon.start();
        }
        System.out.println("all daemons start");
        TimeUnit.MILLISECONDS.sleep(175);
    }
}
out:
all daemons start
Thread[Thread-6,5,main]:concurrency.SimpleDaemons@689751f
Thread[Thread-8,5,main]:concurrency.SimpleDaemons@7e00f258
Thread[Thread-9,5,main]:concurrency.SimpleDaemons@1326c148
Thread[Thread-4,5,main]:concurrency.SimpleDaemons@41873c1
Thread[Thread-2,5,main]:concurrency.SimpleDaemons@13d8ff19
Thread[Thread-7,5,main]:concurrency.SimpleDaemons@70f04fe5
Thread[Thread-0,5,main]:concurrency.SimpleDaemons@5cb1e5a9
Thread[Thread-3,5,main]:concurrency.SimpleDaemons@50adf629
Thread[Thread-5,5,main]:concurrency.SimpleDaemons@5ef7355a
Thread[Thread-1,5,main]:concurrency.SimpleDaemons@7897ae54

在调用之前要使用daemon.setDaemon(true);设置为后台线程。main完成其工作就没有什么能阻止程序终止了。
下面通过编写定制的ThreadFactory可以定制有Executor创建的线程属性(后台、优先级、名称)。

import java.util.concurrent.ThreadFactory;

public class DaemonThreadFactory implements ThreadFactory {
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}

它将后台程序全部设置为true,DaemonThreadFactory 作为参数传递给Executors.newCachedThreadPool():

public class DaemonFromFactory implements Runnable  {
    public void run() {
        try{
            while(true)
            {
                TimeUnit.MILLISECONDS.sleep(100);
                System.out.println(Thread.currentThread()+" "+this);
            }
        }catch(InterruptedException e)
        {
            System.out.println("Interrupted");
        }       
    }
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
        for(int i = 0;i < 10;i++)
        {
            exec.execute(new DaemonFromFactory());

        }
        System.out.println("all daemons start");
        TimeUnit.MILLISECONDS.sleep(500);
    }
}

每个静态的ExecutorService 方法被重载为一个接收ThreadFactory 对象,而这个对象将被用来创建新的线程。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DaemonThreadPoolExecutor extends ThreadPoolExecutor{
    public DaemonThreadPoolExecutor() {
        super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        new DaemonThreadPoolExecutor();
    }
}

可以调用isDaem()方法来确定线程是否是后台线程。如果是后台线程那么创建它的任何线程都是后台线程。

import java.util.concurrent.TimeUnit;

class Daemon implements Runnable{
    private Thread[] t = new Thread[10];    
    public void run() {
        for(int i = 0;i < t.length;i++)
        {
            t[i] = new Thread(new DaemonSpawn());
            t[i].start();
            System.out.println("DaemonSpawn"+i+" started,");
        }
        for(int i = 0;i < t.length;i++)
        {
            System.out.println("t["+i+"].isDaemon() = "+t[i].isDaemon()+", ");
        }
        while(true)
        {
            Thread.yield();
        }       
    }   
}
class DaemonSpawn implements Runnable{
    public void run() {
        while(true)
        {
            Thread.yield();
        }       
    }   
}
public class Daemons {
    public static void main(String[] args) throws Exception {
        Thread d = new Thread(new Daemon());
        d.setDaemon(true);
        d.start();
        System.out.println("d.isDaemon() = "+d.isDaemon()+", ");
        TimeUnit.SECONDS.sleep(1);
    }
}
out:
d.isDaemon() = true, 
DaemonSpawn0 started,
DaemonSpawn1 started,
DaemonSpawn2 started,
DaemonSpawn3 started,
DaemonSpawn4 started,
DaemonSpawn5 started,
DaemonSpawn6 started,
DaemonSpawn7 started,
DaemonSpawn8 started,
DaemonSpawn9 started,
t[0].isDaemon() = true, 
t[1].isDaemon() = true, 
t[2].isDaemon() = true, 
t[3].isDaemon() = true, 
t[4].isDaemon() = true, 
t[5].isDaemon() = true, 
t[6].isDaemon() = true, 
t[7].isDaemon() = true, 
t[8].isDaemon() = true, 
t[9].isDaemon() = true, 

你应该意识到后台进程在不执行finally子句的情况下就会终止其run方法。

class ADaemon implements Runnable{
    public void run() {
        try{
            println("Starting ADaemon");
            TimeUnit.SECONDS.sleep(1);
        }catch(InterruptedException e)
        {
            println("Exiting via InterruptedException");

        }
        finally{
            println("This should always run?");
        }       
    }
}
public class DaemonDontRunFinally {
    public static void main(String[] args) {
        Thread t = new Thread(new ADaemon());
        t.setDaemon(true);
        t.start();
    }
}

当我点击运行的时候么有输出结果就是说finally没有执行,但是但我注释t.setDaemon(true);后就会输出Starting ADaemon
This should always run?
就是说finally执行了。

2.9 编码的变体

到目前为止任务都实现了Runnable。在非常简单的情况下,你可能会希望使用直接从Thread继承这种可替代的方式。

public class SimpleThread extends Thread{
    private int countDown = 5;
    private static int threadCount = 0;
    public SimpleThread()
    {
        super(Integer.toString(++threadCount));
        start();
    }
    public String toString()
    {
        return "#"+getName()+"("+countDown+"), ";
    }
    public void run()
    {
        while(true)
        {
            System.out.print(this);
            if(--countDown == 0)
                return;
        }
    }
    public static void main(String[] args) {
        for(int i = 0;i< 5;i++)
        {
            new SimpleThread();
        }
    }
}
out:
#1(5), #1(4), #1(3), #1(2), #1(1), #3(5), #3(4), #3(3), #3(2), #3(1), #5(5), #5(4), #5(3), #5(2), #5(1), #2(5), #2(4), #2(3), #2(2), #2(1), #4(5), #4(4), #4(3), #4(2), #4(1),  

可以通过调用Thread构造器为Thread对象赋予具体的名称。这个名称可以使用getName()从toString()中获得。
另一种可能会看到的惯用法是自管理的Runnable:

public class SelfManaged implements Runnable{
    private int countDown = 5;
    private Thread t = new Thread(this);
    public SelfManaged() {
        t.start();
    }
    public String toString()
    {
        return Thread.currentThread().getName()+"("+countDown+"), ";
    }
    public void run() {
        while(true)
        {
            System.out.print(this);
            if(--countDown == 0)
                return;
        }       
    }
    public static void main(String[] args) {
        for(int i = 0;i < 5;i++)
            new SelfManaged();

    }
}
out:
Thread-0(5), Thread-0(4), Thread-0(3), Thread-0(2), Thread-0(1), Thread-1(5), Thread-1(4), Thread-1(3), Thread-1(2), Thread-1(1), Thread-4(5), Thread-2(5), Thread-2(4), Thread-2(3), Thread-2(2), Thread-2(1), Thread-4(4), Thread-4(3), Thread-4(2), Thread-4(1), Thread-3(5), Thread-3(4), Thread-3(3), Thread-3(2), Thread-3(1), 

这与从Thread继承并没有什么差别。在构造器中启动线程可能会变得很有问题,因为另一个任务可能会在构造器结束之前执行,这意味着该任务能够访问处于不稳定状态的对象。所以我们优选Executor。
有时候使用内部类将线程代码隐藏在类中可能会更有用。

package concurrency;

import java.util.concurrent.TimeUnit;

import inner_class.DotThis.Inner;
import static net.lijun.util.Print.*;

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

    }
    public InnerRunnable1(String name)
    {
        inner = new Inner(name);
    }
}
class InnerRunnable2
{
    private int countDown = 5;
    private Thread t;   
    public InnerRunnable2(String name) {
        t = new Thread(new Runnable(){
            public void run()
            {
                try
                {
                    while(true)
                    {
                        println(this);
                        if(--countDown == 0)return;
                        TimeUnit.MILLISECONDS.sleep(10);                    
                    }
                }
                catch(InterruptedException e)
                {
                    println("sleep() interrupted");
                }
            }
            public String toString()
            {
                return Thread.currentThread().getName()+": "+countDown;
            }
        },name);
        t.start();
    }
}
class ThreadMethod
{
    private int countDown = 5;
    private Thread t;
    private String name;
    public ThreadMethod(String name) {
        this.name = name;
    }
    public void runTask()
    {
        if(t == null)
        {
            t = new Thread(name){
                public void run()
                {
                    try
                    {
                        while(true)
                        {
                            println(this);
                            if(--countDown == 0)return;
                            sleep(10);                  
                        }
                    }
                    catch(InterruptedException e)
                    {
                        println("sleep() interrupted");
                    }
                }
                public String toString()
                {
                    return Thread.currentThread().getName()+": "+countDown;
                }
            };
            t.start();
        }
    }

}
public class ThreadVariations 
{
    public static void main(String[] args) {
        new InnerThread1("InnerThread1");
        new InnerThread2("InnerThread2");
        new InnerRunnable1("InnerRunnable1");
        new InnerRunnable2("InnerRunnable2");
        new ThreadMethod("ThreadMethod").runTask();

    }

}
out:
InnerThread1:5
InnerThread2: 5
InnerRunnable2: 5
ThreadMethod: 5
InnerThread2: 4
InnerThread1:4
InnerRunnable2: 4
ThreadMethod: 4
InnerThread2: 3
InnerThread1:3
InnerRunnable2: 3
ThreadMethod: 3
InnerThread2: 2
InnerRunnable2: 2
InnerThread1:2
ThreadMethod: 2
InnerThread1:1
InnerThread2: 1
InnerRunnable2: 1
ThreadMethod: 1

2.10 术语

搞清楚任务和线程的区别:

2.11 加入一个线程

一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复。
也可以在调用join方法时带上一个超时参数,这样如果目标线程在这段时间还没有结束的话,join方法总能返回。
列:

package concurrency;
import static net.lijun.util.Print.*;
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)
        {
            println(getName()+" was interrupted"+"isInterrupted: "+isInterrupted());
        }
        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)
        {
            println("Interrupted");
        }
        println(getName()+" join completed");
    }

}
public class Joining extends Thread{
    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();
    }

}
out:
Grumpy was interruptedisInterruptedfalse
Grumpy has awakened
Doc join completed
Sleepy has awakened
Dopey join completed

Sleeper是一个Thread类型,通过构造器传递的参数休眠一段时间。在run方法里面sleep方法有可能在指定的时间期满时返回,也可能被中断。在catch子句中将根据isInterrupted的返回值报告这个中断。当另一个线程在该线程上调用interrupt时,将给该线程设定一个标志,表明该线程已经被中断,然而异常被捕获时将清理这个标志,所以在catch子句中,在异常被捕获的时候这个标志总是为假。
Joiner线程将通过在Sleeper线程上调用join()方法来等待唤醒。

2.12 创建有响应的用户界面

以前说过创建线程的动机之一就是建立有响应的用户界面。下面给处创建用户界面的例子,有两个,一个关注与运算,所以不能读取控制台输入,另一个吧运算放在一个线程里单独运行,此时就可以在运算的同时监听控制台输入。

package concurrency;

import java.io.IOException;

class UnresponsiveUI {
    private volatile double d = 1;
    public UnresponsiveUI() throws Exception {
        while(d > 0){
            d += (Math.PI+Math.E)/d;
            System.in.read();
        }
    }
}
public class ResponsiveUI extends Thread {
    private static volatile double d = 1;
    public ResponsiveUI() {
        setDaemon(true);
        start();
    }
    public void run()
    {
        while(true)
        {
            d += (Math.PI+Math.E)/d;
        }

    }
    public static void main(String[] args) throws Exception {
        new ResponsiveUI();
        System.in.read();
        System.out.println(d);
    }
}

可以看出只有第二种方法程序才会等待输入。

2.13 线程组

线程组持有一个线程集合。在这里尽管忽略它。

2.14 捕获异常

由于线程的本质特征,使得你不能捕获从线程中逃逸的异常。一旦异常逃出run()方法,他就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常,可以使用Executor来解决问题。
下面的任务总会抛出一个异常,该异常会传播到run()方法的外部,并且main()展示了当你运行它是发生的事情。

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

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

    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new ExceptionThread());
    }
}
out:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
    at concurrency.ExceptionThread.run(ExceptionThread.java:8)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

如果将main()的主题放在try-catch 中是么有作用的。输入和上面的一样。
为了解决这个问题我们要修改Executor产生线程的方式。
(不懂)

3 共享受限资源

3.1 不正确 的访问资源

下面我们举个例子,其中一个任务产生偶数,而其他的任务消费这些数字,这里消费者任务的唯一工作室检查偶数的有效性。
首先我们定义EvenChecker,即消费者任务,因为它将在随后的所有示例被复用。为了将EvenChecker与我们要实验的各种类型的生成器解耦,我们将创建一个IntGenerator的抽象类,它包含EvenChecker必须了解的所有方法:一个next()方法和一个可以撤销的方法。

public abstract class IntGenerator {
    private volatile boolean canceled = false;
    public abstract int next();
    //可以被撤销
    public void cancel(){canceled = true;}
    public boolean isCancel(){
        return canceled;
    }

}

IntGenerator有个cancel()方法和isCancel()方法。
任何的IntGenerator都可以用下面的EvenChecker类来测试。

package concurrency;

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

public class EvenChecker implements Runnable {
    private IntGenerator generator;
    private final int id;
    public EvenChecker(IntGenerator g,int ident) 
    {
        generator = g;
        id = ident;
    }
    public void run() 
    {
        while(!generator.isCancel())
        {
            int val = generator.next();
            if(val %2 != 0)
            {
                System.out.println(val+"not even!");
                generator.cancel();
            }
        }       
    }
    public static void test(IntGenerator gp,int count)
    {
        System.out.println("Press Control-C to exit");
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0; i< count;i++)
        {
            exec.execute(new EvenChecker(gp,i));

        }
        exec.shutdown();
    }

    public static void test(IntGenerator gp)
    {
        test(gp,10);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub

    }

}

注意:本类中可以被撤销的类不是Runnable,而是所依赖
于IntGenerator对象的EvenChecker任务来检查它。以查看它是否已经被撤销,正如你在run中所见。通过这种方式共享公共资源(IntGenerator)的任务可以观察该资源的终止信号。这可以消除所谓的竞争条件,即两个或者更多的任务竞争响应某个条件,因此产生冲突或者不一致的情况。你必须仔细考虑并防范并发系统失败的所有可能路径,例如一个任务不能依赖于另一个任务,因为任务关闭的顺序无法保证。这里通过使任务依赖于非任务对象,可以消除潜在的竞争条件。
test()通过启动大量使用相同的IntGenerator的EvenChecker,设置并执行对任何类型的IntGenerator的测试。如果IntGenerator引发失败,那么test()将报告并返回它,否则必须使用control-C来终止。
EvenChecker任务总是读取和测试从与其相关的IntGenerator返回值。
如果generator.isCancel()返回为true,run返回,任何EvenChecker任务都可以在与其相关联的IntGenerator上调用cancel(),这将导致所有其他使用IntGenerator的EvenChecker得本地关闭。以后章节将看到java终止线程的各种通用机制。
我们看到第一个IntGenerator有一个可以产生一系列偶数的next()方法。

public class EvenGenerator extends IntGenerator {

    private int currentEvenValue = 0;
    public int next() {
        ++currentEvenValue;
        Thread.currentThread().yield();
        ++currentEvenValue;
        return currentEvenValue;
    }

    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator()); 

    }
}
out:
Press Control-C to exit
403not even!
281not even!
407not even!
405not even!

为了能快速看到结果我们加入了Thread.currentThread().yield();可以看到这个程序最终是失败的,因为在Thread.currentThread().yield();执行后别的线程可能会执行操作导致失败。还有就是在java中递增操作也不是原子性的,也就是说需要多个步骤,学习过C得应该能理解。

3.2 解决共享资源竞争

java提供关键字synchronized的形式,为防止资源冲突提供支持。
把要访问资源的方法标记为synchronized来防止资源冲突。
如:

synchronized void f(){}
synchronized void g(){}

注意使用并发时,将域设置为private是非常重要的,否则synchronized 将不能防止其他任务直接访问域,这样就会产生冲突。
你应该什么时候使用同步呢:
如果你正在写一个变量,他可能接下来被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写都必须使用相同的监视器锁同步。
很重要的一点:每个访问临界共享资源的方法都必须使用同步。
同步EvenGenerator:

public class EvenGenerator extends IntGenerator {

    private int currentEvenValue = 0;
    public synchronized  int next() {
        ++currentEvenValue;
        Thread.yield();
        ++currentEvenValue;
        return currentEvenValue;
    }
    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator()); 
    }
}

使用显示的Lock对象
Lock必须被显示的创建,锁定和释放,所以与内键的锁的形式,代码缺乏优雅性,但是对于解决某些问题时它更加灵活。
例:

public class MutexEvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;
    private Lock lock = new ReentrantLock();    
    public int next() {
        lock.lock();        
        try
        {
            ++currentEvenValue;
            Thread.yield();
            ++currentEvenValue;
            return currentEvenValue;
        }
        finally
        {
            lock.unlock();
        }       

    }
    public static void main(String[] args) {
        EvenChecker.test(new MutexEvenGenerator()); 

    }
}

MutexEvenGenerator里添加了一个被互斥调用的锁,并使用lock()和unlock()在next()内部创建了临界资源。return必须在try里面保证finally不会过早发生。有了finally语句可以将系统维护在正常的状态。
使用synchronized 相对简单很多,所以只有在复杂的情况下才使用lock对象。
例如:使用synchronized 你不能获取锁,或者尝试着获取锁一段时间然后放弃他;

package concurrency;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.management.RuntimeErrorException;



public class AttemptLocking {

    private Lock lock = new ReentrantLock();
    public void Untimed()
    {
        boolean captured = lock.tryLock();
        try{
            System.out.println("tryLock()"+captured);

        }finally{
            if(captured)
                lock.unlock();
        }
    }
    public void timed()
    {
        boolean captured = false;
        try
        {
            captured = lock.tryLock(2,TimeUnit.SECONDS);
        }catch(InterruptedException e)
        {
            throw new RuntimeException(e);
        }
        try
        {
            System.out.println("tryLock(2,TimeUnit.SECONDS)"+captured);

        }
        finally{
            if(captured)
                lock.unlock();
        }
    }
    public static void main(String[] args) {
        final AttemptLocking al = new AttemptLocking();
        al.Untimed();
        al.timed();
        new Thread(){
            {setDaemon(true);}
            public void run(){
                al.lock.lock();
                System.out.println("acquired");
            }
        }.start();
        Thread.yield();
        try{
            Thread.sleep(1500);
        }catch(InterruptedException e)
        {
            System.out.println();
        }
        al.Untimed();
        al.timed(); 

    }
}
out:
tryLock()true
tryLock(2,TimeUnit.SECONDS)true
acquired
tryLock()false
tryLock(2,TimeUnit.SECONDS)false

3.3 原子性与易变性

3.4 原子类

Java中引入了诸如:AtomicInterger,AtomicLong,AtomicReference等特殊的原子性变量类,他们提供了下面形式的原子性条件更新操作:
boolean compareAndSet(expectedValue, updateValue);
这些类被调整为可以使用在某些现代处理器上的课获得的,并且是机器级别的原子性,因此通常在使用它们时不用担心。一般用在性能调优上。

public class AtomicIntegerTest implements Runnable {

    private AtomicInteger i = new AtomicInteger(0);
    public int getValue(){return i.get();}
    private void evenIncrement(){i.addAndGet(2);}
    public void run() {
        while(true)
        {
            evenIncrement();            
        }       
    }
    public static void main(String[] args) {
        new Timer().schedule(new TimerTask(){
            public void run() {
                System.err.println("Aborting");
                System.exit(0);

            }

        }, 5000);
        ExecutorService exec = Executors.newCachedThreadPool();
        AtomicIntegerTest ait = new AtomicIntegerTest();
        exec.execute(ait);
        while(true)
        {
            int val = ait.getValue();
            if(val%2 != 0)
            {
                System.out.println(val);
                System.exit(0);
            }
        }

    }

}

这里使用了AtomicInteger 就消除了对synchronized的使用,这个程序不会失败。

3.5 临界区

有时,你只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。通过这种方式分离出来的代码段称为临界区,它也使用synchronized关键字来建立。这里synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制:

synchronized(syncObject){
//这里面的代码某一时间只能被一个线程访问
}

这也被称为同步控制块;在进入此段代码钱必须得到syncObject的锁,下面对同步方法和同步控制块做个比较,此外还演示了如何把一个非保护类型的类,在其它类的保护和控制下,应用于多线程环境。

package concurrency;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class Pair{
    private int x,y;
    public Pair(int x,int y){
        this.x = x;
        this.y = y;
    }
    public Pair(){this(0,0);}
    public int getX(){return x;}
    public int getY(){return y;}
    public void incrementX(){x++;}
    public void incrementY(){y++;}
    public String toString(){
        return "x:"+x+",y:"+y;
    }
    public class PairValuesNotEqualException extends RuntimeException{
        public PairValuesNotEqualException(){
            super("Pair value not equal:"+Pair.this);
        }
    }
    public void checkState(){
        if(x != y){throw new PairValuesNotEqualException();}
    }
}
abstract class PairManager{
    AtomicInteger checkCounter = new AtomicInteger(0);
    protected Pair p = new Pair();
    private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>());
    public synchronized Pair getPair(){ return new Pair(p.getX(),p.getY());}
    protected void store(Pair p){
        storage.add(p);
        try{
            TimeUnit.MILLISECONDS.sleep(50);

        }catch(InterruptedException e){}
    }
    public abstract void increment();
}
class PairManager1 extends PairManager{
    @Override
    public synchronized void increment() {
        // TODO Auto-generated method stub
        p.incrementX();
        p.incrementY();
        store(getPair());
    }
}
class PairManager2 extends PairManager{
    @Override
    public void increment() {
        Pair temp;
        synchronized(this){
            p.incrementX();
            p.incrementY();
            temp = getPair();
        }
        store(temp);
    }
}
class PairManipulator implements Runnable{
    private PairManager pm;
    public PairManipulator(PairManager pm) {
        this.pm = pm;
    }
    public void run() {
        while(true){
            pm.increment();
        }       
    }
    public String toString(){
        return "Pair:"+pm.getPair()+"CheckCounter = "+pm.checkCounter.get();
    }
}
class PairChecker implements Runnable{
    private PairManager pm;
    public PairChecker(PairManager pm) {
        this.pm = pm;
    }
    public void run() {
        while(true){
            pm.checkCounter.incrementAndGet();
            pm.getPair().checkState();
        }

    }
}
public class CriticalSection {

    static void
    testApproaches(PairManager pman1,PairManager pman2)
    {
        ExecutorService exec = Executors.newCachedThreadPool();
        PairManipulator 
            pm1 = new PairManipulator(pman1),
            pm2 = new PairManipulator(pman2);
        PairChecker
            pchecker1 = new PairChecker(pman1),
            pchecker2 = new PairChecker(pman2);
        exec.execute(pm1);
        exec.execute(pm2);
        exec.execute(pchecker1);
        exec.execute(pchecker2);
        try{
            TimeUnit.MILLISECONDS.sleep(500);

        }catch(InterruptedException e){
            System.out.println("Sleep interrupted");
        }
        System.out.println("pm1:"+pm1+"\npm2:"+pm2);
        System.exit(0);

    }
    public static void main(String[] args) {
        PairManager
            pman1 = new PairManager1(),
            pman2 = new PairManager2();
        testApproaches(pman1, pman2);
    }
}

正如前面看到的,Pair不是线程安全的,因为他的约束条件需要两个变量要维护成相同的值。
给你一个非线程安全的类,而你需要一个线程安全的环境,通过创建PairManager类就可以实现这一点,至于PairManager中的一些方法在派生类总实现,这种设计叫做模板方法,在PairManager1中整个incement()方法是被同步控制的。PairManager2中increment()方法被同步控制块控制。
store() 方法将一个Pair对象添加到Synchronized ArrayList中所以是线程安全的。
PairManipulator创建用来测试两种不同类型的PairManager,其方法是在某个任务中调用increment(),而PairChecker则在另一个人物中执行,为了可以跟踪可以测试的速度PairChecker在每次成功时都递增checkCounter。在main中创建了两个PairManipulator对象,并允许他们运行一段时间,之后每个PairManipulator会显示运行结果。
尽管每次运行结果不同但是每次PairManager1.increment()不会有PairManager2.increment()那么多。这是使用同步控制块的经典理由。
你还可以使用显示的Lock对象来创建临界区。

class ExplicitPairManager1 extends PairManager{
    private Lock lock = new ReentrantLock();
    public synchronized void increment() {
        lock.lock();
        try{
            p.incrementX();
            p.incrementY();
            store(getPair());
        }finally{
            lock.unlock();
        }

    }
}
class ExplicitPairManager2 extends PairManager{
    private Lock lock = new ReentrantLock();
    public void increment() {
        Pair temp;
        lock.lock();
        try{
            p.incrementX();
            p.incrementY();
            temp = getPair();
        }finally{
            lock.unlock();
        }
        store(temp);
    }
}
public class ExplicitCriticalSection {
    public static void main(String[] args) {
        PairManager
            pman1 = new ExplicitPairManager1(),
            pman2 = new ExplicitPairManager2();
        CriticalSection.testApproaches(pman1, pman2);

    }

}

3.6 在其他对象上同步

synchronized块必须给定一个在其上进行同步的对象,最合理的方式是synchronized(this),这正是PairManager2使用的方式。在这种方式中,如果获得了synchronized块上的锁,那么该对象其他的synchronized方法和临界区就不能被调用了。因此如果在this上同步,临界区的的效果就会直接缩小在同步的范围内。
有时必须要另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步的。下面演示了两个任务可以同时进入同一个对象,只要这个对象上的方法是在不同的锁上同步的即可:

class DualSynch{
    private Object synchObject = new Object();
    public synchronized void f(){
        for(int i = 0;i < 5;i++)
        {
            println("f()");
            Thread.yield();
        }
    }
    public void g(){
        synchronized(SyncObject.class){
            for(int i = 0;i < 5;i++)
            {
                println("g()");
                Thread.yield();
            }
        }
    }
}
public class SyncObject {

    public static void main(String[] args) {
        final DualSynch ds = new DualSynch();
        new Thread(){
            public void run(){
                ds.f();
            }
        }.start();
        ds.g();
    }
}
out:
g()
f()
f()
f()
f()
f()
g()
g()
g()
g()

DualSynch.f()在this上同步,而g()有一个在SyncObject上同步的synchronized块。因此这另个同步是相互独立的。在main中创建Thread对这一点进行了演示,因为main线程是用买调用ds.g();的,从输出结果可以看到这两个方式是同时运行的,因此任何一个方法都么有因为对另一个方法的通过而阻塞。

3.7 线程本地存储

防止任务在线程上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同测线程。因此如果你有5个线程都要使用变量X所表示的对象,那线程本地存储就会生成5个用于X的不同的存储块。主要是,他们可以使的你可以将状态与线程关联起来。
创建和管理线程本地存储都可以由 java.lang.ThreadLocal类来实现。

package concurrency;

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

class Accessor implements Runnable{
    private final int id;
    public Accessor(int id) {
        this.id = id;
    }
    public void run() {
        while(!Thread.currentThread().isInterrupted()){
            ThreadLocalVariableHolder.increment();
            System.out.println(this);
            Thread.yield();
        }

    }
    public String toString(){
        return "#"+id+":"+ThreadLocalVariableHolder.get();
    }
}
public class ThreadLocalVariableHolder {

    private static ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
        private Random rand = new Random(47);
        protected synchronized Integer initialValue(){
            return rand.nextInt(10000);
        }
    };
    public static void increment(){
        value.set(value.get()+1);
    }
    public static int get(){return value.get();}
    public static void main(String[] args) throws Exception{
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0;i < 5;i++)
            exec.execute(new Accessor(i));
        TimeUnit.MILLISECONDS.sleep(3);
        exec.shutdown();

    }

}

ThreadLocal对象通常当做静态域存储。在创建ThreadLocal后只能通过get和set方法访问内容,其中,get方法返回与线程想关联的对象的副本,而set方法会将参数插入的为其线程存储的对象中,并返回存储中原有对象。注意increment和get么有synchronized ,因为ThreadLocal保证不会出现竞争条件。
当运行这个程序时,你可以看到每个单独的线程都被分配了自己的存储,因为他们每个都需要跟踪自己的计数,即便只有一个ThreadLocalVariableHolder对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值