21 并发 Concurrency

到目前为止, 你学到的都是有关顺序编程的知识, 即程序中的所有事物在任意时刻都只能执行一个步骤.

并发具有可论证的确定性, 但是实际上具有不可确定性

Java是一种多线程, 并且提出了并发问题

  • 并发的多样性

用并发解决的问题可分为"速度"和"设计可管理性"两种

更快的执行

并发是用于多处理器变成的基本工具

但是当前速度的提高往往是以多核处理器的形式而不是更快的芯片的形式出现

并发通常是提高运行在单处理器上的程序的性能

通常, 把一个程序分成很多任务比所有部分都顺序执行开销要大, 因为增加了上下文切换的代价

阻塞: 如果程序的某个人物因为该程序控制范围之外的某些条件(I/O)而导致不能继续执行, 那我们就说这个任务阻塞

并发使得程序中一个任务阻塞时, 其他的部分还能继续执行

常见的单处理器提高速度的示例是事件驱动的编程, 尤其是那些具有可响应的用户界面, 比如一个程序有一个"退出"按钮,

在程序运行的大多数时间它都是阻塞的, 不使用并发的话, 需要程序在所有任务中都定期检查用户输入, 通过单独创建执

行线程来响应用户的输入, 使得它单独地保持它的可响应性

同时执行多个任务, 看起来像是有多个CPU, 实际上这是一种错觉

操作系统级别的并发使用的是进程, 每个进程之间相互独立, , 多任务操作系统可以周期性地将CPU从一个进程切换到另一个进程

来实现同时运行多个进程

Java所使用的并发系统会共享内存和I/O资源, 编写的难点也在于协调不同线程驱动的任务之间对这些资源的使用

Java在顺序性语言的基础上提供对线程的支持

线程机制是在由执行程序表示的单一进程中创建任务

多线程系统对可用的线程数量的限制通常都是较小的

为了使得一个程序获得足够的线程, Java使用了协作多线程, Java的线程机制是抢占式的, 这表示调度机制会周期性地中断

线程, 将上下文切换到另一个线程, 从而为每个线程分配时间片

  • 基本的线程机制

并发编程使得我们可以把程序分成一个个分离的, 单独运行的小任务, 通过多线程机制, 这些任务每一个都将由执行线程来驱动

定义任务

由Runnable接口提供描述任务的方式

public class LiftOff implements Runnable {
    protected int countDown = 10;
    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 : "Lifftoff!") + "), ";
    }

    //Runnable接口必须实现的方法
    public void run() {
        while(countDown-- > 0) {
            System.out.print(status());
            //线程调度器 将CPU从一个线程转移给另一个线程
            //它表明 这个任务已经执行完一个生命周期了 可以切换给其他线程
            Thread.yield();
        }
    }
}

main方法也是一个线程, 把上面的任务的run放在main里运行

public class MainThread {
    public static void main(String[] args) throws Exception {
        LiftOff launch = new LiftOff();

        //要实现线程行为, 必须将run方法显式地附着在一个线程上
        launch.run();
    }
}

Thread类

public class BasicThreads {
    public static void main(String[] args) throws Exception {

        //只要用一个Runnable对象去初始化Thread对象就可以
        Thread t = new Thread(new LiftOff());

       //start()方法为该线程执行必须的初始化操作, 然后调用Runnable的run方法
        t.start();
        System.out.println("Waiting for LiftOff");
    }
}
/*
Waiting for LiftOff
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(Lifftoff!), 
*/

Waiting for LiftOff是在run完成之前先输出的, 这是因为在main中开启了第二个线程, LiftOff.run()和main()

线程调度器会在处理器之间默默地分发线程, 所以每次的执行顺序都可能不同

一个Thread单独地创建一个线程, 直到run执行完退出程序才会被回收, 所以它在调用完start()后并不会马上被回收

使用Executor

Executor执行器, 用于管理Thread对象

它可以管理异步任务的执行, 而不需要显式地管理线程的生命周期

用Executor来创建Thread对象

public class CacheThreadPool {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();

        //有限的线程集

        //ExecutorService exec = Executors.newFixedThreadPool(5);
        for(int i = 0;i < 5;i++)
            exec.execute(new LiftOff());
        exec.shutdown();
    }
}

newCachedThreadPool会创建与所需要相当数量的线程, 在回收线程时停止创建, 因此它是合理的Executor的首选

SingleThreadExecutor是线程数为1的FixedThreadPool

如果向SingleThreadExecutor提交多个任务, 那么这些任务会排队, 每个任务都会在下个任务开始前结束, 一个任务完成了, 开始下

一个任务

从任务中返回值

从任务中返回值用到了Callable接口, 它是具有类型参数的一种泛型, 它的参数类型表示从call中返回的值

Interface Callable<T> {

     public T call();

}

//String类型的Callable

class TaskWithResult implements Callable<String> {
    private int id;
    public TaskWithResult(int id) {
        this.id = id;
    }

