原创

Java多线程基础(二)


前言

上一篇文章《Java多线程基础(一)》整理了线程的基本概念和常用实现,本篇继续整理一下线程常用API。

线程的命名与获取

多线程的运行状态是不确定的,线程的命名可以让我们在开发中通过获取线程名字进行相关操作。Thread类中命名方式有以下两种:

  • 通过Thread类的构造函数,示例:
Runnable mr = new Runnable() {
            @Override
            public void run() {}
        };
//将自定义线程名"myRunnable"作为构造参数传入
Thread myRunnable = new Thread(mr,"myRunnable");
  • 通过Thread类的setName()方法,示例:
Thread t1 = new Thread(mr);
//调用线程的setName方法
t1.setName("myThread-1");

获取线程的名字可以使用线程的getName()方法,我们可以搭配currentThread()方法,获取当前执行线程的名字,比如:

Thread.currentThread().getName();


join

join()方法,作用是把指定线程加入到当前线程,将原本交替执行的两个线程合并为顺序执行,当前线程会在指定线程执行结束后再执行。如在A线程的线程执行体中调用B线程join()方法,直到B线程执行完毕,A线程才能继续执行。

join()方法有三种重载方式:

  • join():等待加入的线程执行完成。
  • join(long millis):等待加入的线程指定毫秒时间,若在此时间内,加入的线程还未执行结束,则不会继续等待。
  • join(long millis , int nanos):等待加入的线程指定毫秒加微秒时间,若在此时间内,加入的线程还未执行结束,则不会继续等待。

示例:

