Thinking in java学习笔记-并发(二)

一、终结任务

1、在阻塞中终结

线程的状态

这里写图片描述

进入阻塞状态
1、调用sleep()是任务进入休眠状态
2、通过wait()是线程挂起,直到被notify()或notifyAll()唤醒后进入就绪状态。
3、任务在等待输入/输出完成
4、任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁。

2、中断

  • Thread类包含interrupt()方法,设置线程的中断状态,可以终止被阻塞的任务。
  • 在Executor上调用shutdownNow(),它将发送一个interrupt()调用给它启动的所有线程。
  • 如果只是想中断某个单一的任务,可以在使用Executor时调用submit(),就可以拥有该任务的上下文,它将返回一个泛型Future
class SleepTest implements Runnable{

    @Override
    public void run() {
        System.out.println("SleepTest start");
        try {
            TimeUnit.SECONDS.sleep(100);
        } catch (InterruptedException e) {
            System.out.println("SleepTest interrupted");
        }
    }

}

class IOBlocked implements Runnable{
    //试图执行I/O操作
    private InputStream in;
    public IOBlocked(InputStream is) {
        in = is;
    }

    @Override
    public void run() {
        try {
            System.out.println("IOBlocked start");
            in.read();
        } catch (IOException e) {
            if(Thread.currentThread().isInterrupted()){
                System.out.println("IO interrupted");
            }else{
                throw new RuntimeException();
            }
        }

    }

}

class SynTest implements Runnable{

    public synchronized void syn(){
        while (true) {
            Thread.yield();//不会释放锁
        }
    }

    public SynTest() {
        new Thread(new Runnable() {
            //需要新开一个线程,因为一个线程可以多次获得某个对象锁
            @Override
            public void run() {
                syn();//试图获得锁
            }
        }).start();
    }

    @Override
    public void run() {
        System.out.println("synTest start");
        syn();//阻塞
        System.out.println("synTest end");
    }

}


public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new SynTest());
        exec.execute(new SleepTest());
        exec.execute(new IOBlocked(System.in));
        TimeUnit.SECONDS.sleep(1);
        exec.shutdownNow();
    }   
}

输出:

synTest start
SleepTest start
IOBlocked start
SleepTest interrupted

I/O和在synchronized块上的等待是不可中断的。

I/O中断
关闭任务在其上发生阻塞的底层资源

scoketInput.close();
System.in.close();

被互斥所阻塞
ReentrantLock上阻塞的任务具备可以被中断的能力

class SynBlocked{
    private Lock lock = new ReentrantLock();

    public SynBlocked() {
        lock.lock();//不释放锁
        System.out.println("locked");
    }

    public void releaseLock(){
        try {
            lock.lockInterruptibly();
            System.out.println("lock acquired");
        } catch (InterruptedException e) {
            System.out.println("lock released");
        }
    }

}

public class Test3 {
    public static void main(String[] args) throws InterruptedException {

        Thread thread1 = new Thread(new Runnable() {

            SynBlocked synBlocked = new SynBlocked();

            @Override
            public void run() {         
                synBlocked.releaseLock();//被阻塞
                //不同于创建SynBlocked的对象
            }
        });

        thread1.start();
        TimeUnit.SECONDS.sleep(2);
        thread1.interrupt();
    }
}

结果:

locked
lock released

检查中断
可以调用Thread.interrupted()来检查中断状态