    //从call()中返回String类型值
    public String call() {
        return "result of TaskWithResult " + id;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>>  results = 
            new ArrayList<Future<String>>();
        for(int i = 0;i < 10;i++) {

            //exec.submit(callable)接收Callable对象, 返回Future<T>对象, 泛型保持和Callabel实现类中的一致, 这里是String
            results.add(exec.submit(new TaskWithResult(i)));
        }
        for(Future<String> fs : results)
            try {

                //Future.get()返回Future中的值
                System.out.println(fs.get());
            } catch(InterruptedException e) {
                System.out.println(e);
                return;
            } catch(ExecutionException e) {
                System.out.println(e);
            } finally {
                exec.shutdown();
            }
    }
}

休眠

sleep()方法, 指定让方法休眠一段时间, 即阻塞, 让位于另一线程, 这样做的好处让多个线程的执行遵循固定的顺序

public class SleepingTask extends LiftOff {
    public void run() {
        try {
            while(countDown-- > 0) {
                System.out.print(status());

                /TimeUnit类中的sleep方法, 可以指定时间的单位
                TimeUnit.MILLISECONDS.sleep(100);
            }

        //休眠可能产生InterruptedException异常
        } catch(InterruptedException e) {
            System.out.println("Interrupted");
        }
    }
}

优先级

调度器会根据线程的优先级, 倾向于让优先级高的程序先执行

public class SimplePriorities implements Runnable {
    private int countDown = 5;

    //对d定义成volatile, 不进行任何编译器优化, 增大开销
    private volatile double d;
    private int priority;
    public SimplePriorities(int priority) {
        this.priority = priority;
    }
    public String toString() { 

       //通过Thread.currentThread()可以获得驱动当前任务的Thread对象
        return Thread.currentThread() + ": " + countDown;
    }
    public void run() {

        //设置优先级
        Thread.currentThread().setPriority(priority);
        while(true) {

            //大量运算保证优先级能体现在结果中
            for(int i = 1;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) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();

        //定义5个优先级最低的线程
        for(int i = 0;i < 5;i++)
            exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));

        //定义一个优先级最高的线程
        exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
        exec.shutdown();
    }
}

要在run方法的开头设置优先级, 而不是构造器, 因为构造器中线程还没有开始运行

一个最大优先级的线程和五个最小优先级的线程, 结果那一个优先的线程执行完了计数, 然后剩下五个才轮到执行

让步

Thread.yield()方法, 建议其他具有相同优先级的线程可以被执行

后台线程

后台线程是指在程序运行的时候在后台提供一种通用服务的线程

当所有非后台线程都结束以后, 后台线程也会被杀死

下面是一个后台线程的例子

public class SimpleDaemons implements Runnable {
    public void run() {
        try {
            while(true) {
                TimeUnit.MICROSECONDS.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());

            //setDaemon设置为后台线程, 注意这个语句必须在线程开始之前, 在start之前
            daemon.setDaemon(true);
            daemon.start();
        }
        print("All daemons started");
        TimeUnit.MILLISECONDS.sleep(175);
    }
}

main是一个非后台线程, 当main结束以后, 创建的10个后台的线程也都将结束, 

main在最后休眠了一段时间, 这段时间能看到10个后台线程仍在轮流运行, 输出一些结果

下面是一个创建后台线程的工厂

public class DaemonThreadFactory implements ThreadFactory {

    //对一个类型的Runnable对象, 创建一个线程, 这里额外添加了设置后台线程的操作
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
}
将线程的工厂应用于执行器

public class DaemonFromFactory implements Runnable {
    public void run() {
        try {
            while(true) {
                TimeUnit.MILLISECONDS.sleep(100);
                print(Thread.currentThread() + " " + this);
            }
        } catch(InterruptedException e) {
            print("Interrupted");
        }
    }
    public static void main(String[] args) throws Exception {

        //Executors的静态方法newCachedThreadPool接受一个ThreadFactory作为参数

        //用这个工厂对象来为execute中的Runnable对象创建线程Thread对象
        ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
        for(int i = 0;i < 10;i++)
            exec.execute(new DaemonFromFactory());
        print("All daemons started");
        TimeUnit.MILLISECONDS.sleep(500);
    }
}

下面是一段创建了子线程并把父线程设为后台线程的程序

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();
            printnb("DaemonSpawn " + i + " started, ");
        }

        //查看子线程是否是后台线程
        for(int i = 0;i < t.length;i++)
            printnb("t[" + i + "].isDaemon() = " + 
                t[i].isDaemon() + ", "
            );

       //线程无限循环, 自动让出CPU
        while(true)
            Thread.yield();
    }
}
class DaemonSpawn implements Runnable {
    public void run() {

        //线程无限循环, 自动让出CPU
        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();
        printnb("d.isDaemon() = " + d.isDaemon() + ", ");
        TimeUnit.SECONDS.sleep(1);
    }
}

发现输出的子线程也都是后台线程, 说明设置父线程为后台线程, 会自动把它的子线程也变成后台线程

下面是一个后台线程, 写了finally子句的程序, 发现最后finally里的内容没有执行

