专栏首页陈树义Java并发编程:线程控制

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 条评论
登录 后参与评论

相关文章

  • Java并发编程:线程的基本状态

    一、线程的基本状态 线程基本上有5种状态,分别是:NEW、Runnable、Running、Blocked、Dead。 1)新建状态(New) 当线程对象对创建...

    陈树义
  • java中volatile关键字的含义

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉。 Java语言是支...

    陈树义
  • Java面试题:Servlet是线程安全的吗?

    Servlet不是线程安全的。 要解释为什么Servlet为什么不是线程安全的,需要了解Servlet容器(即Tomcat)使如何响应HTTP请求的。 当Tom...

    陈树义
  • java多线程并发控制countDownLatch和cyclicBarrier的使用

    java主线程等待所有子线程执行完毕在执行,这个需求其实我们在工作中经常会用到,比如用户下单一个产品,后台会做一系列的处理,为了提高效率,每个处理都可以用一个线...

    大道七哥
  • SpringBoot 2.X中的@Async和Java8中的completableFuture的使用比较

    看到項目中有使用到Async注解和completetableFuture的runApply方法的使用。兩者都是異步提交方法的方式。那他两都分别在什么场景底下比较...

    居士
  • 【JMeter-3】JMeter参数化4种实现方式

    什么是参数化?从字面上去理解的话,就是事先准备好数据(广义上来说,可以是具体的数据值,也可以是数据生成规则),而非在脚本中写死,脚本执行时从准备好的数据中取值。

    云深i不知处
  • 线程池实现原理-1

    这个看起来好像没有用到线程池,其实是因为没有可复用的线程,所以就一直创建新的线程了

    Java识堂
  • 多线程实现方式 转

    进程是程序在处理机中的一次运行。一个进程既包括其所要执行的指令,也包括了执行指令所需的系统资源,不同进程所占用的系统资源相对独立。所以进程是重量级的任务,它们之...

    南郭先生
  • 【小家Java】一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

    自从最近的某年某月某天起,线上服务开始变得不那么稳定(软病)。在高峰期,时常有几台机器的内存持续飙升,并且无法回收,导致服务不可用。

    YourBatman
  • windows 常用thread方法

    1.HANDLE CreateThread( _In_opt_LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_S...

    战神伽罗

扫码关注云+社区

领取腾讯云代金券