博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java 线程通信与线程的生命周期
阅读量:3926 次
发布时间:2019-05-23

本文共 16293 字,大约阅读时间需要 54 分钟。

一、并发同步

1、线程通信 -- 使用 synchronized 与 等待和唤醒机制

      线程通信不同的线程执行不同的任务,如果这些任务有某种关系,线程之间必须能够通信、协调完成工作.

经典的生产者和消费者案例(Producer/Consumer):

public class ThreadDemo {    public static void main(String[] args) {        // 创建生产者和消费者共同的资源对象        ShareResource shareResource = new ShareResource();        // 启动生产者线程        new Thread(new Producer(shareResource)).start();        // 启动消费者线程        new Thread(new Consumer(shareResource)).start();    }}// 共享资源--水果class ShareResource{    private String name;    private String colour;    /**     *  生产者向共享资源对象中存储数据     * @param name  存储的名称     * @param colour 存储的颜色     */    public void push(String name, String colour){        this.name = name;        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.colour = colour;    }    /**     * 消费者从共享资源对象中取出数据     */    public void popup(){        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(this.name + "--" + this.colour);    }}// 生产者class Producer implements  Runnable{    // 共享资源对象    private ShareResource shareResource = null;    public Producer(ShareResource shareResource){        this.shareResource = shareResource;    }    @Override    public void run() {        for (int i = 0; i < 50; i++) {            if(i % 2 == 0){                shareResource.push("苹果","红色");            }else{                shareResource.push("柚子","橘黄色");            }        }    }}// 消费者class Consumer implements Runnable{    // 共享资源对象    private ShareResource shareResource = null;    public Consumer(ShareResource shareResource){        this.shareResource = shareResource;    }    @Override    public void run() {        for (int i = 0; i < 50; i++) {            shareResource.popup();        }    }}

                         

分析生产者和消费者案例存在的问题:

    建议在生产水果的名称和颜色之间以及在消费打印之前使用Thread.sleep(10); 使效果更明显.

    问题1:出现名称与颜色紊乱的情况.

                 解决方案:只要保证在生产水果的名称和颜色的过程保持同步,中间不能被消费者线程进来取走数据.

                 可以使用同步代码块/同步方法/Lock机制来保持同步性.

// 共享资源--水果class ShareResource{    private String name;    private String colour;    /**     *  生产者向共享资源对象中存储数据     * @param name  存储的名称     * @param colour 存储的颜色     */    synchronized public void push(String name, String colour){        this.name = name;        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.colour = colour;    }    /**     * 消费者从共享资源对象中取出数据     */    synchronized public void popup(){        try {            Thread.sleep(10);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(this.name + "--" + this.colour);    }}

    问题2:应该出现生产一个数据,消费一个数据,交替出现

                 解决方案:得使用 等待和唤醒 机制.

// 共享资源--水果class ShareResource{    private String name;    private String colour;    private boolean isEmpty = true; // 表示共享资源对象是否为空的状态    /**     *  生产者向共享资源对象中存储数据     * @param name  存储的名称     * @param colour 存储的颜色     */    synchronized public void push(String name, String colour){        try {            while(!isEmpty){ // 当前对象为不空时等待消费者来获取                // 使用同步锁对象来调用,表示当前线程释放同步锁,进入等待池,只能被其他线程所唤醒                this.wait();            }            this.name = name;            Thread.sleep(10);            this.colour = colour;            isEmpty = false; // 设置共享资源中数据为空            this.notify(); // 唤醒一个线程(消费者),多个使用notifyAll()        } catch (InterruptedException e) {            e.printStackTrace();        }    }    /**     * 消费者从共享资源对象中取出数据     */    synchronized public void popup(){        try {            while(isEmpty){ // 当前对象为空时等待生产者来生产                // 使用同步锁对象来调用,表示当前线程释放同步锁,进入等待池,只能被其他线程所唤醒                this.wait();            }            Thread.sleep(10);            System.out.println(this.name + "--" + this.colour);            isEmpty = true; // 设置共享资源中数据为空            this.notify(); // 唤醒一个线程(生产者),多个使用notifyAll()        } catch (InterruptedException e) {            e.printStackTrace();        }    }}

java.lang.Object 类提供了两类(等待和唤醒)用于线程通信的方法.

       wait():执行该方法的线程对象释放同步锁,JVM把该线程存放到等待池中,等待其他的线程唤醒该线程.

       notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,把线程转到锁池中等待.