class ADaemon implements Runnable {
    public void run() {
        try {
            print("Starting ADaemon");
            TimeUnit.SECONDS.sleep(1);
        } catch(InterruptedException e) {
            print("Existing via InterruptedException");
        } finally {
            print("This should always run?");
        }
    }
}
public class DaemonsDontRunFinally {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new ADaemon());
        t.setDaemon(true);
        t.start();
    }
}

编码的变体

Thread线程类, 也是Runnable的一个实现, 所以可以编写直接继承自Thread的类

public class SimpleThread extends Thread {
    private int countDown = 5;
    private static int threadCount = 0;
    public SimpleThread() {

       //Thread的带String参数的构造器, 初始化一个Thread的名字name
        super(Integer.toString(++threadCount));
        start();
    }
    public String toString() {

        //通过getName()读取Thread的name
        return "#" + getName() + "(" + countDown + "), ";
     }
    public void run() {
        while(true) {
            System.out.print(this);
            if(--countDown == 0)
                return;
        }
    }
    public static void main(String[] args) throws Exception {
        for(int i = 0;i < 5;i++)
            new SimpleThread();
    }
}

自管理的Runnable

下面是一个自管理的Runnable的例子

public class SelfManaged implements Runnable {
    private int countDown = 5;

    //内部定义了一个Thread 并用自身对象初始化
    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) throws Exception {
        for(int i = 0;i < 5;i++)
            new SelfManaged();
    }
}

这里在构造器中调用了start, 很可能另一个任务会在构造器结束之前开始执行, 这意味着该任务能够访问处于不稳定状态的对象

所以最好使用Executor来执行

可以把Thread或Runnable用内部类或者返回内部类对象的形式来实现, 隐藏代码细节

下面是一个创建一个Thread派生内部类的例子

class InnerThread1 {
    private int countDown = 5;
    private Inner inner;
    private class Inner extends Thread {
        public Inner(String name) {
            super(name);
            start();
        }
        public void run() { }
    }
    public InnerThread1(String name) {
        inner = new Inner(name);
    }
}

下面是把Thread内部类作为返回值的例子
class InnerThread2 {
    private int countDown = 5;
    private Thread t;
    public InnerThread2(String name) {
        t = new Thread(name) {
            public void run() {}
        };
        t.start();
    }
}

下面是创建一个Runnable派生内部类的例子
class InnerRunnable1 {
    private int countDown = 5;
    private Inner inner;
    private class Inner implements Runnable {
        Thread t;
        Inner(String name) {
            t = new Thread(this, name);
        }
        public void run() { }
    }
    public InnerRunnable1(String name) {
        inner = new Inner(name);
    }
}

下面是把Runnable内部类作为返回值的例子
class InnerRunnable2 {
    private int countDown = 5;
    private Thread t;
    public InnerRunnable2(String name) {
        t = new Thread(new Runnable() {
            public void run() { }
        });
    }
}

下面是一个在方法中创建Thread派生内部类的例子
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() { }
            };
            t.start();
        }
    }
}

join

如果在某个线程上调用t.join(), 这个线程将被挂起, t线程插入执行, 直到t线程执行完毕或者异常退出, 这个线程才继续执行

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) {

           //捕获异常以后isInterrupted()标记被清空, 返回false
            print(getName() + "was interrupted. " + 
                "isInterrupted(): " + isInterrupted());
            return;
        }
        print(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线程插入执行
            sleeper.join();
        } catch(InterruptedException e) {
            print("Interrupted");
        }
        print(getName() + " join completed");
    }
}
public class Joining {
    public static void main(String[] args) throws Exception {
        Sleeper sleepy = new Sleeper("Sleepy", 1500),
                grumpy = new Sleeper("Grumpy", 1500);
        Joiner dopey = new Joiner("Dopey", sleepy),
               doc = new Joiner("Doc", grumpy);

        //在main线程中调用grumpy.interrupt(), 让grumpy进入阻塞
        grumpy.interrupt();
    }
}
/*
Grumpywas interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*/ 

在一个线程(main)中调用另一个线程(grumpy)的interrupt()使它进入阻塞, 之后让grumpy插入的Doc也马上返回了

捕获异常

从线程中逃逸的异常不能直接被捕获, 它会向外传播到控制台, 

可以用Executor来解决这个问题

Thread.UncaughtExceptionHandler表示每一个Thread上附着的一个异常处理器

class ExceptionThread2 implements Runnable {
    public void run() {
        Thread t = Thread.currentThread();
        System.out.println("run() by " + t);

        //获取附着在线程上的异常处理器getUncaughtExceptionHandler()
        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);

        //为Thread工厂创建的每一个Thread分配一个异常捕获器setUncaughtExceptionHandler()
        t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        System.out.println("eh = " + t.getUncaughtExceptionHandler());
        return t;
    }
}
public class CaptureUncaughtException {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool(
            new HandlerThreadFactory()
        );
        exec.execute(new ExceptionThread2());
    }
}

这个例子通过Thread工厂产生Thread, 这个Thread工厂会为每个Thread绑定一个我们自定义的异常捕获器, 

