Java并发编程:线程控制

在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期。这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态,如何设置线程的优先级等。

一、join()   等待阻塞

 让一个线程等待另一个线程完成才继续执行。如A线程线程执行体中调用B线程的join()方法,则A线程被阻塞,知道B线程执行完为止,A才能得以继续执行。

package com.chanshuyi.thread;

public class ThreadDemo6 {

    public static void main(String[] args) {
        //I'm the thread.
        //Main Thread is Running over.
        Thread thread = new Thread(){
            @Override
            public void run(){
                 System.out.println("I'm the thread.");
                 try{
                     Thread.sleep(2000);  //让其休眠2秒,测试主线程是否等待线程执行完再执行
                 }catch(Exception e){
                     e.printStackTrace();
                 }
            }
        };
        thread.start();
        try {
            thread.join();    //让main线程等待线程执行完再执行
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Main Thread is Running over.");
    }

}

代码中我特意让子线程休眠了2秒,但是最终的结果还是main线程等待子线程运行后再继续运行。

二、wait()/notify()   锁阻塞 

wait() 和 notify() 方法的调用需要调用的方法有一个锁对象,主要用于进行不同线程之间的同步协作,常见的有生产者消费者模型。

package com.chanshuyi.thread;

/**
 * 生产者消费者模型
 * @author Administrator
 *
 */
public class ThreadDemo92 {

