在上一篇文章中(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

Java并发编程:线程控制的更多相关文章

  1. Java 并发编程 | 线程池详解

    原文: https://chenmingyu.top/concurrent-threadpool/ 线程池 线程池用来处理异步任务或者并发执行的任务 优点: 重复利用已创建的线程,减少创建和销毁线程造 ...

  2. java并发编程 线程基础

    java并发编程 线程基础 1. java中的多线程 java是天生多线程的,可以通过启动一个main方法,查看main方法启动的同时有多少线程同时启动 public class OnlyMain { ...

  3. java并发编程 | 线程详解

    个人网站:https://chenmingyu.top/concurrent-thread/ 进程与线程 进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配 ...

  4. Java并发编程:线程间通信wait、notify

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  5. Java并发编程:线程和进程的创建(转)

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  6. Java并发编程——线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  7. Java并发编程——线程池

    本文的目录大纲: 一.Java中的ThreadPoolExecutor类 二.深入剖析线程池实现原理 三.使用示例 四.如何合理配置线程池的大小 一.Java中的ThreadPoolExecutor类 ...

  8. Java并发编程--线程封闭(Ad-hoc封闭 栈封闭 ThreadLocal)

    线程封闭实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发.避免并发最简单的方法就是线程封闭.什么是线程封闭呢?就是把对象封装到一个线程里,只有这一个线程能看到此对象.那么这个对象就算不是线程 ...

  9. Java并发编程-线程可见性&线程封闭&指令重排序

    一.指令重排序 例子如下: public class Visibility1 { public static boolean ready; public static int number; } pu ...

  10. JAVA 并发编程-线程范围内共享变量(五)

    线程范围内共享变量要实现的效果为: 多个对象间共享同一线程内的变量 watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsi ...

随机推荐

  1. 子进程 已安装 post-installation 脚本 返回错误状态 1,dpkg: 处理软件包 python-crypto (--configure)时出错: 该软件包正处于非常不稳定的状态;

    这几天在学习redis的时候,装软件总是报错,两个问题都和dpkg有关,上网查阅了些解决办法,发现整体来说执行以下方法均可解决. 虽然每个人需要安装的包不同,但是出现此类问题的不同也只有安装包的名字, ...

  2. JVM内存分配与回收策略

    对象优先在Eden分配 大多数情况下,对象在新生代Eden区中分配. 当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC. Minor GC:新生代GC,指发生在新生代的垃圾收集动作 ...

  3. 初识html5——试试博文编辑器

    1.html5简介 HTML5 将成为 HTML.XHTML 以及 HTML DOM 的新标准. HTML 的上一个版本诞生于 1999 年.自从那以后,Web 世界已经经历了巨变. HTML5 仍处 ...

  4. MES设备支持快速完工

    1) 在菜单界面点击指定快速键 2) 初始界面 3) 一般流程 a) 扫描任务单号,即可完成工序加工 a1) 获取任务单工序的条件 按任务单卡号或配模的模具卡号搜索行状态为O的工序 a2) 工序完工操 ...

  5. if(){}else 语句的正确写法以及它的嵌套使用

    if(一个返回bool值的条件表达式) { 程序块 } else{} 它的执行过程我们可以通过一个程序来了解 static void Main(string[] args) { ) // 条件1 { ...

  6. Django之Model世界

    Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用MySQLdb 来连接数据库,并编写数据访问层代码 业务逻辑层去调用数据访问层执行数 ...

  7. Nginx的Rewrite规则与实例

    通过Rewrite规则可以实现规范的URL.根据变量来做URL转向及选择配置,用好Rewrite有时起到事半功倍的效果. 语法 Nginx的Rewrite相比Apache的要好理解很多,主要使用指令有 ...

  8. Zookeeper与Kafka集群搭建

    一 :环境准备: 物理机window7 64位 vmware 3个虚拟机 centos6.8  IP为:192.168.17.[129 -131] JDK1.7安装配置 各虚拟机之间配置免密登录 安装 ...

  9. linux学习初体验

    前天买了鸟哥私房菜,昨天一早就到了.阅读了前两章. 一.Linux是什么 二.Linux如何学习 还有前面的计算机概论也值得一看.对于计算机构成,硬件解读,数据存储,比一般的电脑维修类的书深一些. 第 ...

  10. wemall app商城源码Android之支付宝接口公用函数

    wemall-mobile是基于WeMall的Android app商城,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可定制修改.本文分享wemall app商城源码Android之  ...