在Runnable执行run的过程中, 抛出的异常会被执行它的Thread的异常捕获器捕获

下面这个静态方法可以为所有的Thread指定同一个异常捕获器

Thread.setDefaultUncaughtExceptionHandler(eh);

  • 共享受限资源

多个线程有可能同时请求相同的资源, 导致冲突矛盾, 比如修改同一个值等等

一个例子是, 一个任务产生偶数, 而其他任务消费偶数, 那么每个消费者必须检查当前偶数的有效性

下面是一个通用的迭代器

public abstract class IntGenerator {
    private volatile boolean canceled = false;
    public abstract int next();
    public void cancle() { canceled = true; }
    public boolean isCanceled() { return canceled; }
}

这个迭代器有一个cancel参数, 表示当前这个Int的有效性, 可以显式地修改和查询cancel的值

下面是一个检查偶数的任务的例子

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.isCanceled()) {
            int val = generator.next();
            if(val % 2 != 0) {
                System.out.println(val + " not even!");
                generator.cancle();
            }
        }
    }
    public static void test(IntGenerator gp, int count) {
        System.out.println("Press Control-C to exit");
        ExecutorService exec = Executors.newCachedThreadPool();

       //这里为所有的线程分配的是同一个generator
        for(int i = 0;i < count;i++)
            exec.execute(new EvenChecker(gp, i));
        exec.shutdown();
    }
    public static void test(IntGenerator gp) {
        test(gp,10);
    }
}

IntGenerator是任务的共享资源, 从它身上可以观察该资源的终止信号, 

由于所有的EvenChecker公用一个IntGenerator, 

所有的线程都共用它的cancel值, 并且都可以修改cancel值, 一次修改之后,

就不能再访问while(!generator.isCanceled()){}, 所有线程都将会结束

下面是一个偶数迭代器, 它是上面这个迭代器的子类

public class EvenGenerator extends IntGenerator {
    private int currentEvenValue = 0;
    public int next() {
        ++currentEvenValue;
        ++currentEvenValue;
        return currentEvenValue;
    }
    public static void main(String[] args) throws Exception {
        EvenChecker.test(new EvenGenerator());
    }
}
/*
Press Control-C to exit
1843 not even!
*/

在两次++currentEvenValue;的过程中, 有可能第一次++currentEvenValue;之后第二次++currentEvenValue;之前

有另一个线程调用了next()方法, 使得最后currentEvenValue变成了一个奇数

这时候进入线程run的奇数判断条件中, 将generator的cancel置为true, 结束了所有的线程

解决共享资源竞争

序列化访问共享资源: 在给定时刻只允许一个任务访问公共资源,

这种机制常常称为互斥量

使用synchronized关键字标记方法

对于共享资源, 一般要把它们先打包成一个对象, 然后对所有要访问这个资源的方法, 标记为synchronized

所有的对象都具有单一的锁,  当在对象上调用其任意synchronized方法的时候, 此对象都被加锁, 该对象上的其他synchronized

方法只有等到前一个方法调用完毕并释放了锁之后才能被调用

在使用并发时, 应该将域设置为private的, 防止方法被其他任务直接访问

一个任务可以多次获得对象的锁, 因为在调用对象的一个synchronized方法时, 方法中可能调用了另一个synchronized的方法, 

对象被加锁的次数会被记录, 当对象被解锁, 完全释放后, 其计数变为0

针对每个类, 也有一个锁, 所以synchronized static可以在类的范围内防止对static数据的并发访问

同步规则:

如果你正在写一个变量, 它可能接下来将被另一个线程读取, 或者正在读取上一个已经被上一个线程写过的对象, 那么你必须使用

同步, 并且, 读写线程都必须用相同的监视器锁同步

亦即, 所有涉及读写的可能被其他线程访问的变量

public synchronized int next() {
        ++currentEvenValue;
        ++currentEvenValue;
        return currentEvenValue;
 }

将generator的next()方法改为synchronized, 就能保证同时只有一个线程在执行next()方法, currentEvenValue不会在写到一半的

时候被另一个线程读取

第一个进入next()的任务将获得锁, 后面的任务只能等待第一个任务锁被释放后才可获得

使用显式的Lock对象

Lock对象必须被显式地创建, 锁定和释放