       notifyAll():执行该方法的线程唤醒在等待池中等待的所有的线程,把线程转到锁池中等待.

注意上述方法只能被同步监听锁对象来调,否则报错 IllegalMonitorStateException异常.

同步锁池:

      同步锁一般都选择多个线程共同的资源对象.

      当前生产者在生产数据时(先拥有同步锁),其他线程就在锁池中等待获取锁.

      当线程执行完同步代码块时,就会释放同步锁,其他线程开始抢锁的使用权.

多个线程只有共享使用相同的一个对象时,多线程之间才有互斥效果,把这个用来做互斥的对象称之为 同步监听对象/同步锁.

      同步锁对象可以选择任意类型的对象都可以,只需要保证多个线程使用的是相同锁对象即可.

因为,只有同步监听锁对象才能调用 wait和 notify方法,所以,wait和 notify方法应该存在于Object类中,而不是Thread类中.

 

2、线程通信 -- 使用Lock机制和Condition接口

     wait和notify方法,只能被同步监听锁对象来调用,否则报错 IllegalMonitorStateException异常.

     而Lock机制根本就没有同步锁,也就没有自动获取锁和自动释放锁的概念。

     因为没有同步锁,所以Lock机制不能调用wait和notify方法.

解决方案:Java5中提供了Lock机制的同时也提供了处理Lock机制的通信控制的Condition接口.

从Java5开始,可以:

      1)使用Lock机制取代 synchronized代码块和 synchronized方法.

      2)使用Condition接口对象的await,signal,signalAll方法取代Object类中的wait,notify,notifyAll方法.

Condition接口提供一个使用实例

     

public class ThreadDemo {    public static void main(String[] args) {        // 创建生产者和消费者共同的资源对象        ShareResource shareResource = new ShareResource();        // 启动生产者线程        new Thread(new Producer(shareResource)).start();        // 启动消费者线程        new Thread(new Consumer(shareResource)).start();    }}// 共享资源--水果class ShareResource{    private String name;    private String colour;    private boolean isEmpty = true; // 表示共享资源对象是否为空的状态    private final Lock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    /**     *  生产者向共享资源对象中存储数据     * @param name  存储的名称     * @param colour 存储的颜色     */    public void push(String name, String colour){        lock.lock(); // 获取锁        try {            while(!isEmpty){ // 当前对象为不空时等待消费者来获取                condition.await();            }            this.name = name;            Thread.sleep(10);            this.colour = colour;            isEmpty = false; // 设置共享资源中数据为空            condition.signal(); // 唤醒一个线程(消费者),多个使用signalAll()        } catch (InterruptedException e) {            e.printStackTrace();        }finally {            lock.unlock(); // 释放锁        }    }    /**     * 消费者从共享资源对象中取出数据     */    public void popup(){        lock.lock(); // 获取锁        try {            while(isEmpty){ // 当前对象为空时等待生产者来生产                condition.await();            }            Thread.sleep(10);            System.out.println(this.name + "--" + this.colour);            isEmpty = true; // 设置共享资源中数据为空            condition.signal(); // 唤醒一个线程(消费者),多个使用signalAll()        } catch (InterruptedException e) {            e.printStackTrace();        }finally {            lock.unlock(); // 释放锁        }    }}// 生产者class Producer implements  Runnable{    // 共享资源对象    private ShareResource shareResource = null;    public Producer(ShareResource shareResource){        this.shareResource = shareResource;    }    @Override    public void run() {        for (int i = 0; i < 50; i++) {            if(i % 2 == 0){                shareResource.push("苹果","红色");            }else{                shareResource.push("柚子","橘黄色");            }        }    }}// 消费者class Consumer implements Runnable{    // 共享资源对象    private ShareResource shareResource = null;    public Consumer(ShareResource shareResource){        this.shareResource = shareResource;    }    @Override    public void run() {        for (int i = 0; i < 50; i++) {            shareResource.popup();        }    }}

 

二、线程的生命周期

      生命周期:一个事物从出生的那一刻开始到最终死亡中间的整个过程。线程也是有生命周期的,也存在不同的状态的,状态相互之间的可转换。如下图:

      

线程对象的状态存放在Thread类的内部类(State)中:

注意:Thread.State类其实是一个枚举类,因为线程对象的状态是固定的,只有6种,此时使用枚举来表示是最恰当的。

     

      有人又把阻塞状态、等待状态和计时等待状态合称为阻塞状态。如下图:

      

1、新建状态(new)

