zoukankan      html  css  js  c++  java
  • Java高级特性系列--多线程基础

    转载自:http://www.cnblogs.com/skywang12345/p/3479202.html

    进程:受操作系统管理的基本运行单元。360浏览器是一个进程,正在操作系统中运行的.exe都可以理解为一个进程。

    线程:进程中独立运行的子任务就是线程。像QQ.exe运行的时候就有很多子任务在运行,比如聊天线程/好友视频线程/下载文件线程等。

    多线程优点:

    1,资源利用率更好

    2,程序设计更简单

    3,程序相应更快

    多线程代价:

    1,有时程序设计会更复杂

    2,上下文切换开销大

    3,增加资源消耗

    一,多线程相关概念:

    线程的5种状态:

    1,新建状态(New):线程对象被创建之后,就进入了新建状态。Thread thread = new Thread();

    2,  就绪状态(Runnable):可执行状态,线程对象被创建后,其他线程调用了该对象的start()方法,该线程就启动了。处于就绪状态,随时可能被CPU调度执行。

    3,运行状态(Running):线程获取到CPU正在执行。线程只能从就绪状态转为运行状态,不能从其他状态进入运行状态。

    4,阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃了CPU的使用权,暂时停止运行。阻塞分为三种情况:

      (a)等待阻塞:通过调用线程的wait()方法,让线程等待某项工作的完成。

      (b)同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),就会进入同步阻塞状态。

      (c)其他阻塞:通过调用线程的sleep() or join() or 发生IO请求时,线程会进入阻塞状态。当sleep()超时,join()等待线程终止或超时,或者IO操作处理完成时,线程重新进入就绪状态。

    5,死亡状态(Dead):线程完成了或者因异常退出了run()方法,该线程结束生命周期。

    二,常用的实现多线程的两种方式

    1,实现Runnable接口

    2,继承Thread类

    Thread是类,而Runnable是接口,Thread本身实现了Runnable接口。推荐使用Runnable接口,因为一,允许实现类去继承其他父类,可扩展性增加;二,可以实现资源共享,多个线程都是基于一个Runnable对象建立的,这样它们就会共享Runnable对象上的资源。

    三,synchronized

    在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在。如果某个对象的同步锁被某个线程占用,那么其他线程就必须等待。

    当我们调用某对象的synchronized方法时,就获取了该对象的同步锁。例如,synchronized(obj)就获取了“obj这个对象”的同步锁。
    不同线程对同步锁的访问是互斥的。也就是说,某时间点,对象的同步锁只能被一个线程获取到!通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问。 例如,现在有两个线程A和线程B,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程A获取到“obj的同步锁”并在执行一些操作;而此时,线程B也企图获取“obj的同步锁” —— 线程B会获取失败,它必须等待,直到线程A释放了“该对象的同步锁”之后线程B才能获取到“obj的同步锁”从而才可以运行。

     我们将synchronized的基本规则总结为下面3条,并通过实例对它们进行说明。
    第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
    第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块
    第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。

    四,锁

    实例锁:锁在某个实例对象上。实例锁对应的就是synchronized关键字。

    全局锁:该锁针对的是类,无论实例多少,线程都共享该锁。全局锁对应的是static synchronized,或者是锁在该类的class或者classloader对象上。

    关于“实例锁”和“全局锁”有一个很形象的例子:

    pulbic class Something {
        public synchronized void isSyncA(){}
        public synchronized void isSyncB(){}
        public static synchronized void cSyncA(){}
        public static synchronized void cSyncB(){}
    }

    假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
    (01) x.isSyncA()与x.isSyncB()  不可以被同时访问
    (02) x.isSyncA()与y.isSyncA() 可以被同时访问
    (03) x.cSyncA()与y.cSyncB() 不可以被同时访问
    (04) x.isSyncA()与Something.cSyncA() 可以被同时访问

    五,线程等待与唤醒

    wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

    Object类中关于等待/唤醒的API详细信息如下:
    notify()        -- 唤醒在此对象监视器上等待的单个线程。
    notifyAll()   -- 唤醒在此对象监视器上等待的所有线程。
    wait()                                         -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
    wait(long timeout)                    -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
    wait(long timeout, int nanos)  -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。

    wait()和notify()示例

    下面通过示例演示"wait()和notify()配合使用的情形"。

    复制代码
    // WaitTest.java的源码
    class ThreadA extends Thread{
    
        public ThreadA(String name) {
            super(name);
        }
    
        public void run() {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+" call notify()");
                // 唤醒当前的wait线程
                notify();
            }
        }
    }
    
    public class WaitTest {
    
        public static void main(String[] args) {
    
            ThreadA t1 = new ThreadA("t1");
    
            synchronized(t1) {
                try {
                    // 启动“线程t1”
                    System.out.println(Thread.currentThread().getName()+" start t1");
                    t1.start();
    
                    // 主线程等待t1通过notify()唤醒。
                    System.out.println(Thread.currentThread().getName()+" wait()");
                    t1.wait();
    
                    System.out.println(Thread.currentThread().getName()+" continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    复制代码

    运行结果:

    main start t1
    main wait()
    t1 call notify()
    main continue

    结果说明
    如下图,说明了“主线程”和“线程t1”的流程。

    (01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表WaitTest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
    (02) “主线程”通过 new ThreadA("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
    (03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
    (04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
    (05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。

    对于上面的代码?曾经有个朋友问到过:t1.wait()应该是让“线程t1”等待;但是,为什么却是让“主线程main”等待了呢?
    在解答该问题前,我们先看看jdk文档中关于wait的一段介绍:

    Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. 
    In other words, this method behaves exactly as if it simply performs the call wait(0). The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

    中文意思大概是:

    引起“当前线程”等待,直到另外一个线程调用notify()或notifyAll()唤醒该线程。换句话说,这个方法和wait(0)的效果一样!(补充,对于wait(long millis)方法,当millis为0时,表示无限等待,直到被notify()或notifyAll()唤醒)。
    “当前线程”在调用wait()时,必须拥有该对象的同步锁。该线程调用wait()之后,会释放该锁;然后一直等待直到“其它线程”调用对象的同步锁的notify()或notifyAll()方法。然后,该线程继续等待直到它重新获取“该对象的同步锁”,然后就可以接着运行。

    注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
    这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!

    再看下一个例子:

    package com.util.concurrent;
    
    
    public class WaitTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            ThreadA t1 = new ThreadA("t1");
            String s1 = new String("test");
            
            synchronized (t1) {
                try {
                    System.out.println(Thread.currentThread().getName() + "start t1");
                    t1.start();
                    
                    System.out.println(Thread.currentThread().getName() + "call wait()");
                    s1.wait();
                    
                    System.out.println(Thread.currentThread().getName() + "continue");
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
        }
    
    }
    
    class ThreadA extends Thread{
        public ThreadA(String name) {
            super(name);
        }
        
        public void run() {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + "call notify()");
                notify();
            }
        }
    }

    在这个例子中做了小小的修改,调用wait()的不是t1,而是一个新的对象s1,所以运行结果如下:

    main start t1
    main call wait()
    t1 call notify()

    主线程阻塞后就再也没起来,因为调用notify()的对象是t1,只会唤醒等待t1同步锁的线程,而main线程现在需要的是s1的notify。所以t1.wait() 的意思是让当前线程等待t1的同步锁,直到有其他线程调用了t1.notify,才会唤醒之前等待的线程。

    wait(long timeout)和notify()

    wait(long timeout)会让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
    下面的示例就是演示wait(long timeout)在超时情况下,线程被唤醒的情况。

    复制代码
    // WaitTimeoutTest.java的源码
    class ThreadA extends Thread{
    
        public ThreadA(String name) {
            super(name);
        }
    
        public void run() {
            System.out.println(Thread.currentThread().getName() + " run ");
            // 死循环,不断运行。
            while(true)
                ;
        }
    }
    
    public class WaitTimeoutTest {
    
        public static void main(String[] args) {
    
            ThreadA t1 = new ThreadA("t1");
    
            synchronized(t1) {
                try {
                    // 启动“线程t1”
                    System.out.println(Thread.currentThread().getName() + " start t1");
                    t1.start();
    
                    // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
                    System.out.println(Thread.currentThread().getName() + " call wait ");
                    t1.wait(3000);
    
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    复制代码

    运行结果

    main start t1
    main call wait 
    t1 run                  // 大约3秒之后...输出“main continue”
    main continue

    结果说明
    如下图,说明了“主线程”和“线程t1”的流程。
    (01) 注意,图中"主线程" 代表WaitTimeoutTest主线程(即,线程main)。"线程t1" 代表WaitTest中启动的线程t1。 而“锁” 代表“t1这个对象的同步锁”。
    (02) 主线程main执行t1.start()启动“线程t1”。
    (03) 主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者 “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
    (04) “线程t1”运行之后,进入了死循环,一直不断的运行。
    (05) 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。

    wait() 和 notifyAll()

    通过前面的示例,我们知道 notify() 可以唤醒在此对象监视器上等待的单个线程。
    下面,我们通过示例演示notifyAll()的用法;它的作用是唤醒在此对象监视器上等待的所有线程。

    复制代码
     1 public class NotifyAllTest {
     2 
     3     private static Object obj = new Object();
     4     public static void main(String[] args) {
     5 
     6         ThreadA t1 = new ThreadA("t1");
     7         ThreadA t2 = new ThreadA("t2");
     8         ThreadA t3 = new ThreadA("t3");
     9         t1.start();
    10         t2.start();
    11         t3.start();
    12 
    13         try {
    14             System.out.println(Thread.currentThread().getName()+" sleep(3000)");
    15             Thread.sleep(3000);
    16         } catch (InterruptedException e) {
    17             e.printStackTrace();
    18         }
    19 
    20         synchronized(obj) {
    21             // 主线程等待唤醒。
    22             System.out.println(Thread.currentThread().getName()+" notifyAll()");
    23             obj.notifyAll();
    24         }
    25     }
    26 
    27     static class ThreadA extends Thread{
    28 
    29         public ThreadA(String name){
    30             super(name);
    31         }
    32 
    33         public void run() {
    34             synchronized (obj) {
    35                 try {
    36                     // 打印输出结果
    37                     System.out.println(Thread.currentThread().getName() + " wait");
    38 
    39                     // 唤醒当前的wait线程
    40                     obj.wait();
    41 
    42                     // 打印输出结果
    43                     System.out.println(Thread.currentThread().getName() + " continue");
    44                 } catch (InterruptedException e) {
    45                     e.printStackTrace();
    46                 }
    47             }
    48         }
    49     }
    50 }
    复制代码

    运行结果

    复制代码
    t1 wait
    main sleep(3000)
    t3 wait
    t2 wait
    main notifyAll()
    t2 continue
    t3 continue
    t1 continue
    复制代码

    结果说明
    参考下面的流程图。 
    (01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
    (02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
    (03) 主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!

    为什么notify(), wait()等函数定义在Object中,而不是Thread中

    Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。

    wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
    OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。

    负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。

    总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。

    六,线程让步

    yield()定义在Thread.java中。

    yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

    yield() 与 wait()的比较

    我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而yield()的作用是让步,它也会让当前线程离开“运行状态”。它们的区别是:
    (01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
    (02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。

    下面通过示例演示yield()是不会释放锁的。

    package com.util.concurrent;
    
    public class YieldTest {
    
        private static Object obj =new Object();
        public static void main(String[] args) {
            ThreadB t1 = new ThreadB("t1");
            ThreadB t2 = new ThreadB("t2");
            
            t1.start();
            t2.start();
        }
        
        static class ThreadB extends Thread {
            public ThreadB(String name) {
                super(name);
            }
            
            public void run() {
                synchronized (obj) {
                    for(int i=0; i<10; i++) {
                        System.out.println(this.getName() + ":" + this.getPriority() + ":" + i);
                        
                        if(i%4 == 0) {
                            Thread.yield();
                        }
                    }
                }
            }
        }
    }

    结果:

    t1:5:0
    t1:5:1
    t1:5:2
    t1:5:3
    t1:5:4
    t1:5:5
    t1:5:6
    t1:5:7
    t1:5:8
    t1:5:9
    t2:5:0
    t2:5:1
    t2:5:2
    t2:5:3
    t2:5:4
    t2:5:5
    t2:5:6
    t2:5:7
    t2:5:8
    t2:5:9

    结果说明

    主线程main中启动了两个线程t1和t2。t1和t2在run()会引用同一个对象的同步锁,即synchronized(obj)。在t1运行过程中,虽然它会调用Thread.yield();但是,t2是不会获取cpu执行权的。因为,t1并没有释放“obj所持有的同步锁”!

    七,线程休眠

    sleep() 定义在Thread.java中。
    sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。

     sleep() 与 wait()的比较

    我们知道,wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态”。
    但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
    下面通过示例演示sleep()是不会释放锁的。

    package com.util.concurrent;
    
    public class SleepTest {
    
        private static Object obj = new Object();
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            ThreadC t1 = new ThreadC("t1");
            ThreadC t2 = new ThreadC("t2");
            t1.start();
            t2.start();
        }
        
        static class ThreadC extends Thread {
            public ThreadC(String name) {
                super(name);
            }
            
            public void run() {
                synchronized (obj) {
                    for(int i=0; i<10; i++) {
                        System.out.println(this.getName() + ":" + this.getPriority() + ":" + i);
                        
                        if(i%4 == 0) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                // TODO Auto-generated catch block
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }
    
    }

    结果:

    t1:5:0
    t1:5:1
    t1:5:2
    t1:5:3
    t1:5:4
    t1:5:5
    t1:5:6
    t1:5:7
    t1:5:8
    t1:5:9
    t2:5:0
    t2:5:1
    t2:5:2
    t2:5:3
    t2:5:4
    t2:5:5
    t2:5:6
    t2:5:7
    t2:5:8
    t2:5:9

    主线程main中启动了两个线程t1和t2。t1和t2在run()会引用同一个对象的同步锁,即synchronized(obj)。在t1运行过程中,虽然它会调用Thread.sleep(100);但是,t2是不会获取cpu执行权的。因为,t1并没有释放“obj所持有的同步锁”!
    注意,若我们注释掉synchronized (obj)后再次执行该程序,t1和t2是可以相互切换的。

    八,join

    join()定义在Thread.java中,与sleep()&yield()不同,它是一个实例方法,不是类方法。

    join()的作用:让父线程等待子线程结束之后才能继续运行。这里的子线程必须是父线程创建出来的线程。

    join()源码分析(基于JDK1.7.0_40)

    复制代码
    public final void join() throws InterruptedException {
        join(0);
    }
    
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    复制代码

    说明
    从代码中,我们可以发现。当millis==0时,会进入while(isAlive())循环;即只要子线程是活的,主线程就不停的等待。
    我们根据上面解释join()作用时的代码来理解join()的用法!
    问题
    虽然s.join()被调用的地方是发生在“Father主线程”中,但是s.join()是通过“子线程s”去调用的join()。那么,join()方法中的isAlive()应该是判断“子线程s”是不是Alive状态;对应的wait(0)也应该是“让子线程s”等待才对。但如果是这样的话,s.join()的作用怎么可能是“让主线程等待,直到子线程s完成为止”呢,应该是让"子线程等待才对(因为调用子线程对象s的wait方法嘛)"?
    答案wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。所以,虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”!

    示例:

    // JoinTest.java的源码
    public class JoinTest{ 
    
        public static void main(String[] args){ 
            try {
                ThreadA t1 = new ThreadA("t1"); // 新建“线程t1”
    
                t1.start();                     // 启动“线程t1”
                t1.join();                        // 将“线程t1”加入到“主线程main”中,并且“主线程main()会等待它的完成”
                System.out.printf("%s finish
    ", Thread.currentThread().getName()); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } 
    
        static class ThreadA extends Thread{
    
            public ThreadA(String name){ 
                super(name); 
            } 
            public void run(){ 
                System.out.printf("%s start
    ", this.getName()); 
    
                // 延时操作
                for(int i=0; i <1000000; i++)
                   ;
    
                System.out.printf("%s finish
    ", this.getName()); 
            } 
        } 
    }

    结果: 

    t1 start
    t1 finish
    main finish

    结果说明:

    运行流程如图 
    (01) 在“主线程main”中通过 new ThreadA("t1") 新建“线程t1”。 接着,通过 t1.start() 启动“线程t1”,并执行t1.join()。
    (02) 执行t1.join()之后,“主线程main”会进入“阻塞状态”等待t1运行结束。“子线程t1”结束之后,会唤醒“主线程main”,“主线程”重新获取cpu执行权,继续运行。

    示例二:

    package com.util.concurrent;
    
    public class JoinTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            ThreadD f = new ThreadD("f");
            f.start();
            
        }
    
    }
    
    class ThreadD extends Thread{
        public ThreadD(String name) {
            super(name);
        }
        
        public void run() {
            ThreadDD s = new ThreadDD("s");
            s.start();
            try {
                s.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            for (int i = 0; i<20; i++) {
                System.out.println(Thread.currentThread().getName() + " is running" + " " + i);
            }
        }
    }
    
    class ThreadDD extends Thread{
    
        public ThreadDD(String name) {
            super(name);
        }
        
        public void run() {
            for (int i = 0; i<20; i++) {
                System.out.println(Thread.currentThread().getName() + " is running" + " " + i);
            }
        }
    }

    结果:

    s is running 0
    s is running 1
    s is running 2
    s is running 3
    s is running 4
    s is running 5
    s is running 6
    s is running 7
    s is running 8
    s is running 9
    s is running 10
    s is running 11
    s is running 12
    s is running 13
    s is running 14
    s is running 15
    s is running 16
    s is running 17
    s is running 18
    s is running 19
    f is running 0
    f is running 1
    f is running 2
    f is running 3
    f is running 4
    f is running 5
    f is running 6
    f is running 7
    f is running 8
    f is running 9
    f is running 10
    f is running 11
    f is running 12
    f is running 13
    f is running 14
    f is running 15
    f is running 16
    f is running 17
    f is running 18
    f is running 19

    所以调用join之后,是调用者的父进程必须等待调用者进程结束。

    九,线程终止方式

    Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!可以使用interrupt的特性

    interrupt()的作用是中断本线程。
    本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
    如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
    如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
    如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
    中断一个“已终止的线程”不会产生任何操作。

    1,终止处于“阻塞状态”的线程

    通常,我们通过“中断”方式终止处于“阻塞状态”的线程。
    当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的位置就能终止线程,形式如下:

    复制代码
    @Override
    public void run() {
        try {
            while (true) {
                // 执行任务...
            }
        } catch (InterruptedException ie) {  
            // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
        }
    }
    复制代码

    说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException中断。中断的捕获在while(true)之外,这样就退出了while(true)循环!
    注意:对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理。形式如下:

    复制代码
    @Override
    public void run() {
        while (true) {
            try {
                // 执行任务...
            } catch (InterruptedException ie) {  
                // InterruptedException在while(true)循环体内。
                // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出
                break;
            }
        }
    }
    复制代码

    说明:上面的InterruptedException异常的捕获在whle(true)之内。当产生InterruptedException异常时,被catch处理之外,仍然在while(true)循环体内;要退出while(true)循环体,需要额外的执行退出while(true)的操作。

    2,终止处于“运行状态”的线程

    通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
    (01) 通过“中断标记”终止线程。
    形式如下:

    @Override
    public void run() {
        while (!isInterrupted()) {
            // 执行任务...
        }
    }

    说明:isInterrupted()是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时,就会退出while循环。
    注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。

    (02) 通过“额外添加标记”。
    形式如下:

    复制代码
    private volatile boolean flag= true;
    protected void stopTask() {
        flag = false;
    }
    
    @Override
    public void run() {
        while (flag) {
            // 执行任务...
        }
    }
    复制代码

    说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
    注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。

    综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:

    复制代码
    @Override
    public void run() {
        try {
            // 1. isInterrupted()保证,只要中断标记为true就终止线程。
            while (!isInterrupted()) {
                // 执行任务...
            }
        } catch (InterruptedException ie) {  
            // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
        }
    }
    复制代码

    interrupt()常常被用来终止阻塞状态线程,示例:

    package com.util.concurrent;
    
    public class InterruptedTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            try {
                Thread t1 = new MyInterruptedThread("t1");
                System.out.println(t1.getName() +" ("+t1.getState()+") is new."); 
                
                t1.start();
                System.out.println(t1.getName() +" ("+t1.getState()+") is started."); 
                
                Thread.sleep(100);
                t1.interrupt();
                System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted."); 
                
                Thread.sleep(300);
                System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now."); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    }
    
    class MyInterruptedThread extends Thread{
        public MyInterruptedThread(String name) {
            super(name);
        }
        
        public void run() {
            try {
                int i=0;
                while(true) {
                    Thread.sleep(300);
                    i++;
                    System.out.println(Thread.currentThread().getName() + "(" + this.getState() + ") loop " + i);
                }
                //System.out.println(Thread.currentThread().getName() + "(" + this.getState() + ") loop " + i);
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");
            }
        }
    }

    结果:

    t1 (NEW) is new.
    t1 (RUNNABLE) is started.
    t1 (TIMED_WAITING) is interrupted.
    t1 (RUNNABLE) catch InterruptedException.
    t1 (TERMINATED) is interrupted now.

    可以通过额外添加标记的方式终止“运行状态”的线程,示例:

    // Demo3.java的源码
    class MyThread extends Thread {
    
        private volatile boolean flag= true;
        public void stopTask() {
            flag = false;
        }
        
        public MyThread(String name) {
            super(name);
        }
    
        @Override
        public void run() {
            synchronized(this) {
                try {
                    int i=0;
                    while (flag) {
                        Thread.sleep(100); // 休眠100ms
                        i++;
                        System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
                    }
                } catch (InterruptedException ie) {  
                    System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
                }
            }  
        }
    }
    
    public class Demo3 {
    
        public static void main(String[] args) {  
            try {  
                MyThread t1 = new MyThread("t1");  // 新建“线程t1”
                System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
    
                t1.start();                      // 启动“线程t1”
                System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
    
                // 主线程休眠300ms,然后主线程给t1发“中断”指令。
                Thread.sleep(300);
                t1.stopTask();
                System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
    
                // 主线程休眠300ms,然后查看t1的状态。
                Thread.sleep(300);
                System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
            } catch (InterruptedException e) {  
                e.printStackTrace();
            }
        } 
    }

    结果:

    t1 (NEW) is new.
    t1 (RUNNABLE) is started.
    t1 (RUNNABLE) loop 1
    t1 (RUNNABLE) loop 2
    t1 (TIMED_WAITING) is interrupted.
    t1 (RUNNABLE) loop 3
    t1 (TERMINATED) is interrupted now.

    interrupted() 和 isInterrupted()的区别

    interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。
    区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。

    十,生产者消费者问题

    生产/消费者问题是个非常典型的多线程问题,涉及到的对象包括“生产者”、“消费者”、“仓库”和“产品”。他们之间的关系如下:
    (01) 生产者仅仅在仓储未满时候生产,仓满则停止生产。
    (02) 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
    (03) 当消费者发现仓储没产品可消费时候会通知生产者生产。
    (04) 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。

    package com.util.concurrent;
    
    public class ProductConsumeTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
    
            Repository rep = new Repository(100);
            Producer p = new Producer(rep);
            Consumer c = new Consumer(rep);
            p.produce(60);
            p.produce(120);
            c.consume(90);
            c.consume(150);
        }
    
    }
    
    class Repository {
        private int capacity;
        private int size;
        
        public Repository(int capacity) {
            this.capacity = capacity;
            this.size = 0;
        }
        
        public synchronized void produce(int num) {
            try {
                int left = num;
                while(left > 0) {
                    while(size >= capacity) {
                        wait();
                    }
                    int inc = (size + left) > capacity ? (capacity - size): left;
                    size += inc;
                    left -= inc;
                    System.out.printf("%s produce(%3d) <-- left=%3d, inc=%3d, size=%3d
    ", Thread.currentThread().getName(), num, left, inc, size);
                    notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        public synchronized void consume(int num) {
            try {
                int left = num;
                while(left > 0) {
                    while(size <= 0) {
                        wait();
                    }
                    int des = size < left ? size: left;
                    size -= des;
                    left -= des;
                    System.out.printf("%s consume(%3d) <-- left=%3d, des=%3d, size=%3d
    ", Thread.currentThread().getName(), num, left, des, size);
                    
                    notifyAll();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class Producer {
        private Repository rep;
        public Producer(Repository rep) {
            this.rep = rep;
        }
        
        public void produce(final int val) {
            new Thread() {
                public void run() {
                    rep.produce(val);
                }
            }.start();
        }
        
    }
    
    class Consumer extends Thread {
        private Repository rep;
        public Consumer(Repository rep) {
            this.rep = rep;
        }
        
        public void consume(final int val) {
            new Thread() {
                public void run() {
                    rep.consume(val);
                }
            }.start();
        }
    }

    结果: 

    Thread-1 produce( 60) <-- left=  0, inc= 60, size= 60
    Thread-4 consume(150) <-- left= 90, des= 60, size=  0
    Thread-2 produce(120) <-- left= 20, inc=100, size=100
    Thread-3 consume( 90) <-- left=  0, des= 90, size= 10
    Thread-4 consume(150) <-- left= 80, des= 10, size=  0
    Thread-2 produce(120) <-- left=  0, inc= 20, size= 20
    Thread-4 consume(150) <-- left= 60, des= 20, size=  0

     (01) Producer是“生产者”类,它与“仓库(depot)”关联。当调用“生产者”的produce()方法时,它会新建一个线程并向“仓库”中生产产品。
    (02) Customer是“消费者”类,它与“仓库(depot)”关联。当调用“消费者”的consume()方法时,它会新建一个线程并消费“仓库”中的产品。
    (03) Depot是“仓库”类,仓库中记录“仓库的容量(capacity)”以及“仓库中当前产品数目(size)”。
            “仓库”类的生产方法produce()和消费方法consume()方法都是synchronized方法,进入synchronized方法体,意味着这个线程获取到了该“仓库”对象的同步锁。这也就是说,同一时间,生产者和消费者线程只能有一个能运行。通过同步锁,实现了对“残酷”的互斥访问。
           对于生产方法produce()而言:当仓库满时,生产者线程等待,需要等待消费者消费产品之后,生产线程才能生产;生产者线程生产完产品之后,会通过notifyAll()唤醒同步锁上的所有线程,包括“消费者线程”,即我们所说的“通知消费者进行消费”。
          对于消费方法consume()而言:当仓库为空时,消费者线程等待,需要等待生产者生产产品之后,消费者线程才能消费;消费者线程消费完产品之后,会通过notifyAll()唤醒同步锁上的所有线程,包括“生产者线程”,即我们所说的“通知生产者进行生产”。

    十一,线程优先级和守护线程

    java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。

    java 中有两种线程:用户线程守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。
    用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:Java虚拟机在“用户线程”都结束后会后退出。

    每个线程都有一个优先级。“高优先级线程”会优先于“低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于“创建它的主线程的优先级”,当且仅当“创建它的主线程是守护线程”时“子线程才会是守护线程”。
    
    当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:
    (01) 调用了exit()方法,并且exit()有权限被正常执行。
    (02) 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。
    
    每一个线程都被标记为“守护线程”或“用户线程”。当只有守护线程运行时,JVM会自动退出。

    优先级示例:
    package com.util.concurrent;
    
    public class PriorityTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            System.out.println(Thread.currentThread().getName() + "("+Thread.currentThread().getPriority()+ ")");
            Thread t1 = new MyPriorityThread("t1");
            Thread t2 = new MyPriorityThread("t2");
            t1.setPriority(1);
            t2.setPriority(10);
            t1.start();
            t2.start();
            
        }
    
    }
    
    class MyPriorityThread extends Thread {
        public MyPriorityThread(String name) {
            super(name);
        }
        
        public void run() {
            for(int i=0; i< 30; i++) {
                System.out.println(currentThread().getName() + "(" + currentThread().getPriority() + "), loop " + i);
            }
        }
    }

    结果: 

    main(5)
    t1(1), loop 0
    t1(1), loop 1
    t1(1), loop 2
    t1(1), loop 3
    t2(10), loop 0
    t1(1), loop 4
    t2(10), loop 1
    t1(1), loop 5
    t2(10), loop 2
    t2(10), loop 3
    t1(1), loop 6
    t2(10), loop 4
    t2(10), loop 5
    t1(1), loop 7
    t2(10), loop 6
    t2(10), loop 7
    t2(10), loop 8
    t2(10), loop 9
    t2(10), loop 10
    t2(10), loop 11
    t2(10), loop 12
    t2(10), loop 13
    t1(1), loop 8
    t2(10), loop 14
    t1(1), loop 9
    t2(10), loop 15
    t2(10), loop 16
    t2(10), loop 17
    t2(10), loop 18
    t2(10), loop 19
    t2(10), loop 20
    t2(10), loop 21
    t2(10), loop 22
    t2(10), loop 23
    t2(10), loop 24
    t2(10), loop 25
    t2(10), loop 26
    t2(10), loop 27
    t2(10), loop 28
    t2(10), loop 29
    t1(1), loop 10
    t1(1), loop 11
    t1(1), loop 12
    t1(1), loop 13
    t1(1), loop 14
    t1(1), loop 15
    t1(1), loop 16
    t1(1), loop 17
    t1(1), loop 18
    t1(1), loop 19
    t1(1), loop 20
    t1(1), loop 21
    t1(1), loop 22
    t1(1), loop 23
    t1(1), loop 24
    t1(1), loop 25
    t1(1), loop 26
    t1(1), loop 27
    t1(1), loop 28
    t1(1), loop 29

    并不是优先级设为最高,就一定优先执行。

    守护线程示例:

    package com.util.concurrent;
    
    public class DeamonThreadTest {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            Thread t1 = new MyUserThread("u");
            Thread t2 = new MyDeamonThread("d");
            t2.setDaemon(true);
            t1.start();
            t2.start();
        }
    
    }
    
    class MyUserThread extends Thread {
        public MyUserThread(String name) {
            super(name);
        }
        
        public void run() {
            try {
                for (int i=0;i<5;i++) {
                    Thread.sleep(5);
                    System.out.println(this.getName() + "(isDeamon=" + this.isDaemon() + "),loop " + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    class MyDeamonThread extends Thread {
        public MyDeamonThread(String name) {
            super(name);
        }
        
        public void run() {
            try {
                for (int i=0;i<100000;i++) {
                    Thread.sleep(5);
                    System.out.println(this.getName() + "(isDeamon=" + this.isDaemon() + "),loop " + i);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    结果: 

    u(isDeamon=false),loop 0
    d(isDeamon=true),loop 0
    u(isDeamon=false),loop 1
    d(isDeamon=true),loop 1
    d(isDeamon=true),loop 2
    u(isDeamon=false),loop 2
    d(isDeamon=true),loop 3
    u(isDeamon=false),loop 3
    u(isDeamon=false),loop 4
    d(isDeamon=true),loop 4

    结果说明
    (01) 主线程main是用户线程,它创建的子线程t1也是用户线程。
    (02) t2是守护线程。在“主线程main”和“子线程t1”(它们都是用户线程)执行完毕,只剩t2这个守护线程的时候,JVM自动退出。

  • 相关阅读:
    过滤器--起步阶段
    常用指令-起步阶段
    模型和控制器-起步阶段
    指令介绍-起步阶段
    AngularJS简介-起步阶段
    c++之helloworld与命名空间
    c++ 之重要性
    一个没曾摸透的程序
    linux中什么是文件结构体?
    很全的linux网络编程技巧
  • 原文地址:https://www.cnblogs.com/IvySue/p/7467449.html
Copyright © 2011-2022 走看看