public class MuteEvenGenerator
extends IntGenerator {
    private int currentEvenValue;
    private Lock lock = new ReentrantLock();
    public int next() {
        lock.lock();
        try {
            ++currentEvenValue;
            Thread.yield();
            ++currentEvenValue;

            //return语句必须在try中出现, 防止lock过早地解锁
            return currentEvenValue;
        } finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws Exception {
        EvenChecker.test(new MuteEvenGenerator());
    }
}

lock.tryLock();

trytryLock允许你尝试获取锁, 但实际不直接获得锁

lock.tryLock(2, TimeUnit.SECONDS);

tryLock允许你尝试指定时间之后才失败

原子性与易变性

原子操作是不能被线程调度机制中断的操作

但不应该依赖原子性, 就不去做防止线程竞争的操作

对于除了long和double以外其他类型的简单操作, 都可视作原子操作

定义long或double变量时, 如果使用vloatile关键字, 就会获得原子性

volatile关键字还确保了应用中的可视性, 如果你将一个域声明为vloatile的, 那么只要对这个域产生了写操作, 那么所有的读操作就

都可以看到这个修改, 因为volatile会直接在主存中进行读写操作

原子性操作不会刷新到主存中, 其他任务不会读到修改的新值, 所以如果多个任务同时访问某个域

要么用同步, 否则就要用volatile来保证可视性

public class AtomicityTest implements Runnable {
    private int i = 0;
    public int getValue() { return i; }
    private synchronized void evenIncrement() { i++;i++; }
    public void run() {
        while(true)
            evenIncrement();
    }
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        AtomicityTest at = new AtomicityTest();
        exec.execute(at);
        while(true) {
            int val = at.getValue();
            if(val % 2 != 0) {
                System.out.println(val);
                System.exit(0);
            }
        }
    }
}

getValue()里面return i虽然是原子操作, 但因为缺少同步, 故可以在evenIncrement()的中间不稳定状态被读取, 读出奇数来

Java的递增操作不是原子性的, 它涉及一个put, 一个get, 一写一读, 是两个操作

如果一个域可能会被多个任务同时访问, 或者这些任务中至少有一个是写入任务, 就应该将这个域设置为volatile

原子类

AtomicInteger, AtomicLong, AtomicReference等特殊的原子性变量类

有以下方法

boolean compareAndSet(expectedValue, updateValue);

下面是应用原子类的方法, 可以在不用同步的情况下防止资源竞争

public class AtomicIntegerTest 
implements Runnable {

    //原子类对象
    private AtomicInteger i = new AtomicInteger(0);
    public int getValue() { return i.get(); }

    //原子类对象+2操作
    private void evenIncrement() { i.addAndGet(2); }
    public void run() {
        while(true) {
            evenIncrement();
        }
    }
    public static void main(String[] args) throws Exception {

        //5秒钟后自动中断程序
        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) {

            //最终getValue不会读到不稳定数据
            int val = ait.getValue();
            if(val % 2 != 0) {
                System.out.println(val);
                System.exit(0);
            }
        }
    }
}

只有在特殊情况下采用原子类, 通常依赖于锁更安全一些

临界区

从方法中分离出一些代码段, 希望防止多个线程同时访问的只是这些代码段, 而不是整个方法, 

用synchronized来指定对象, 这个对象的锁被用来对花括号内的代码进行控制

synchronized(syncObject) {

}

这也被称为同步控制块

进入这个代码块之前, 必须得到syncObject对象的锁, 如果其他线程已经得到这个锁, 就需要等到锁被释放后, 才能进入临界区

下面是一个用了同步控制块的例子

//Pair要控制x和y保持一致

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 values not equal: " + Pair.this);
        }
    }
    public void checkState() {
        if(x != y) 
            throw new PairValuesNotEqualException();
    }
}

//使一个Pair中的x,y自增, 并保持一致, 加入到List中 
abstract class PairManager {
    AtomicInteger  checkCounter = new AtomicInteger(0);
    protected Pair p = new Pair();
    private List<Pair> storage =
        Collections.synchronizedList(new ArrayList<Pair>());
    //处理成synchronized的方法
    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 ignore) {}
    }
    //模板方法, 在实现类中处理
    public abstract void increment();
}
class PairManager1 extends PairManager {
    //同步控制increment()方法
    public synchronized void increment() {
        p.incrementX();
        p.incrementY();
        store(getPair());
    }
}
class PairManager2 extends PairManager {
    public void increment() {
        Pair temp;
        //使用同步控制块
        synchronized(this) {
            p.incrementX();
            p.incrementY();
            temp = getPair();
        }
        store(temp);
    }
}
//一个线程执行线程不安全的increment操作
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();
    }
}
//一个线程执行线程安全的incrementAndGet()操作
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
            pcheck1 = new PairChecker(pman1),
            pcheck2 = new PairChecker(pman2);
        exec.execute(pm1);
        exec.execute(pm2);
        exec.execute(pcheck1);
        exec.execute(pcheck2);
        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) throws Exception {
        PairManager 
            pman1 = new PairManager1(),
            pman2 = new PairManager2();
        testApproaches(pman1, pman2);
    }
}
/*

同时有PairChecker PairManipulator两个任务在运行, 

PairManipulator占有时,x,y自增1, PairChecker占有时checkCounter++
使用了同步控制块, 线程不加锁的时间更长, PairChecker能被执行的次数更多, 导致checkCounter更大
pm1: Pair: x: 33, y: 33 checkCounter = 68
pm2: Pair: x: 34, y: 34 checkCounter = 25086522
*/

还可以用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();
        }
    }
}

在其他对象上同步

synchronized必须给定一个在其上执行的对象, 并且最合理的方法, 就是使其应用在正在被调用的对象上, 