    public static void main(String[] args) {
        final ProduceConsumer pc = new ProduceConsumer();
        //生产者线程
        new Thread(){
            @Override
            public void run(){
                //生产10次
                for(int i = 0; i < 5; i++){
                    System.out.println("Ready to Produce:" + (i + 1));
                    pc.produce(i);
                }
            }
        }.start();
        //消费者线程
        new Thread(){
            @Override
            public void run(){
                //消费10次
                for(int j = 0; j < 5; j++){
                    System.out.println("Ready to Consume:" + (j + 1));
                    pc.consume(j);
                }
            }
        }.start();
    }
}

class ProduceConsumer{
    //生产者
    public synchronized void produce(int i){
        if(isEmpty){
            //没东西了,可以生产
            num = (int)(Math.random() * 100);
            System.out.println("Producer:" + (i + 1) + "," + num);
            isEmpty = false;
            notify();
        }else{
            try {
                System.out.println("producer执行wait操作:" + (i + 1));
                wait();
                System.out.println("producer醒来:" + (i + 1));
                num = (int)(Math.random() * 100);
                System.out.println("Producer:" + (i + 1) + "," + num);
                isEmpty = false;
                notify();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    //消费者
    public synchronized void consume(int i){
        if(!isEmpty){
            System.out.println("Consumer:" + (i + 1) + "," + num);
            isEmpty = true;
            notify();
        }else{
            try {
                System.out.println("consumer执行wait操作:" + (i + 1));
                wait();
                System.out.println("consumer醒来:" + (i + 1));
                System.out.println("Consumer:" + (i + 1) + "," + num);
                isEmpty = true;
                notify();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    
    public ProduceConsumer(){
        isEmpty = true; //默认为空
    }
            
    private boolean isEmpty;  //是否为空
    
    private int num; //生产的东西

    public boolean isEmpty() {
        return isEmpty;
    }

    public void setEmpty(boolean isEmpty) {
        this.isEmpty = isEmpty;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }    
}

线程同步属于线程的一个非常重要的知识,而且也相对比较复杂,这里只做一个简单的介绍,后面会有更加详细的讲解。

三、sleep()   其他类型阻塞

让当前的正在执行的线程暂停指定的时间,并进入阻塞状态。直接使用 Thread.sleep(long millionSeconds) 就可以了

package com.chanshuyi.thread;

public class ThreadDemo7 {

    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run(){
                System.out.println("Sleep 2 Seconds.");
            }
        };
        thread.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Awake");
    }
} 

四、yield()  线程让步

将线程从运行状态转换为就绪状态。

当某个线程调用 yiled() 方法从运行状态转换到就绪状态后,CPU 会从就绪状态线程队列中只会选择与该线程优先级相同或优先级更高的线程去执行。

在使用时直接用 Thread.yield() 静态方法就可以了。一般情况下我们的 CPU 都很难达到 100% 的利用率,所以当我们使用 yield() 方法将线程挂起之后,一般又会立即获得资源,继续执行,因此很难写个程序去进行验证。而且这个方法在工作中也是比较少用到,所以只需要了解其作用就可以了。

sleep() 和 yield() 两者的区别:

① sleep()方法会给其他线程运行的机会,不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会。yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。

② 当线程执行了 sleep(long millis) 方法,将转到阻塞状态,参数millis指定睡眠时间。当线程执行了yield()方法,将转到就绪状态。

③ sleep() 方法声明抛出InterruptedException异常,而 yield() 方法没有声明抛出任何异常。

package com.chanshuyi.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadDemo8 {

    public static void main(String[] args) {
        final Lock lock = new ReentrantLock();
        Thread thread1 = new Thread(){
            @Override
            public void run(){
                System.out.println("Enter thread1.");
                lock.lock();
                System.out.println("Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Thread.yield();
                System.out.println("I'm the thread1");
                lock.unlock();
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run(){
                System.out.println("Enter thread2.");
                lock.lock();
                System.out.println("Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Thread.yield();
                System.out.println("I'm the thread2");
                lock.unlock();
            }
        };
        Thread thread3 = new Thread(){
            @Override
            public void run(){
                System.out.println("Enter thread3.");
                lock.lock();
                System.out.println("Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Thread.yield();
                System.out.println("I'm the thread3");
                lock.unlock();
            }
        };
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread2.setPriority(Thread.NORM_PRIORITY);
        thread3.setPriority(Thread.MAX_PRIORITY);
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

在上面中,我们让3个线程竞争一个锁。其中一个线程随机获得lock锁,之后休眠两秒等待其他2个线程进入Lock Block状态。之后获得锁lock锁的线程调用yield()释放锁,这个时候,应该是优先级最高的那个线程获得锁,但实际上却不是这样的,具体原因我也没分析出来。下面是一些执行结果,感兴趣的朋友可以分析一下。

Enter thread2.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
Enter thread3.
Enter thread1.
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1


Enter thread2.
Enter thread3.
Enter thread1.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1


Enter thread3.
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
Enter thread1.
Enter thread2.
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
I'm the thread2

Enter thread2.
Thread 2 had acquire the lock. Thread2 will sleep for 2 seconds
Enter thread3.
Enter thread1.
I'm the thread2
Thread 3 had acquire the lock. Thread3 will sleep for 2 seconds
I'm the thread3
Thread 1 had acquire the lock. Thread1 will sleep for 2 seconds
I'm the thread1

五、setPriority()  改变线程的优先级

每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。每个线程默认的优先级都与创建它的线程的优先级相同。main线程默认具有普通优先级。

设置线程优先级:setPriority(int priorityLevel)。参数priorityLevel范围在1-10之间,常用的有如下三个静态常量值:

MAX_PRIORITY:10

MIN_PRIORITY:1

NORM_PRIORITY:5

获取线程优先级:getPriority()。

注:具有较高线程优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。

package com.chanshuyi.thread;

public class ThreadDemo7 {

    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run(){
                System.out.println("I'm the Priority Test Thread.");
            }
        };
        thread.setPriority(Thread.MAX_PRIORITY);
        thread.start();
    }

}

六、setDaemon(true)  设置为后台线程

概念/目的:后台线程主要是为其他线程(相对可以称之为前台线程)提供服务,或“守护线程”。如JVM中的垃圾回收线程。

生命周期:后台线程的生命周期与前台线程生命周期有一定关联。主要体现在:当所有的前台线程都进入死亡状态时,后台线程会自动死亡(其实这个也很好理解,因为后台线程存在的目的在于为前台线程服务的,既然所有的前台线程都死亡了,那它自己还留着有什么用...伟大啊 ! !)。

设置后台线程:调用Thread对象的setDaemon(true)方法可以将指定的线程设置为后台线程。

package com.chanshuyi.thread;

public class ThreadDemo91 {

    public static void main(String[] args) {
        //输出:Main Thread is going to die
        Thread thread = new Thread(){
            @Override
            public void run(){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("Hello, I'm background thread.");
            }
        };
        thread.setDaemon(true);
        thread.start();
        System.out.println("Main Thread is going to die.");
    }
}

上面的代码中存在两个线程,一个是Main线程,是前台线程,一个是我们创建的后台线程。我们在后台线程中故意使其休眠了1秒,而在这1秒钟内前台线程Main已经执行完毕了,所以后台线程也就直接结束了”Main Thread is going to die.“,而不会输出后台线程中的语句。

判断线程是否是后台线程:调用thread对象的isDeamon()方法。

注:main线程默认是前台线程,前台线程创建中创建的子线程默认是前台线程,后台线程中创建的线程默认是后台线程。调用setDeamon(true)方法将前台线程设置为后台线程时,需要在start()方法调用之前,否则一但线程运行,将无法改变其类型。前天线程都死亡后,JVM通知后台线程死亡,但从接收指令到作出响应,需要一定的时间。

参考博文:

http://www.cnblogs.com/lwbqqyumidi/p/3817517.html

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Janti

JVM活学活用——类加载机制

类的实例化过程 ---- 有父类的情况 1. 加载父类静态     1.1 为静态属性分配存储空间并赋初始值     1.2 执行静态初始化块和静态初始化...

4088
来自专栏深度学习之tensorflow实战篇

Python中if __name__ == "__main__": 的作用

Python中if __name__ == "__main__": 的作用 在很多python脚本中在最后的部分会执行一个判断语句if __name__ =...

3415
来自专栏java一日一条

JVM运行时数据区域及GC

方法区 (Method Area or Permanent Generation):

1102
来自专栏用户2442861的专栏

Java虚拟机工作原理详解

http://blog.csdn.net/bingduanlbd/article/details/8363734

951
来自专栏Janti

Java多线程高并发学习笔记(一)——Thread&Runnable

 进程与线程 首先来看百度百科关于进程的介绍: 进程是一个具有独立功能的程序关于某个数据集合的一次运行活动。它可以申请和拥有系统资源,是一个动态的概念,是一个活...

74810
来自专栏与神兽党一起成长

使用commons-pool管理FTP连接

在封装一个FTP工具类文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。

1422
来自专栏专注 Java 基础分享

基于 JDK 的动态代理机制

874
来自专栏我是攻城师

深入理解Java类加载器机制

Java里面的类加载机制,可以说是Java虚拟机核心组件之一,掌握和理解JVM虚拟机的架构,将有助于我们站在底层原理的角度上来理解Java语言,这也是为什么我们...

2992
来自专栏Android 研究

Java虚拟机基础——2JVM运行时数据区

本篇文章主要讲解JVM运行时数据区,所以我们按照线程是否私有的维度将本篇文章一分为二,分为线程私有数据区和所有线程共有的数据区。而在线程私有的数据区又可以分为程...

1375
来自专栏java一日一条

JAVA中volatile、synchronized和lock解析

在研究并发程序时,我们需要了解java中关键字volatile和synchronized关键字的使用以及lock类的用法。

1062

扫码关注云+社区

领取腾讯云代金券