Osheep

时光不回头,当下最重要。

Java线程总结

写在前面

Android开发过程中很少会用到复杂的线程操作,但是不能因为这个原因就不去学习。以下内容也许工作中并不会用到,但至少偶尔会在面试中遇到,所以不论怎样都是有帮助的。

欢迎Star or Follow我的GitHub

欢迎搜索微信公众号SamuelAndroid关注我,定期推送原创文章和代码。

线程状态

  1. 新生状态(New):当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新生(New)状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是或者的(not alive)。
  2. 就绪状态(Runnable):通过调用线程实例的start()方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPCU,等待状态并不是执行状态; 此时线程是活着的(alive)。
  3. 运行状态(Running):一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;此时线程是活着的(alive)。
  4. 阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞(blocked)状态;处于Blocking状态的线程仍然是活着的(alive)。
  5. 死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,当该Thread已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive)。

以上五个状态对于开发人员来说,状态5即线程阻塞是我们的学习重点。

《Java线程总结》

线程状态图

阻塞详解(join、sleep、wait)

1. join

使用该方法的线程会在此之间执行完毕后再往下继续执行。以下为测试代码:

@Test
    public void joinTest(){
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
        AThread at = new AThread(bt);
        try {
            bt.start();
            Thread.sleep(2000);
            at.start();
            at.join();
        } catch (Exception e) {
            System.out.println("Exception from main");
        }
        System.out.println(threadName + " end!");

        sleep();
    }


    private void sleep(){
        try {
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class BThread extends Thread {
        public BThread() {
            super("[BThread] Thread");
        };
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " start.");
            try {
                for (int i = 0; i < 5; i++) {
                    System.out.println(threadName + " loop at " + i);
                    Thread.sleep(1000);
                }
                System.out.println(threadName + " end.");
            } catch (Exception e) {
                System.out.println("Exception from " + threadName + ".run");
            }
        }
    }
    static class AThread extends Thread {
        BThread bt;
        public AThread(BThread bt) {
            super("[AThread] Thread");
            this.bt = bt;
        }
        public void run() {
            String threadName = Thread.currentThread().getName();
            System.out.println(threadName + " start.");
            try {
                bt.join();
                System.out.println(threadName + " end.");
            } catch (Exception e) {
                System.out.println("Exception from " + threadName + ".run");
            }
        }
    }

输出结果:

main start.
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[BThread] Thread loop at 2
[AThread] Thread start.
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
[AThread] Thread end.
main end!

2. sleep:

当前线程睡眠/millis的时间(millis指定睡眠时间是其最小的不执行时间,因为sleep(millis)休眠到达后,无法保证会被JVM立即调度);sleep()是一个静态方法(static method) ,所以他不会停止其他的线程也处于休眠状态;线程sleep()时不会失去拥有的对象锁。 作用:保持对象锁,让出CPU,调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留一定的时间给其他线程执行的机会。想必该方式大家都有使用过,这里就不再列出测试代码了。

3. wait:

当一个线程执行到wait()方法时,他就进入到一个和该对象相关的等待池(Waiting Pool)中,同时失去了对象的机锁—暂时的,wait后还要返还对象锁。当前线程必须拥有当前对象的锁,如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常,所以wait()必须在synchronized block中调用。

    @Test
    public void waitTest() {
        new Thread(new waitRun1()).start();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(new waitRun2()).start();

        sleep();
    }
    class waitRun1 implements Runnable {
        @Override
        public void run() {
            synchronized (ThreadTest.class) {
                System.out.println("enter thread1...");
                System.out.println("thread1 is waiting...");
                try {
                    //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
                    ThreadTest.class.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread1 is going on ....");
                System.out.println("thread1 is over!!!");
            }
        }
    }
    class waitRun2 implements Runnable {
        @Override
        public void run() {
            synchronized (ThreadTest.class) {
                System.out.println("enter thread2....");
                System.out.println("thread2 is sleep....");
                //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
                ThreadTest.class.notify();
                //==================
                //区别
                //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify()
                //方法,则线程永远处于挂起状态。
                try {
                    //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
                    //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
                    //在调用sleep()方法的过程中,线程不会释放对象锁。
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 is going on....");
                System.out.println("thread2 is over!!!");
            }
        }
    }

输出结果:

enter thread1…
thread1 is waiting…
enter thread2….
thread2 is sleep….
thread2 is going on….
thread2 is over!!!
thread1 is going on ….
thread1 is over!!!

4. yield(不是阻塞):

让出CPU的使用权,给其他线程执行机会、让同等优先权的线程运行(但并不保证当前线程会被JVM再次调度、使该线程重新进入Running状态),如果没有同等优先权的线程,那么yield()方法将不会起作用。我认为该方式几乎没有使用场景,具体运行效果可以查看github

欢迎搜索微信公众号SamuelAndroid关注我,定期推送原创文章和代码。

《Java线程总结》

公众号
点赞