synchronized(this)

如果不是应用在调用方法的对象上, 而是在其他对象上, 就要求所有的相关任务都是在同一个对象上同步的, 

class DualSynch {
    private Object syncObject = new Object();

    //f()通过声明方法synchronized, 在this上同步
    public synchronized void f() {
        for(int i = 0;i < 5;i++) {
            print("f()");
            Thread.yield();
        }
    }
    public void g() {

        //在syncObject上同步的同步控制块
        synchronized(syncObject) {
            for(int i = 0;i < 5;i++) {
                print("g()");
                Thread.yield();
            }
        }
    }
}
public class Sync {
    public static void main(String[] args) throws Exception {
        final DualSynch ds = new DualSynch();
        new Thread() {
            public void run() {
                ds.f();
            }
        }.start();
        ds.g();
    }
}

线程本地存储

防止任务在共享资源上产生冲突的另一个种方法是根除对变量的共享, 

线程本地存储是一种自动化机制, 可以为使用相同变量的每个不同线程创建不同的存储, 

下面是一个线程本地存储的例子

class Accessor implements Runnable {
    private final int id;
    public Accessor(int idn) { id = idn; }
    public void run() {
        while(!Thread.currentThread().isInterrupted()) {

           //调用ThreadLocalVariableHolder的静态方法
            ThreadLocalVariableHolder.increment();
            System.out.println(this);
            Thread.yield();
        }
    }
    public String toString() {

        //仍旧是一个静态方法
        return "#" + id + ": " + 
                ThreadLocalVariableHolder.get();
    }
}
public class ThreadLocalVariableHolder {

    //ThreadLocal被定义为静态成员
    public static ThreadLocal<Integer> value = 
    new ThreadLocal<Integer>() {
        private Random rand = new Random(47);
        protected synchronized Integer initialValue() {
            return rand.nextInt(10000);
        }
    };

    //用到ThreadLocal的get方法
    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.SECONDS.sleep(1);
        exec.shutdownNow();
    }
}

ThreadLocal被定义成类中的静态成员, 通过它的get()或set()方法来访问, 看起来多个线程都公用这一个对象, 

但实际上get()方法返回与其线程相关联的对象的副本, set()方法会将参数插入到为其线程存储的对象中, 并返回存储中原有的对象

每个线程被分配了自己的存储, 因为它们每个都需要跟踪自己的计数值, 即便只有一个对象

  • 终结任务

下面是一个参观者从不同入口进入花园并计数的程序

class Count {
    private int count = 0;
    private Random rand = new Random(47);
    //同步方法, 增加总参观者人数
    public synchronized int increment() {
        int temp = count;
        //一半时间产生让步
        if(rand.nextBoolean())
            Thread.yield();
        return (count = ++temp);
    }
    //同步方法, 返回总参观者人数
    public synchronized int value() {
        return count;
    }
}
class Entrance implements Runnable {
    //总参观者人数作为一个静态成员
    private static Count count = new Count();
    //每个线程获取一个对象, 放在这个静态的List里
    private static List<Entrance> entrances = 
        new ArrayList<Entrance>();
    //每个线程单独的计数
    private int number = 0;
    private final int id;
    //最后终止所有线程
    private static volatile boolean canceled = false;
    public static void cancel() { canceled = true; }
    public Entrance(int id) {
        this.id = id;
        entrances.add(this);
    }
    public void run() {
        while(!canceled) {
            //对象自身计数值, 设为同步控制块
            synchronized(this) {
                ++number;
            }
            //访问总计数值递增, 使用同步方法
            print(this + " Total: " + count.increment());
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch(InterruptedException e) {
                print("sleep interrupted");
            }
        }
        print("Stopping " + this);
    }
    public synchronized int getValue() {
        return number;
    }
    public String toString() {
        return "Entrance " + id + ": " + getValue();
    }
    public static int getTotalCount() {
        return count.value();
    }
    public static int sumEntrances() {
        int sum = 0;
        for(Entrance entrance : entrances)
            sum += entrance.getValue();
        return sum;
    }
}
public class OrnamentalGarden {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0;i < 5;i++)
            exec.execute(new Entrance(i));

        //main休眠后向Entrance发送cancel消息, 终止所有线程
        TimeUnit.SECONDS.sleep(3);
        Entrance.cancel();
        exec.shutdown();

        //任务在超时时间内结束, awaitTermination返回true, 否则返回false
        if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
            print("Some tasks were not terminated!");
        print("Total: " + Entrance.getTotalCount());
        print("Sum of Entrances: " + Entrance.sumEntrances());
    }
}

在main线程中, 启动五个入口线程, 每个线程独立计数, 并用static的count统计总的参观人数, 两个计数部分都用同步控制

在main休眠的时间内五个线程并行运行, 最后main中向Entrance发送cancel消息, 终止所有的线程

在阻塞时终结

线程可以处于四个状态

1) 创建(new), 当线程被创建时, 它只会短暂地处于这个状态, 此时它分配了必须的系统资源, 并执行了初始化