      使用new创建一个线程对象,仅仅在堆中分配内存空间,在调用start方法之前;新建状态下,线程压根就没有启动,仅仅只是存在一个线程对象而已。

      Thread t = new Thread(); //此时t就属于新建状态

      当新建状态下的线程对象调用了start方法,此时从新建状态进入可运行状态.

      线程对象的start方法只能调用一次,否则报错:IllegalThreadStateException异常。

2、可运行状态(runnable)

  分成两种状态:ready和running。分别表示就绪状态和运行状态。

       就绪状态:线程对象调用start方法之后,等待JVM的调度(此时该线程并没有运行)。

       运行状态:线程对象获得JVM调度,如果存在多个CPU,那么允许多个线程并行运行。

3、阻塞状态(blocked)

     正在运行的线程因为某些原因放弃CPU,暂时停止运行,就会进入阻塞状态。此时JVM不会给线程分配CPU,直到线程重新进入就绪状态,才有机会转到运行状态.

     阻塞状态只能先进入就绪状态,而不能直接进入运行状态.

     阻塞状态的两种情况:

            1)当A线程处于运行过程时,试图获取同步锁时,却被B线程获取,此时JVM把当前A线程存到对象的锁池中,A线程进入阻塞状态.

            2)当线程处于运行过程时,发出了IO请求时,此时进入阻塞状态.

4、等待状态(waiting)(等待状态只能被其他线程唤醒):此时使用的无参数的wait方法,

       当线程处于运行过程时,调用了wait()方法,此时JVM把当前线程存在对象等待池中.

5、计时等待状态(timed waiting)(使用了带参数的wait方法或者sleep方法)

       1)当线程处于运行过程时,调用了wait(long time)方法,此时JVM把当前线程存在对象等待池中.

       2)当前线程执行了sleep(long time)方法.

6、终止状态(terminated):通常称为死亡状态,表示线程终止.

        1)正常执行完run方法而退出(正常死亡).

        2)遇到异常而退出(出现异常之后,程序就会中断)(意外死亡).

线程一旦终止,就不能再重启启动,否则报错:IllegalThreadStateException异常。

 

三、线程的控制操作

1、线程休眠:让执行的线程暂停一段时间,进入计时等待状态

     方法:static void sleep(long millis)

     调用sleep后,当前线程放弃CPU,在指定时间段之内,sleep所在线程不会获得执行的机会。此状态下的线程不会释放同步锁/同步监听器。该方法更多的用于模拟网络延迟,让多线程并发访问同一个资源的错误效果更明显,在开发中也会故意使用该方法。

    线程的sleep方法应该写在线程的run()方法里,sleep()又是静态方法,所以最好的调用方法就是 Thread.sleep()。

    注意:sleep方法只能让当前线程睡眠。调用某一个线程类的对象t.sleep(),睡眠的不是t,而是当前线程。

public static void main(String[] args) throws InterruptedException{        for (int i = 10; i >0; i--) {            System.out.println("剩余"+ i + "秒");            Thread.sleep(1000);        }        System.out.println("Boom....");    }

    java.util.concurrent.TimeUnit也可以控制线程睡眠: 

TimeUnit.SECONDS.sleep(1);

TimeUnit.MINUTES.sleep(1);
TimeUnit.HOURS.sleep(1);
TimeUnit.DAYS.sleep(1);

2、联合线程:

     方法:void join()  :表示一个线程等待另一个线程(这个join的线程)完成后才执行。

     join方法被调用之后,线程对象处于阻塞状态

    主要作用就是同步,它可以使得线程之间的并发执行变为串行执行。有人也把这种方式称为联合线程,就是说把当前线程和当前线程所在的线程联合成一个线程。

    注意:join方法必须在线程start方法调用之后调用才有意义。如果一个线程都没有start,那它也就无法同步了。

    比如:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。

     join方法中可以传入参数,如果A线程中调用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并发执行。注意:jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。     

public class ThreadDemo {    public static void main(String[] args) throws InterruptedException{        System.out.println("begin....");        JoinThread joinThread = new JoinThread();        for (int i = 0; i < 15; i++) {            System.out.println("main " + i);            if (i ==5 ) {                joinThread.start();//启动join线程            }            if (i == 10 ) {                joinThread.join(); //强制运行该线程,直到结束后运行另一个线程            }        }        System.out.println("end...");    }}//联合线程class JoinThread extends Thread {    public void run() {        for (int i = 0; i < 50; i++) {            System.out.println("Join" + i);        }    }}

3、后台线程:

