美文网首页
Thinking in java 之并发其三:线程的状态

Thinking in java 之并发其三:线程的状态

作者: Tearsweet | 来源:发表于2018-12-02 11:27 被阅读12次

    Thinking in java 之并发其三:线程的状态

    一、线程的四种状态

    在 java 中,一个线程可以处于下列四种状态之一:

    • 新建(new):当线程被创建时,它会短暂的处于这种状态。在这种状态下时,线程已经分配了必需的系统资源,并执行了初始化。此刻线程已经有资格获得 cpu 时间了,之后调度器将把这个线程转变为就绪或阻塞状态。

    • 就绪(Runnable):在这种状态下,只要调度器把时间片分给线程,线程就可以运行。也就是说,在这种状态下,线程是可以运行也可以不运行的。只要调度器把时间片分给线程,线程立刻可以运行。这是就绪状态与阻塞或死亡状态的区别。

    • 阻塞(Blocked):线程能够运行,但有某个条件阻止了它的运行。当线程进入阻塞状态时,调度器将忽略线程,不会将 cpu 时间分配给它。一个任务进入到阻塞状态,通常有以下几个原因:

      • 通过调用 sleep() 使任务进入休眠状态;
      • 通过调用 wait() 使线程挂起;
      • 任务在等待某个输入/输出完成;
      • 任务试图在某个对象上调用其同步控制方法。
    • 死亡(dead):该状态下,线程不可能再被调度,并且再也不会得到 cpu 时间,它的任务已结束。任务死亡的方式是从 run() 方法返回。

    二、终结任务

    在一些情况下,我们会希望我们的线程能够在运行一段时间后终止。一种做法是,在 Runnable 里添加一个状态标识码,通过这个状态码来控制任务是否继续进行或者结束。下面就是这种方法的一个例子:

    package ThreadTest.SycnSourceTest.concurrency;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    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();
        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);
        }
    
        @Override
        public void run() {
            while(!canceled) {
                synchronized(this) {
                    ++number;
                }
                System.out.println(this+" total: " + count.increment());
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            System.out.println("Stopping "+this);
        }
    
        public synchronized int getValue(){return number;}
        public String toString() {
            return "Entrances " + 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 OrnametalGarden {
        public static void main(String[] args) throws InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool();
            for(int i=0;i<5;i++) {
                exec.execute(new Entrance(i));
            }
            TimeUnit.SECONDS.sleep(3);
            Entrance.cancel();
            exec.shutdown();
            if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
                System.out.println("Some task were not terminated");
                System.out.println("Total: "+Entrance.getTotalCount());
                System.out.println("Sum of Entrances: "+Entrance.sumEntrances());
        }
    }
    

    我们通过布尔变量 cannel 来控制任务是否应该终止,当 main 的线程进行到某一时刻时,我们将 cannel 置为 true (此处的 cannel 是volatile 的,所以它的改变会立刻被其他任务捕捉到),从而终止所有正在进行的任务。

    有趣的时,我们从结果中不难发现,计数器并不是递增的,它会出现跳跃的情况。1 2 4 3 6 5... 这说明,虽然某个任务得以先进行,但未必会第一个完成。

    java 的 concurrency 包也为我们提供了中断线程的方法。在第一篇线程文章里,我们使用了 Future 实现了让 run() 返回特定类型的信息。Future 也可以帮我们实现中断线程的操作。

    如果我们在使用 Excutor 来启动线程时,不使用 executor() 而是使用 submit(),我们就可以获得一个 Future<?> 。这个 Future 是持有任务的上下文的,我们可以通过它的 cancel 方法来实现中断线程的操作。

    package ThreadTest.ThreadStatus;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    import java.util.concurrent.TimeUnit;
    
    class SleepBlocked implements Runnable{
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(100);
            }catch(InterruptedException e) {
                System.out.println("Catch InterruptedExcetpion");
            }
            System.out.println("Exiting SleepBlocked run()");
        }
    }
    
    class IOBlocked implements Runnable{
        private InputStream in;
        public IOBlocked(InputStream is) {
            in = is;
        }
        public void run() {
            try {
                System.out.println("Waiting for read()");
                in.read();
            }catch(IOException e) {
                if(Thread.currentThread().isInterrupted()) {
                    System.out.println("Interrupted from block I/O");
                }else {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("Exiting IOBlocked.run()");
        }
    }
    
    class SynchronizedBlocked implements Runnable{
        public synchronized void f() {
            while(true) {
                Thread.yield();
            }
        }
        public SynchronizedBlocked() {
            new Thread() {
                public void run() {
                    f();
                }
            }.start();
        }
        public void run() {
            System.out.println("Trying to call f()");
            f();
            System.out.println("Exiting SynchronizedBlocked r()");
        }
    }
    public class Inturrupting {
    
        public static ExecutorService exec = Executors.newCachedThreadPool();
        static void test(Runnable r) throws InterruptedException{
            Future<?> f = exec.submit(r);
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println("Interrupting "+r.getClass().getName());
            f.cancel(true);
            System.out.println("Interrupt sent to "+r.getClass().getName());
        }
        public static void main(String[] args) throws InterruptedException {
            // TODO Auto-generated method stub
            test(new SleepBlocked());
            test(new IOBlocked(System.in));
            test(new SynchronizedBlocked());
            TimeUnit.SECONDS.sleep(3);
            System.out.println("Aborting with system.exit(0)");
            System.exit(0);
    
        }
    
    }
    

    在这个示例中,我们一共对3中阻塞情况进行了中断任务操作。

    对于 sleep() 引起的阻塞,在我们通过 Future 对其进行了中断操作之后,任务跑出了 InturruptedException 异常,证明了任务的确被中断。

    另外两种情况(IO 阻塞和等待锁阻塞)我们并没有得到它们被中断的输出。这会导致一些问题,尤其是在创建 IO 的任务是,我们可能会被 IO 锁住多线程程序。

    一个比较笨拙的解决方式是关闭任务在其上发生阻塞的底层资源。

    (此处本该有示例,但是运行结果并没有符合预期,目前原因未知)

    Java 的 IO 的 nio 类还为我们提供更加人性化 IO 中断操作。被阻塞的 nio 通道会自动的响应中断。

    至于由于等待锁而造成的阻塞,Java 的 ReentrantLock 具备阻塞时中断的功能。

    package ThreadTest.ThreadStatus;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class BlockedMutex{
        private Lock lock = new ReentrantLock();
        public BlockedMutex() {
            lock.lock();
        }
    
        public void f() {
            try {
                lock.lockInterruptibly();
                System.out.println("lock acquire in f()");
            }catch(InterruptedException e) {
                System.out.println("Interrupted from lock acquisitiong in f()");
            }
        }
    }
    
    class Blocked2 implements Runnable{
        BlockedMutex block = new BlockedMutex();
        public void run() {
            System.out.println("wait for f() in BlockedMuex");
            block.f();
            System.out.println("Broken out of blocked call");
        }
    }
    public class Interrupting2 {
    
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(new Blocked2());
            t.start();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Issuing t.interrupt()");
            t.interrupt();
        }
    
    }
    

    BlokedMutex 类的构造器会获取所创建对象上自身的 lock,并且我们没有在任何地方去释放这个锁。所以当其他任务想要调用 f() 时,将会因为Mutex不可获得而被阻塞。在Blcked2中,run() 方法总是在调用 f() 的地方停止。与 I/O 调用不同,interript() 可以打断被互斥锁阻塞的调用。

    如果我们编写的程序有线程中断的可能,那么为了避免 run() 里面的循环能够检测到线程被中断并且正确退出(而不是通过抛出异常的方式退出)。检测的方式可以利用 Thread.interrupted() 实现:

    package ThreadTest.ThreadStatus;
    
    import java.util.concurrent.TimeUnit;
    
    class NeedsCleanup{
        private final int id;
        public NeedsCleanup(int ident) {
            this.id = ident;
            System.out.println("NeedsCleanUp: " + id);
        }
        public void cleanup(){
            System.out.println("cleaning up " + id);
        }
    
    }
    
    class Blocked3 implements Runnable{
        private volatile double d = 0.0;
        @Override
        public void run() {
            try {
                while(!Thread.interrupted()) {
                    NeedsCleanup n1 = new NeedsCleanup(1);
                    try {
                        System.out.println("Sleeping");
                        TimeUnit.SECONDS.sleep(1);
                        NeedsCleanup n2 = new NeedsCleanup(2);
                        try {
                            System.out.println("Calculation");
                            for(int i=1;i<2500000;i++) {
                                d=d+(Math.PI+Math.E)/d;
                            }
                            System.out.println("Finished time-consuming operation");
                        }finally {
                            n2.cleanup();
                        }
                    }finally{
                        n1.cleanup();
                    }
                }
                System.out.println("Exiting via while() test");
            }catch(InterruptedException e) {
                System.out.println("Exiting via InterruptedException");
            }
    
        }
    }
    public class InterruptingIdiom {
    
        private static int tm = 1002;
        public static void main(String[] args) throws InterruptedException {
            Thread t=new Thread(new Blocked3());
            t.start();
            TimeUnit.MILLISECONDS.sleep(tm);
            t.interrupt();
        }
    
    }
    

    在这个示例中 NeedsCleanup 表示一个必须要做清理操作的类。我们使用 try-finally 来保证它的清理方法 cleanup 总是被调用。

    通过调节 tm 的值,我们可以控制程序在 sleep 阶段或者在 calculation 阶段停止。当在 sleep 阶段停止时,任务会以抛出异常的方式退出,而在 calculation 阶段停止时,任务会在 while() 的判断处被中断。

    相关文章

      网友评论

          本文标题:Thinking in java 之并发其三:线程的状态

          本文链接:https://www.haomeiwen.com/subject/ozhjcqtx.html