2) 就绪(Runnable), 这种状态下, 只要线程调度器把时间片分配给线程, 线程就可以运行

3) 阻塞(Blocked), 线程能够运行, 但是有某个条件阻止它运行, 当线程处于阻塞状态时, 调度器将忽略线程, 不会给它分配时间片

4) 死亡(Dead), 死亡的线程不会再获得时间片, 不处于调度中, 通常线程是因为run的返回而死亡, 也可能因为被中断而死亡

可能进入阻塞状态的原因

1) 通过调用sleep(milliseconds)使任务进入休眠状态

2) 通过调用wait()使线程挂起, 直到线程得到了notify()或者notifyAll()或者signal()或者signalAll()消息

3) 任务在等待某个输入/输出完成

4) 任务试图在某个对象上调用其同步控制方法, 但是对象锁已经被占用

下面一个例子展示了sleep, I/O, 调用同步方法时中断它们

class SleepBlocked implements Runnable {
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(100);
        } catch(InterruptedException e) {
            print("InterruptedException");
        }
        print("Exiting SleepBlocked.run()");
    }
}
class IOBlocked implements Runnable {
    private InputStream in;
    public IOBlocked(InputStream is) { in = is; }
    public void run() {
        try {
            print("Waiting for read():");
            in.read();
        } catch(IOException e) {
            if(Thread.currentThread().isInterrupted()) {
                print("Interrupted from blocked I/O");
            } else {
                throw new RuntimeException(e);
            }
        }
        print("Exiting IOBlocking.run()");
    }
}
class SynchronizedBlocked implements Runnable {
    public synchronized void f() {
        while(true)
            Thread.yield();
    }
    public SynchronizedBlocked() {

        //在创建这个任务的时候开启一个并行的Thread并让它一直运行, 它会一直占用f()
        new Thread() {
            public void run() {
                f();
            }
        }.start();
    }
    public void run() {
        print("Trying to call f()");
        f();
        print("Exiting SynchronizedBlocked.run()");
    }
}
public class Interrupting  {
    private static ExecutorService exec =
        Executors.newCachedThreadPool();
    static void test(Runnable r) throws InterruptedException {

        //返回一个Future<?> 
        Future<?> f = exec.submit(r);
        TimeUnit.MILLISECONDS.sleep(100);
        print("Interrupting " + r.getClass().getName());

        //可以在这个泛型上调用cancel(), 用来中断这个任务
        f.cancel(true);
        print("Interrupt sent to " + r.getClass().getName());
    }
    public static void main(String[] args) throws Exception {
        test(new SleepBlocked());
        test(new IOBlocked(System.in));
        test(new SynchronizedBlocked());
        TimeUnit.SECONDS.sleep(3);
        print("Aborting with System.exit(0)");
        System.exit(0);
    }
}

/*

Interrupting concurrency.SleepBlocked
Interrupt sent to concurrency.SleepBlocked
InterruptedException
Exiting SleepBlocked.run()
Waiting for read():
Interrupting concurrency.IOBlocked
Interrupt sent to concurrency.IOBlocked
Trying to call f()
Interrupting concurrency.SynchronizedBlocked
Interrupt sent to concurrency.SynchronizedBlocked
Aborting with System.exit(0)

*/

SleepBlock是可以中断的阻塞类型, 而I/O和synchronized上的等待是不能中断的

可以用执行器exector强行终止, 关闭任务在其上发生阻塞的底层资源

public class CloseResource {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        ServerSocket server = new ServerSocket(8080);
        InputStream socketInput = new Socket("localhost", 8080).getInputStream();
        exec.execute(new IOBlocked(socketInput));
        exec.execute(new IOBlocked(System.in));
        TimeUnit.MILLISECONDS.sleep(100);
        print("Shutting down all threads");

        //关闭在exec上执行的任务
        exec.shutdown();

        TimeUnit.SECONDS.sleep(1);
        print("Closing " + socketInput.getClass().getName());
        socketInput.close();
        TimeUnit.SECONDS.sleep(1);
        print("Closing " + System.in.getClass().getName());
        System.in.close();
    }
}

用NIO进行中断, NIO会自动地响应中断

class NIOBlocked implements Runnable {
    private final SocketChannel sc;
    public NIOBlocked(SocketChannel sc) { this.sc = sc; }
    public void run() {
        try {
            print("Waiting for read() in" + this);
            sc.read(ByteBuffer.allocate(1));
        } catch(ClosedByInterruptException e) {
            print("ClosedByInterruptException");
        } catch(AsynchronousCloseException e) {
            print("AsynchronousCloseException");
        } catch(IOException e) {
            print("IOException");
        }
        print("Exiting NIOBlocked.run() " + this);
    }
}
public class NIOInterruption {
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        ServerSocket server = new ServerSocket(8080);
        InetSocketAddress isa = 
            new InetSocketAddress("localhost", 8080);
        SocketChannel sc1 = SocketChannel.open(isa);
        SocketChannel sc2 = SocketChannel.open(isa);

        //submit其中一个线程
        Future<?> f = exec.submit(new NIOBlocked(sc1));