     在后台运行的线程,其目的是为其他线程提供服务,也称为“守护线程"。JVM的垃圾回收线程就是典型的后台线程。

     特点:若所有的前台线程都死亡,后台线程自动死亡。反过来,如果后台线程先执行完,前台线程会不停止。

方法:

     boolean isDaemon() ;//测试这个线程是否是守护线程。

     void setDaemon(boolean on) ;//将此线程标记为 daemon线程或用户线程。 

     前台线程创建的线程默认是前台线程,可以通过setDaemon方法设置为后台线程,并且当且仅当后台线程创建的新线程时,新线程是后台线程。

     设置后台线程:thread.setDaemon(true);该方法必须在start方法调用前,否则报错:IllegalThreadStateException异常

public class ThreadDemo {    public static void main(String[] args){        //判断当前线程是否是守护线程        System.out.println(Thread.currentThread().isDaemon()); // false        for (int i = 0; i < 10; i++) {            System.out.println("main " +i);            if(i == 5){                DaemomThread dt =new DaemomThread();                dt.setDaemon(true);//设置为后台线程,并且在调用start之前设置                dt.start();            }            //当前台线程结束之后.后台线程也会相应的自动结束        }    }}//后台线程class DaemomThread extends Thread{    public void run(){        for (int i = 0; i <133; i++) {            System.out.println(super.getName()+ "  " + i);            //System.out.println(Thread.currentThread().isDaemon());        }    }}

4、线程优先级(1-10):

      每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关,并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度。

     MAX_PRIORITY=10,最高优先级

     MIN_PRIORITY=1,最低优先级

     NORM_PRIORITY=5,默认优先级

方法:

     int getPriority() ; //返回此线程的优先级

     void setPriority(int newPriority) ;//更改此线程的优先级。

     每个线程都有默认优先级,主线程默认优先级为5,如果A线程创建了B线程,那么B线程和A线程具有相同优先级。

注意:不同的操作系统支持的线程优先级不同的,建议使用上述三个常量优先级就够了,不要自定义。

public class ThreadDemo {    public static void main(String[] args){        //设置当前线程的优先级        Thread.currentThread().setPriority(8);        //获取当前的线程的优先级级别        System.out.println(Thread.currentThread().getPriority()); // 8        PriorityThread max = new PriorityThread("高优先级");        max.setPriority(Thread.MAX_PRIORITY);        PriorityThread min = new PriorityThread("低优先级");        min.setPriority(Thread.MIN_PRIORITY);        min.start();        max.start();    }}class PriorityThread extends Thread{    public PriorityThread(String name){        super(name);//调用父类的构造器    }    @Override    public void run(){        for (int i = 0; i < 10; i++) {            System.out.println(super.getName() +  "-" + i);        }    }}

5、线程礼让:

     static void yield() 方法:表示当前线程对象提示调度器自己愿意让出CPU资源,但是调度器可以自由的忽略该提示。

     调用该方法之后,线程对象进入就绪状态,所以完全有可能某个线程调用了yield()之后,线程调度器又把它调度出来重新执行。

     从 Java7 提供的文档上可以清楚的看出,开发中很少会使用到该方法,该方法主要用于调试或测试,它可能有助于因多线程竞争条件下的错误重现现象。

public class ThreadDemo {    public static void main(String[] args){        YieldThread max = new YieldThread("高优先级");        max.setPriority(Thread.MAX_PRIORITY);        YieldThread min = new YieldThread("低优先级");        min.setPriority(Thread.MIN_PRIORITY);        min.start();        max.start();    }}class YieldThread extends Thread{    public YieldThread(String name){        super(name);//调用父类的构造器    }    @Override    public void run(){        for (int i = 0; i < 100; i++) {            System.out.println(super.getName() +  "-" + i);            if(i ==20){                Thread.yield();//当i=20的时候,做出礼让 (效果不明显)            }        }    }}

sleep方法和yield方法的区别:

  1)都能使当前处于运行状态的线程放弃CPU,把运行的机会给其他线程.

  2)sleep方法会给其他线程运行机会,但是不考虑其他线程的优先级,yield方法只会给相同优先级或者更高优先级的线程运行的机会.

  3)调用sleep方法后,线程进入计时等待状态,调用yield方法后,线程进入就绪状态.

6、线程中断方法

1)interrupt()方法,作用于调用者线程

     表示中断此线程(指调用该方法的Thread实例所代表的线程),但实际上只是给线程设置一个中断标志,线程仍会继续运行。

2)isInterrupted()方法,作用于调用者线程