看一下下面这段代码:

        while(!Thread.interrupted()){
            //point1
            try {
                TimeUnit.SECONDS.sleep(2);
                //point2

                dosomething();

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

如果interrupt()在注释point2之后(非阻塞操作)被调用,那么循环将结束满所有的本地对象将被销毁,最后循环会经由while语句的顶部退出。如果在point1和point2之间(sleep()之前或过程中)被调用,这个任务就会在第一次试图调用阻塞操作之前,经由InterruptedException退出。

二、线程之间的协作

如果两个任务在交替着步入某项共享资源,可以使用互斥来使得任何时刻只有一个任务可以访问这项资源。当多个任务一起工作的时候,某项工作依赖于前一项工作的完成,这就需要线程之间的协作。

wait()、notify()、notifyAll()
三种方法都是基类Object中的,只能在同步控制方法或同步控制块中调用,否则会抛出IllegalMonitorStateException异常,wait()、notify()、notifyAll()在调用前必须得到对象的锁。

wait()释放锁,任务被挂起,知道外部将它唤醒。(sleep()和yield()时锁没有被释放)
notify()唤醒等待同一个锁的任务中的一个,任务必须等待相同的条件,当条件发生变化时,必须只有一个任务能够从中受益。
notifyAll()唤醒所有在等待的任务,因某个特定锁而被调用时只有等待这个锁的任务才能被唤醒

信号的错失
假如两个线程已下面的方式实现

T1:
    synchronized (sharedMonitor) {
        sharedMonitor.notify();
    }

T2: 
    while(someCondition){
        synchronized(sharedMonitor){
            sharedMonitor.wait();
        }
    }

这种方式可能会导致信号的错失
这里写图片描述
错失了notify(),T2将进入wait(),并一直等待着一个已经发出的信号,从而产生死锁。
解决方法,防止在someCondition变量上产生竞争条件

    synchronized(sharedMonitor){
        while(someCondition){
            sharedMonitor.wait();
    }

生产者消费者问题
可以参考我另外一篇文章:http://blog.csdn.net/linsawako/article/details/53289126

使用显示的Lock和Condition对象
使用互斥并允许任务挂起的基本类是Condition,单个Lock将产出一Condition对象,这个对象被用来管理任务间的通信。调用await()来挂起一个任务,signal()和signalAll()来唤醒等待中的任务。和synchronized一样,调用这些方法之前,需要先取得这个锁。

class Buffer{
    private ArrayList<Integer> list = new ArrayList<>();
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public int get(){
        int num;
        lock.lock();//加锁
        try {
            while(list.size() < 1){
                System.out.println("    等待数据输出");
                condition.await();
            }

            num = list.remove(0);//移除数据
            condition.signalAll();//唤醒等待中的数据
            return num;
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁
        }
        return 0;
    }

    public void push(int num){
        lock.lock();
        try {
            while(list.size() > 0){
                System.out.println("等待数据输入");
                condition.await();
            }

            list.add(num);//添加数据
            condition.signalAll();
        }catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

class Producer implements Runnable{
    //生产者
    private Buffer buffer;
    private Random random = new Random();

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            int num = random.nextInt(100);
            buffer.push(num);
            System.out.println("写入数据:" + num);

            //休眠一段时间
            try {
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

class Consumer implements Runnable{
    //消费者
    private Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while(!Thread.interrupted()){
            System.out.println("    得到数据:" + buffer.get());

            //休眠一段时间
            try {
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

public class LockCondition {
    private Buffer buffer = new Buffer();
    private Producer producer = new Producer(buffer);
    private Consumer consumer = new Consumer(buffer);

    public LockCondition() {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(producer);
        exec.execute(consumer);
        exec.shutdown();
    }

    public static void main(String[] args) {
        new LockCondition();
    }
}

生产者-消费者与队列
可以使用同步队列来解决任务协作问题,同步队列在任何时刻都只允许一个任务插入或移除元素,在java.util.concurrent.BlockingQueue接口提供了这个队列

  • SynchronousQueue是无界的,是一种无缓冲的等待队列
  • LinkedBlockingQueue是无界的,是一个无界缓存的等待队列。
  • ArrayListBlockingQueue是有界的,是一个有界缓存的等待队列。
class Producer1 implements Runnable{
    //生产者
    private BlockingQueue<Integer> queue;
    private Random random = new Random();

    public Producer1(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (!Thread.interrupted()) {
            //休眠一段时间
            try {
                int num = random.nextInt(100);
                queue.put(num);
                System.out.println("写入数据:" + num);
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

class Consumer1 implements Runnable{
    //消费者
    private BlockingQueue<Integer> queue;

    public Consumer1(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(!Thread.interrupted()){
            //休眠一段时间
            try {
                System.out.println("    得到数据:" + queue.take());
                Thread.sleep((long) (Math.random() * 1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

public class BlockingQueueTest {
    private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);//阻塞队列,容量为2

    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new Producer1(queue));
        exec.execute(new Consumer1(queue));
        exec.shutdown();
    }

}

运行后:

写入数据:82
    得到数据:82
写入数据:56
    得到数据:56
写入数据:25
写入数据:3
    得到数据:25
写入数据:48
    得到数据:3
    得到数据:48
写入数据:31
写入数据:37
    得到数据:31
    得到数据:37
写入数据:44
    得到数据:44

对于不受限的队列而言,put方法将永远不会阻塞

任务间使用管道进行输入/输出
java输入/输出对应物为Pipedwriter类(允许任务向管道写)和PipedReader(允许不同任务从同一个管道中读取)

三、死锁

有时两个或多个线程需要在几个共享对象上获取锁,就可能导致死锁

先来看一下下面这段情况

线程1

synchronized (object1) {
    //do something  
    synchronized (object2) {
        //do something      
    }

}

线程2

synchronized (object2) {
    //do something  
    synchronized (object1) {
        //do something      
    }

}

假如线程1获得了object1的锁,在等着object2的锁,线程2获得了object2的锁,在等着object1的锁,这样每个线程都在等待另一个线程释放它所需要的锁,导致都无法继续运行。

以下四个条件同时满足时,就会发生死锁:

1)互斥条件,任务使用的资源中至少有一个是不能共享的
2)至少有一个任务它必须持有一个资源且在等待获取一个当前被别的任务持有的资源
3)资源不能被任务抢占,不能抢夺其他线程已经获得的锁
4)必须持有循环等待

要防止死锁,只需要破坏其中的一个。

最容易的是破坏第4个条件,使用资源排序,给每一个需要锁的对象指定一个顺序,确保每个线程都按这个顺序来获得锁,假如上面的例子,按object1、object2的顺序来对两个顺序排序。

经典的死锁问题为哲学家就餐问题:有五个哲学家,他们会花部分时间思考,花部分时间就餐。当他们思考的时候,他们不需要共享任何资源互不影响。而当他们就餐时,因为这里只有五只筷子,在他们每人都需要两只筷子的情况下,就会形成对筷子的竞争

代码来自《Thinking in java》

class Chopstick{
    private boolean token=false; 

    public synchronized void take() throws InterruptedException{
           while(token){
               wait();
           }
           token=true;
    }

    public synchronized void drop(){
        token=false;
        notifyAll();
    }
}

class Philosopers implements Runnable{

    private Chopstick left;
    private Chopstick right;

    private final int id;
    private final int pauseFactor;
    private Random rand = new Random(200);

    public Philosopers(Chopstick left,Chopstick right,int id,int pauseFactor){
        this.left=left;
        this.right=right;
        this.id=id;
        this.pauseFactor=pauseFactor;
    }

    private void pause() throws InterruptedException{ 
        TimeUnit.MILLISECONDS.sleep(pauseFactor*rand.nextInt(100));
    }

    public void run(){
        try{
            while(!Thread.interrupted()){
                System.out.println(this+" "+"thinking");
                pause();
                left.take();
                System.out.println(this+" 取得左边的筷子");
                right.take();
                System.out.println(this+" 取得右边的筷子");
                System.out.println(this+"Eating");
                pause();

                left.drop();
                right.drop();
            }
        }catch(InterruptedException ex){
            System.out.println(this+" 通过中断异常退出");
        }
    }

    public String toString(){
        return (id+1)+"号哲学家 ";
    }
}

public class Test4 {

   public static void main(String[] args) throws Exception{
       int pauseFactor=0;
       int size=5;
       Chopstick[] chopstick = new Chopstick[size];

       for(int i=0;i<size;i++){
           chopstick[i] = new Chopstick();
       }

       ExecutorService exec = Executors.newCachedThreadPool();

       for(int i=0; i<size; i++){
          exec.execute(new Philosopers(chopstick[i],chopstick[(i+1)%size],i,pauseFactor));
       }

       exec.shutdown();
       System.out.println("press 'Enter' to quit");
       System.in.read();
       exec.shutdownNow();
   }
}

运行结果:

...
1号哲学家  取得左边的筷子//14号哲学家  取得右边的筷子
4号哲学家 Eating
4号哲学家  thinking
5号哲学家  取得左边的筷子//53号哲学家  取得右边的筷子
3号哲学家 Eating
3号哲学家  thinking
4号哲学家  取得左边的筷子//42号哲学家  取得右边的筷子
2号哲学家 Eating
2号哲学家  thinking
2号哲学家  取得左边的筷子//23号哲学家  取得左边的筷子//3

想象着五个哲学家围成一圈,都举着左手的筷子,并等待着其他人放下,结果都傻傻地等着。导致死锁

可以设置最后一个哲学家先放下右手的Chopstick,移除死锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Thinking in Java》是一本经典的Java编程入门教材,通过阅读该书可以系统地学习Java编程的基础知识和高级概念。阅读过程中,我结合自己的学习体验和实践,进行了详细的学习笔记。 首先,该书从基础语法开始介绍,包括数据类型、控制语句、数组等。对于初学者来说,这些基础知识是最基本的,通过书中的示例代码和解析,我能够更好地理解这些概念和语法规则。 其次,书中对面向对象编程进行了深入的讲解。通过学习面向对象的思想,我明白了类、对象、继承、多态等概念的含义和使用方法。同时,书中还讲解了接口、内部类、异常处理等较为高级的概念,极大地拓宽了我的Java知识面。 另外,该书还介绍了Java的常见类库和工具,如字符串操作、集合框架、输入输出、线程等。这些内容对于实际开发非常有用,而且书中的示例代码也能帮助我更好地理解和应用这些类库和工具。 此外,该书通过大量的实例和案例,生动地展示了Java编程的实战应用。这些实例涵盖了各种不同的应用场景,从简单的程序到复杂的项目,都有所涉及。通过分析这些实例,我不仅可以学习到实际编程的技巧,还能提高自己的解决问题的能力。 总的来说,读完《Thinking in Java》后,我对Java编程有了更深入的理解和掌握。通过学习笔记的整理,我不仅复习了各个知识点,还加深了对这些概念的理解。希望这份学习笔记能够帮助到其他想要学习Java的同学,让他们能够更快地入门和掌握这门编程语言。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值