        //使用execute来启动两个任务
        exec.execute(new NIOBlocked(sc2));

        //终止所有线程
        exec.shutdown();
        TimeUnit.SECONDS.sleep(1);
        f.cancel(true);
        TimeUnit.SECONDS.sleep(1);
        sc2.close();
    }
}

sc1抛出ClosedByInterruptException

sc2抛出AsynchronousCloseException

被互斥所阻塞

当你尝试在一个对象上调用其sychronized方法, 而这个对象的锁已经被其他任务获得, 那么调用任务将被挂起, 直到这个锁可获得

同一个互斥可以被同一个任务获得

public class MultiLock {
    public synchronized void f1(int count) {
        if(count-- > 0) {
            print("f1() calling f2() with count " + count);
            f2(count);
        }
    }
    public synchronized void f2(int count) {
        if(count-- > 0) {
            print("f2() calling f1() with count " + count);
            f1(count);
        }
    }
    public static void main(String[] args) throws Exception {
        final MultiLock multiLock = new MultiLock();
        new Thread() {
            public void run() {
                multiLock.f1(10);
            }
        }.start();
    }
}

创建一个对象用multiLock对象调用f1, 这时这个任务获得了multiLock的对象锁, 因此这个任务能够在对f2()的调用中再一次获得这

个锁

class BlockedMutex {
    private Lock lock = new ReentrantLock();
    public BlockedMutex() {
        lock.lock();
    }
    public void f() {
        try {
            lock.lockInterruptibly();
            print("lock acquired in f()");
        } catch(InterruptedException e) {
            print("Interrupted from lock acquisition in f()");
        }
    }
}
class Blocked2 implements Runnable {
    BlockedMutex blocked = new BlockedMutex();
    public void run() {
        print("Waiting for f() in BlockMutex");
        blocked.f();
        print("Broken out of blocked call");
    }
}
public class Interrupting2 {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Blocked2());
        t.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("Issuing t.interrupt()");
        t.interrupt();
    }
}

BlockedMutex在创建一个对象的时候总是获取所创建对象的锁并且从不释放, 于是在Blocked2的任务中调用block.f()便会因为

BlockedMutex不可获得而阻塞

使用interrupt()可以打断被互斥阻塞的调用

检查中断

class NeedsCleanup {
    private final int id;
    public NeedsCleanup(int ident) {
        id = ident;
        print("NeedsCleanup " + id);
    }
    public void cleanup() {
        print("Cleaning up " + id);
    }
}
class Blocked3 implements Runnable {
    private volatile double d = 0.0;
    public void run() {
        try {
            while(!Thread.interrupted()) {
                NeedsCleanup n1 = new NeedsCleanup(1);
                try {

                    //可能会产生阻塞的操作, 经由抛出InterruptedException来退出
                    print("Sleeping");
                    TimeUnit.SECONDS.sleep(1);
                    NeedsCleanup n2 = new NeedsCleanup(2);
                    try {

                        //不会阻塞的计算操作, 通过interrrupted(), 改变中断状态

                        //while(Thead.interruped()) 可以检查中断状态, 以此退出run
                        print("Calculating");
                        for(int i = 1;i < 2500000;i++)
                            d = d + (Math.PI + Math.E) / d;
                        print("Finished time-consuming operation");
                    } finally {

                        //每种操作后面用try-finally子句回收资源
                        n2.cleanup();
                    }
                } finally {

                    //每种操作后面用try-finally子句回收资源
                    n1.cleanup();
                }
            }
            print("Exiting via while() test");
        } catch(InterruptedException e) {
            print("Exiting via InterruptedException");
        }
    }
}
public class InterruptingIdiom {
    public static void main(String[] args) throws Exception {
        Thread t = new Thread(new Blocked3());
        t.start();
        TimeUnit.MILLISECONDS.sleep(1100);
        t.interrupt();
    }
}

调用interrupted, 如果中断发生在可能会发生阻塞的操作之前, 会抛出InterruptedException来退出任务, 如果在可能会发生阻塞的

操作之后, 则通过while(Thead.interrupted()) 判断中断状态来退出run

对于每一种操作, 用try-finally子句对使用的资源进行回收

线程之间的协作

多个线程协作完成一个大的工作, 有的任务之间可以并行进行, 有的任务则需要等待一项或多项其他任务完成以后才能开始

wait()与notifyAll()

wait()将当前任务挂起, 等待外部有notigy()或者notifyAll()发生时, 才将这个任务唤醒去检查所有的变化

调用sleep()或者yield()的时候锁并没有被释放, 而调用wait()的时候把任务挂起, 对象上的锁是被释放的, 此时该对象上的其他

synchronized方法可以在wait()期间被调用

有两种wait(), 一种提供以毫秒数为参数, 表示等待的时间

另一个没有任何参数, 表示无限等待下去

只能在同步控制方法或者同步控制块synchronized里使用wait()以及notifyAll(), 因为需要获得锁

想要调用notifyAll()就必须获取调用对象的锁

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值