     表示测试此线程(调用者代表的线程)是否已经中断,不清除中断状态。

3)interrupted() 静态方法,作用于当前线程

     表示测试当前线程是否已经中断并清除中断状态(中断标志),有中断标记的话,返回一个ture并清除中断状态,第二次再调用时中断状态已经被清除,则返回一个false。

public class InterruptDemo {    public static void main(String[] args) {        Thread threadDemo = new Thread(new ThreadDemo());        threadDemo.start();        // 给 threadDemo打中断标记        threadDemo.interrupt();        // 此线程:threadDemo        System.out.println(threadDemo.isInterrupted()); // true        System.out.println(threadDemo.isInterrupted()); // true        System.out.println("线程是否存活" + threadDemo.isAlive()); // true        // 当前线程:main线程 无标记        System.out.println(Thread.interrupted()); // fasle        // 给当前线程打中断标记        Thread.currentThread().interrupt();        System.out.println(Thread.interrupted()); // true        System.out.println(Thread.interrupted()); // fasle    }    private static class ThreadDemo implements Runnable{        @Override        public void run() {            for (int i = 0; i < 10; i++) {                System.out.println(Thread.currentThread().getName() + "==" + i);            }        }    }}

     interrupt()只是打标记,若想要是实现调用interrupt()方法真正的终止线程,则可以在线程的run方法中做处理即可,比如直接跳出run()方法使线程结束。

private static class ThreadDemo implements Runnable{        @Override        public void run() {            for (int i = 0; i < 10; i++) {                System.out.println(Thread.currentThread().getName() + "==" + i);                if(Thread.currentThread().isInterrupted()){                    return;                }            }        }    }

7、定时器和线程组

1、定时器

     在JDK的java.util包中提供了Timer类,可以定时执行特定的任务.

     TimerTask子类表示定时器执行的某一项任务.

常用方法:

     void schedule(TimerTask task, Date firstTime, long period) ;//从指定 的时间开始 ,对指定的任务执行重复的 固定延迟执行 。

     void schedule(TimerTask task, long delay) ;//在指定的延迟之后安排指定的任务执行。

     void cancel();// 终止此计时器,丢弃任何当前计划的任务。 

public class ThreadDemo {    public static void main(String[] args){        System.out.println("begin...");        Timer timer = new Timer();        for (int i = 0; i < 5; i++) {            //此处Timer必须设置为前台线程,否则看不到效果            //timer.schedule(new PrintDemo(), 3000);        }        System.out.println("end...");        //timer.cancel();//终止此计时器                // 只是用一次,可以用匿名内部类        timer.schedule(new TimerTask() {            @Override            public void run() {                System.out.println(new Date().toLocaleString());            }        }, 2000,1000);    }}//定时器class PrintDemo extends TimerTask {    @Override    public void run(){        //在3秒之后打印内容5次        System.out.println("五一节快乐~");    }}

2、线程组

     ThreadGroup类表示线程组,可以对一组线程进行集中管理.

     用户在创建线程对象时,可以通过构造器指定其所属的线程组.

     构造器:Thread(ThreadGroup group, String name) ;//分配一个新的 Thread对象。

     如果A线程创建了B线程,如果没有设置B线程的分组,那么B线程加入到A线程的线程组.

     一旦线程加入某个线程组,该线程就一直存在于该线程组中直到线程死亡,不能在中途修改线程的分组.

     当Java程序运行时,JVM会创建名为main的线程组,在默认情况下,所有的线程都是该在线程组下.

 

ends ~

 

转载地址:http://nncgn.baihongyu.com/

你可能感兴趣的文章
Java笔记之JTextField JTextArea区别
查看>>
Android学习笔记之Spinner
查看>>
UVA 题目401 - Palindromes
查看>>
Android学习笔记之SeekBar
查看>>
题目26 孪生素数问题
查看>>
java web 连接mysql数据库
查看>>
docker架构
查看>>
Docker Client创建与命令执行
查看>>
springMVC学习笔记
查看>>
PageRank算法与特征向量和特征值(eigenvector和eigenvalue)
查看>>
HITS算法--从原理到实现
查看>>
MapReduce原理
查看>>
zookeeper原理
查看>>
MapReduce入门
查看>>
WEB服务器、应用程序服务器、HTTP服务器区别
查看>>
归并排序(JAVA)
查看>>
对Java Serializable(序列化)的理解和总结
查看>>
Netty Buffer(缓冲)
查看>>
Docker简单介绍
查看>>
.ftl文件 是什么文件
查看>>