• 第2章 Java并行程序基础(一)


    2.1 有关线程你必须知道的事

    • 进程是系统进行资源分配和调度的基本单位,是程序的基本执行实体。

    • 线程就是轻量级进程,是程序执行的最小单位。

    • 线程的生命周期,如图2.3所示。

    • 线程的所有状态都在Thread中的State枚举中定义,如下所示:

    public enum State {
        NEW,
        RUNABLE,
        BLOKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }
    
    • NEW状态表示刚刚创建的线程,这种线程还没有开始执行。等到线程的start()方法调用时,才表示线程开始执行。当线程执行时,处于RUNNABLE状态,表示线程所需的一切资源都已经准备好了。如果线程在 执行过程中遇到了synchronized同步块,就会进入BLOCKED阻塞状态,这时线程就会暂停执行,直到获得请求的锁。WAITING和TIMED_WAINTING都表示等待状态,它们的区别是WAITING会进入一个无时间限制的等待,TIMED_WAINTING会进入一个有时限的等待。那等待的线程究竟在等什么呢?一般来说,WAINTING的线程正是在等待一个特殊的事件。比如,通过wait()方法等待的线程在等待notify()方法,而通过join()方法等待的线程则会等待目标线程的终止。一旦等到了期望的事件,线程就会再次执行,进入RUNNABLE状态。当线程执行完毕后,则进入TERMINATED状态,表示结束。

    2.2 初始线程:线程的基本操作

    2.2.1 新建线程

    • 只要使用new关键字创建一个线程对象,并且将它start()起来即可。
    Thread t1 = new Thread();
    t1.start();
    
    • 线程Thread,有一个run()方法,start()方法就会新建一个线程并让这个线程执行run()方法。
    Thread t1 = new Thread() {
        @Override
        public void run() {
            System.out.println("Hello, I am t1");
        }
    };
    t1.start();
    
    • 上述代码使用匿名内部类,重载了run()方法。
    • 考虑Java是单继承的,也就是说继承本身也是一种很宝贵的资源,因此,我们也可以使用Runnable接口来实现同样的操作。Runable接口是一个单方法接口,它只有一个run()方法:
    public interface Runnable {
        public abstract void run();
    }
    
    • Thread类有一个非常重要的构造方法:
    public Thread(Runnable target)
    
    public class T1 implements Runnable {
        public static void main(String[] args) {
            Thread t1 = new Thread(new T1());
            t1.start();
        }
        
        @Override
        public void run() {
            System.out.println("Oh, I am Runnable");
        }
    }
    

    2.2.2 终止线程

    • Thread.stop()方法在结束线程时,会直接终止线程,并且会立即释放这个线程所持有的锁。而这些锁恰恰是用来维持对象一致性的。如果此时,写线程写入数据正写到一半,并强行终止,那么对象就会 被写坏,同时,由于锁已经被释放,另外一个等待该锁的读线程就顺理成章的读到了这个不一致的对象,悲剧也就此发生。整个过程如图2.4所示。

    • 可以自行决定线程何时退出,如下所示:

    public static class ChangeObjectThread extends Thread {
        volatile boolean stopme = false;
        
        public void stopMe() {
            stopme = true;
        }
        
        @Override
        public void run() {
            while (true) {
                if (stopme) {
                    System.out.println("exit by stop me");
                    break;
                }
                synchronized (u) {
                    int v = (int) (System.currentTimeMillis() / 1000);
                    u.setId(v);
                    
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    u.setName(String.valueOf(v));
                }
                Thread.yield();
            }
        }
    }
    

    2.2.3 线程中断

    • 与线程中断有关的三个方法:
    public void Thread.interrupt()  //中断线程,设置中断标志位
    public boolean Thread.isInterrupted()  //判断是否被中断(通过检查中断标志位)
    public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态
    
    • 下面这段代码对t1线程进行了中断,那么中断后,t1会停止执行吗?
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
    
    • 在这里,虽然对t1进行了中断,但是在t1中并没有中断处理的逻辑,因此,即使t1线程被置上中断状态,但是这个中断不会发生任何作用。
    • 如果希望t1在中断后退出,就必须为它增加相应的中断处理代码:
    Thread t1 = new Thread() {
        @Override
        public void run() {
            while (true) {
                if (Thread.currentThread().isInterrupted()) {
                    System.out.println("Interruted");
                    break;
                }
                Thread.yield();
            }
        }
    }
    
    • 下面,先来了解一下Thread.sleep()函数,它的签名如下:
    public static native void sleep(long millis) throws InterruptedException
    
    • Thread.sleep()方法会让当前线程休眠若干时间,它会抛出一个InterruptException中断异常。InterruptedException不是运行时异常,也就是说程序必须捕获并且处理它,当线程在sleep()休眠时,如果被中断,这个异常就会产生。
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) {
                        System.out.println("Interrupted!");
                        break;
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted When Sleep");
                        //设置中断状态
                        Thread.currentThread().interrupt();
                    }
                    Thread.yield();
                }
            }
        };
        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }
    
    • 执行Thread.sleep(),线程被中断,则程序会抛出异常,进入catch中,但没有立即退出线程。因为还要进行后续的处理,保证数据的一致性和完整性,因此,执行了Thread.interrupt()方法再次中断自己,置上中断标记位。只有这样做,在Thread.isInterrupted()检查中,才能发现当前线程已经被中断。
    • 注意:Thread.sleep()方法会清除中断标记,故再次设置中断标记位。

    2.2.4 等待(wait)和通知(notify)

    • 任何对象都可以调用这两个方法。
    public final void wait() throws InterruptedException
    public final native void notify()
    
    • 当在一个对象实例上调用wait()方法后,当前线程就会在这个对象上等待。一直等到其他线程调用了该对象的notify()方法为止。

    • 图2.5展示了两者的工作过程。

    • 强调一点,Object.wait()方法必须包含在对应的synchronized语句中,无论是wait()或者notify都需要首先获得目标对象的一个监视器。如图2.6所示,显示了wait()和notify()的工作流程细节。

    • 为了方便大家理解,这里给出了一个简单地使用wait()和notify()的案例:

    public class SimpleWN {
        final static Object object = new Object();
        public static class T1 extends Thread {
            public void run() {
                synchronized (object) {
                    System.out.println(System.currentTimeMillis() + ":T1 start!");
                    try {
                        System.out.println(System.currentTimeMillis() + ":T1 wait for object");
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        public static class T2 extends Thread {
            synchronized (object) {
                System.out.println(System.currentTimeMillis() + ":T2 start! notify one thread");
                object.notify();
                System.out.println(System.currentTimeMillis() +":T2 end!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    
                }
            }
        }
        
        public static void main(String[] args) {
            Thread t1 = new T1();
            Thread t2 = new T2();
            t1.start();
            t2.start();
        }
    }
    
    • 上述代码中,开启了两个线程T1和T2。T1执行了object.wait()方法。在T1中执行wait()方法前,T1先申请object的对象锁。因此,在执行object.wait()时,它是持有object的锁的。wait()方法执行后,T1会进行等待,并释放object的锁。T2在执行notify()之前也会获得object的对象锁。这里为了让实验效果明显,特意安排在notify()通知后,让T2休眠2秒钟,这样做可以更明显地说明,T1得到notify()通知后,还是会先尝试重新得到object的对象锁。
    T1 start!
    T1 wait for object
    T2 start! notify one thread
    T2 end!
    T1 end!
    
    • 注意:Object.wait()和Thread.sleep()方法都可以让线程等待若干时间。除了wait()可以被唤醒外,另外一个主要的区别是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。

    2.2.5 挂起(suspend)和继续执行(resume)线程

    • 这两个操作是一对相反的操作,被挂起的线程,必须要等到resume()操作后,才能继续指定。

    • 不推荐使用suspend()去挂起线程的原因,是因为suspend()在导致线程暂停的同时,并不会去释放任何锁资源。此时,其他任何线程想要访问被它暂用的锁时,都会被牵连,导致无法正常运行(如图2.7所示)。直到对应的线程上进行了resume()操作,被挂起的线程才能继续,从而其他所有阻塞在相 关锁上的线程也可以继续执行。但是,如果resume()操作意外地在suspend()前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且,更严重的是:它所占用的锁不会被释放,因此可能会导致整个系统工作不正常。而且,对于被挂起的线程,从它的线程状态上看,居然还是Runnable。

    • 为了方便大家理解suspend()的问题,这里准备一个简单的程序。演示了这种情况:

    public class BadSuspend() {
        public static Object u = new Object();
        static ChangeOjectThread t1 = new ChangeObjectThread("t1");
        static ChangeOjectThread t2 = new ChangeObjectThread("t2");
        
        public static class ChangeObjectThread extends Thread {
            public ChangeObjectThread(String name) {
                super.setName(name);
            }
            @Override
            public void run() {
                synchronized (u) {
                    System.out.println("in " + getName);
                    Thread.currentThread().suspend();
                }
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            t1.start();
            Thread.sleep(100);
            t2.start();
            t1.resume();
            t2.resume();
            t1.join();
            t2.join();
        }
    }
    
     in t1
     in t2
    

    2.2.6 等待线程结束(join)和谦让(yield)

    public final void join() throws InterruptedException
    public final synchronized void join(long millis) throws InterruptedException
    
    • 第一个join()方法表示无限等待,它会一直阻塞当前线程,直到目标线程执行完毕。第二个方法给出了最大等待时间,如果超出给定时间目标线程还在执行,当前线程也会因为“等不及了”,而继续往下执行。
    public class JoinMain {
        public volatile static int i = 0;
        public static class AddThread extends Thread {
            @Override
            public void run() {
                for (i = 0; i < 10000000; i++);
            }
        }
        public static void main(String[] args) throws InterruptedException {
            AddThread at = new AddThread();
            at.start();
            at.join();
            System.out.println(i);
        }
    }
    
    • 主函数中,如果不使用join()等待AddThread,那么得到的i很可能是0或者一个非常小的数字。因为AddThread还没开始执行,i的值就已经被输出了。但在使用join()方法后,表示主线程愿意等待AddThread执行完毕,故在join()返回时,AddThread已经执行完毕,故i总是10000000。
    • join()的本质是让调用线程wait()在当前线程对象实例上。
    while (isAlive()) {
        wait(0);
    }
    
    • 可以看到,它让调用线程在当前线程对象上进行等待。
    • 另外一个有趣的方法,是Thread.yield(),它的定义如下:
    public static native void yield();
    
    • 这是一个静态方法,一旦执行,它会使当前线程让出CPU。
    • 如果你觉得一个线程不那么重要,或者优先级非常低,而且又害怕它会占用太多的CPU资源,那么可以在适当的时候调用Thread.yield()。
  • 相关阅读:
    HTML 5 使用 FileReader、FormData实现文件上传
    【JS深入学习】——事件代理/事件委托
    【JS深入学习】——函数创建和重载
    Yii
    YII简单的基于角色的访问控制
    怎样在Yii中显示静态页
    Yii framework 应用总结小窍门(转)
    Yii PHP 框架分析(四)
    Yii PHP 框架分析(三)
    Yii PHP 框架分析(二)
  • 原文地址:https://www.cnblogs.com/sanjun/p/8317699.html
Copyright © 2020-2023  润新知