public class ThreadDemo {
    public static void main(String[] args) {
        for (int i=0;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+"---"+i);
            if(i==3){
                //通过匿名内部类实现Runnable接口
                Runnable myRunnable = new Runnable() {
                    @Override
                    public void run() {
                        for (int i=0;i<=3;i++ ){
                            System.out.println(Thread.currentThread().getName()+"--"+i);
                        }
                    }
                };
                //使用myRunnable创建线程并调用start
                Thread mr = new Thread(myRunnable);
                Thread mr2 = new Thread(myRunnable,"mr2");
                mr.start();
                mr2.start();
                try {
                    //在main线程执行体中调用mr线程的join()方法,main停止执行,需要等待mr线程执行完后才能继续执行
                    mr.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
输出结果:
main--0
main--1
main--2
main--3 //i=3时,调用mr线程的join方法,main线程停止
mr--0
mr2--0 //mian线程停止,但是mr2线程还是正常交替执行
mr--1
mr2--1
mr--2
mr2--2
mr--3
mr2--3
main--4 //mr线程执行完后,main线程恢复
main--5


sleep

sleep()方法,作用是让当前正在执行的线程暂停指定的时间,并进入阻塞状态。在其睡眠的时间段内,由于并未处于就绪状态,即使此时系统中没有任何其他可执行的线程,其也不会得到执行的机会;睡眠时间结束后则进入就绪状态,等待CPU调度执行。

sleep()方法有两种重载方式:

  • static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态
  • static void sleep(long millis , int nanos):让当前正在执行的线程暂停millis毫秒加nanos微秒,并进入阻塞状态

sleep()方法常用来暂停线程执行:

public static void main(String[] args) {
        //内部匿名类实现Runnable接口
        Runnable mr = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<=5;i++){
                    if(i==3){
                        try {//循环到i=3时,阻塞1秒
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName()+"--"+i);
                }
            }
        };
        //创建线程进入就绪状态
        Thread myRunnable = new Thread(mr,"myRunnable");
        myRunnable.start();
        for (int i=0;i<=10;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }
输出结果:
main--0
myRunnable--0
main--1
myRunnable--1
main--2
myRunnable--2 //可以看到main线程i=3之前正常交叉执行,i=3时进入阻塞状态
main--3
main--4
main--5
main--6
main--7
main--8
main--9
main--10
myRunnable--3  //睡眠时间结束,main就绪,CPU调度继续执行   
myRunnable--4
myRunnable--5


守护线程(Daemon Thread)

Java中线程可分为两类:User Thread(用户线程)、Daemon Thread(守护线程)。其中守护线程的存在主要是为用户线程提供服务,如JVM中的垃圾回收线程(GC),两者在构造和使用上几乎没有区别,不同处在于生命周期,当所有用户线程都进入死亡状态后,守护进程就会随着JVM一起停止运行(毕竟服务的对象都没有了,守护线程也没必要存在了)。

设置守护线程示例:

Thread myDaemon = new Thread();
// 设置 myDaemon 为 守护线程
myDaemon.setDaemon(true);
// 验证当前线程是否为守护线程,返回 true 则为守护线程
myDaemon.isDaemon();

需要注意的是setDaemon()必须在线程的start()方法之前执行,否则会抛出IllegalThreadStateException异常,你不能把正在运行的常规线程设置为守护线程;另外,在守护线程中产生的新线程也会是守护线程。

守护线程死亡示例:

class UserThread implements Runnable{//用户线程
    @Override
    public void run() {
        for (int i=0;i<=5;i++){
            System.out.println("用户线程开始执行---"+i);
        }
    }
}

class DaemonThread implements Runnable{//守护线程
    @Override
    public void run() {
        for (int i=0;i<=5;i++){
            System.out.println("守护线程开始执行---"+i);
            try {
                //这里让守护线程暂停1毫秒,让用户线程可以先执行完成
                Thread.sleep(1); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    }
}

public class DaemonDemo {
    public static void main(String[] args) {
       Runnable userThread = new UserThread();
       Runnable daemonThread = new DaemonThread();
       Thread t1 = new Thread(userThread);
       Thread t2 = new Thread(daemonThread);
       t1.start();
       //将t2设置为守护线程
       t2.setDaemon(true);
       t2.start();
    }

}
输出结果:
用户线程开始执行---0
守护线程开始执行---0  //守护线程执行了一次
用户线程开始执行---1
用户线程开始执行---2
用户线程开始执行---3
用户线程开始执行---4
用户线程开始执行---5  //用户线程执行完成 

Process finished with exit code 0

从上面输出结果我们可以看出,当所有用户线程都执行完成后,JVM会直接停止运行,不会判断是否还有守护线程未执行完成。


线程优先级

每个线程在执行时都具有一定的优先级,优先级高的线程具有较多的执行机会。线程默认的优先级继承自创建它的线程,main线程默认具有普通优先级(5)。

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

  • MAX_PRIORITY:10

  • MIN_PRIORITY:1

  • NORM_PRIORITY:5

获取线程优先级:getPriority()

使用示例:

public class PriorityDemo {
    public static void main(String[] args) {
        Runnable myRunnable = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<=6;i++){
                    System.out.println(Thread.currentThread().getName()+"执行--"+i);
                }
            }
        };

        Thread t1 = new Thread(myRunnable,"线程1");
        Thread t2 = new Thread(myRunnable,"线程2");
        //这里给线程1设置优先级10,线程2继承main线程的默认优先级5
        t1.setPriority(Thread.MAX_PRIORITY);
        //输出2个线程的优先级
        System.out.println("线程1优先级:"+t1.getPriority());
        System.out.println("线程2优先级:"+t2.getPriority());
        t1.start();
        t2.start();
    }
}
输出结果:
线程1优先级:10
线程2优先级:5
线程1执行--0
线程2执行--0
线程1执行--1
线程2执行--1
线程1执行--2
线程1执行--3
线程1执行--4
线程1执行--5
线程1执行--6
线程2执行--2
线程2执行--3
线程2执行--4
线程2执行--5
线程2执行--6

通过以上结果可以验证,具有较高优先级的线程对象仅表示此线程具有较多的执行机会,而非优先执行。


yeild

yield()方法,使当前执行的线程让出CPU资源,进入就绪状态,让其他拥有相同优先级的线程获取CPU执行的机会;因为调用方法的线程是就绪状态,所以并不能控制让出CPU的时间,可能下一刻被CPU调用的线程还会是它。示例:

class Thread1 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<=5;i++){
               //调用yield(),尝试让出cpu
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }
}
class Thread2 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+"--"+i);
        }
    }
}

public class YieldDemo {
    public static void main(String[] args) {
        Runnable mr1 = new Thread1();
        Runnable mr2 = new Thread2();
        Thread t1 = new Thread(mr1);
        Thread t2 = new Thread(mr2);
        Thread t3 = new Thread(mr2);

        t1.start();
        t2.start();
        t3.start();
    }
}
输出结果:   //多次运行,结果都还是交替执行
Thread-0--0
Thread-1--0
Thread-1--1
Thread-1--2
Thread-1--3
Thread-1--4
Thread-2--0
Thread-1--5
Thread-0--1
Thread-2--1
Thread-0--2
Thread-2--2
Thread-0--3
Thread-2--3
Thread-0--4
Thread-2--4
Thread-0--5
Thread-2--5

通过以上结果可以验证,yield()方法有时并不能达到让步的效果,因为让步的线程还有可能被线程调度程序再次选中。

正文到此结束