线程基础以及线程之间的共享与协作<二>

线程基础以及线程之间的共享与协作<二>

前言

本文接着学习线程之间的共享与协作,线程基础以及线程之间的共享与协作<一> 主要介绍了线程的基础概念和线程的启动与终止,本文接着讲述线程的一些常用方法与 synchronized 加锁的方法。
关于线程的一些基本方法,下面这张图可以很清晰地表述各个方法得作用与联系。
线程基本流程

线程的基本方法

yield 方法

介绍一些方法的使用,我一般都会去看源码,因为源码是最真实可靠的,也是自己可以去考证的。好啦,上源码:

/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     * 给调度程序的一个提示,即当前线程愿意产生其当前使用的处理器。
     * 调度程序可以忽略此提示。
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     * Yield是一种启发式尝试,旨在改善线程之间的相对进程,否则会过度使用CPU。
     * 它的使用应该与详细的分析和基准测试相结合,以确保它确实具有预期的效果。
     * 
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     * 使用这种方法很少合适。它可能对调试或测试有用,因为它可能有助于重现由于竞争条件而产生的bug。
     * 在设计并发控制结构(如{java.util.concurrent.locks}包
     */
    public static native void yield();

yield 方法首先是一个 native 方法,是调用操作系统的方法;yield 会让当前线程让出执行权,然后CPU随机分配时间片给处于就绪态的线程,要注意的是,当前线程让出执行权之后也会转到就绪态,所以说当前线程也有几率被CPU选中。

线程执行 yield 方法让出CPU执行权,并不会让出当前线程所占用的其他资源,比如内存空间、磁盘I/O等。

有人可能会问执行 yield 方法会让当前线程释放锁吗?
答案是不会的。第一点,我们可以想一下,在一个进程中,并不是所有线程都需要加锁的;第二点,在执行 yield 方法的时候,线程并不一定会持有锁。综合以上两点,所有线程执行 yield 方法并不会释放锁。

yield 方法在实际开发中很少使用,主要是对调试和测试有用,在某些情况下它可能由于竞争条件而有助于重现错误。当设计并发控制结构时,例如link java.util.concurrent.locks包中的并发控制结构,它也可能很有用。

举个实际的例子,来简单演示一下 yield 方法的作用。
老王laowang在电影院排队买票,突然看到女神goddess,于是主动献殷勤,让女神插队,后来女神男盆友goddessBoyFriend来了,女神主动让男盆友插队。(有点儿心疼老王。。。)

简单分析:
老王主动让女神插队,相当于让出执行权给女神,老王执行 yield 方法;女神让男盆友插队,女神执行 yield 方法。

代码如下:

public class UseYield {

    private static class MarkThread extends Thread {

        private Thread thread;

        public MarkThread(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            System.out.println("laowang开始排队买票。。。");
            if (thread != null) {
                // 当前线程主动让出执行权,由运行态转到可运行状态
                // 在这里就是 laowang 主动让 goddess 插队
                yield();
            }
            // 休眠4秒
            SleepTools.second(4);
            System.out.println(Thread.currentThread().getName() + " laowang买票结束。。。");
        }
    }

    private static class GoddessThread extends Thread {

        private Thread thread;

        public GoddessThread(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            System.out.println("goddess开始排队买票。。。");
            if (thread != null) {
                // 当前线程主动让出执行权,由运行态转到可运行状态
                // 在这里就是 goddess 主动让 goddessBoyFriend 插队
                yield();
            }
            // 休眠两秒
            SleepTools.second(2);
            System.out.println(Thread.currentThread().getName() + " goddess买票结束。。。");
        }
    }

    private static class GoddessBoyFriendThread extends Thread {

        @Override
        public void run() {
            System.out.println("goddessBoyFriend开始排队买票。。。");
            // 休眠两秒
            SleepTools.second(2);
            System.out.println(Thread.currentThread().getName() + " goddessBoyFriend买票结束。。。");
        }
    }

    public static void main(String[] args) {
        GoddessBoyFriendThread goddessBoyFriend = new GoddessBoyFriendThread();
        GoddessThread goddess = new GoddessThread(goddessBoyFriend);
        MarkThread mark = new MarkThread(goddess);
        mark.start();
        goddess.start();
        goddessBoyFriend.start();
    }
}

执行结果:

laowang开始排队买票。。。
goddessBoyFriend开始排队买票。。。
goddess开始排队买票。。。
Thread-0 goddessBoyFriend买票结束。。。
Thread-1 goddess买票结束。。。
Thread-2 laowang买票结束。。。

join 方法

同样的,读源码,分析方法。源码如下:

/**
     * Waits for this thread to die.
     * 等待该线程死亡。
     * 
     * <p> An invocation of this method behaves in exactly the same
     * way as the invocation
     * 此方法的调用与调用的行为方式完全相同
     *
     * <blockquote>
     * {@linkplain #join(long) join}{@code (0)}
     * </blockquote>
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     * 当前线程被中断,中断标志位会变成false,并抛出异常
     */
    public final void join() throws InterruptedException {
        join(0);
    }

执行 join 方法可以把指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行。

也就是说,例如现在有 thread1 和 thread2,thread1 正在执行,thread2 调用 join() 方法,thread1就被挂起了,那么 thread1 就要让出执行权,thread2 获取执行权,而且 thread1 要等待 thread2 执行结束(线程死亡)之后,才能拿到执行权。

同样是老王去电影院排队买票的案例,用 join 方法的实现方法如下:

public class UseJoin {

    static class Goddess implements Runnable {
        private Thread thread;

        public Goddess(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            System.out.println("Goddess开始排队买票.....");
            try {
                if (thread != null) {
                    thread.join();
                }
            } catch (InterruptedException e) {
            }
            SleepTools.second(2);//休眠2秒
            System.out.println(Thread.currentThread().getName()
                    + " Goddess排队买票...");
        }
    }

    static class GoddessBoyfriend implements Runnable {
        @Override
        public void run() {
            SleepTools.second(2);//休眠2秒
            System.out.println("GoddessBoyfriend开始排队买票.....");
            System.out.println(Thread.currentThread().getName()
                    + " GoddessBoyfriend排队买票...");
        }
    }
    
    public static void main(String[] args) throws Exception {
        // 这里用主线程表示老王排队买票
        Thread laowang = Thread.currentThread();
        GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
        Thread gbf = new Thread(goddessBoyfriend);
        Goddess goddess = new Goddess(gbf);
        //Goddess goddess = new Goddess();
        Thread g = new Thread(goddess);
        g.start();
        gbf.start();
        System.out.println("laowang开始排队买票.....");
        g.join();
        //让主线程休眠2秒
        SleepTools.second(2);
        System.out.println(Thread.currentThread().getName() + " laowang买票完成.");
    }
}

执行完成:

laowang开始排队买票.....
Goddess开始排队买票.....
GoddessBoyfriend开始排队买票.....
Thread-0 GoddessBoyfriend排队买票...
Thread-1 Goddess排队买票...
main laowang买票完成.

线程的优先级

线程的setPriority()方法可以给线程设置优先级,也就是更新线程的优先级,因为线程的默认优先级为 5,线程的优先级是从1-10,优先级越高,CPU给线程分配的时间片可能就越多,优先执行的概率可能就越高。
但是能不能真正的执行,还要看操作系统,假设给线程设置了10的优先级,可能操作系统本身最高的优先级只有5,那也是不起作用的。如果你设置了超出这个范围(1-10)的优先级,就会报错,throw new IllegalArgumentException()。

基本规则:
侧重于休眠、I/O操作的线程优先级可以设置高一点,侧重于计算的线程优先级可以设置低一点

总结:
想要通过设置优先级来指定哪个线程先执行,哪个后执行,这样是不起作用的,能不能先执行还是要看操作系统的。

守护线程

线程的setDaemon()方法,可以让我们的线程变成守护线程。

什么是守护线程?
守护线程是一种支持性线程,因为主要被用作程序中后台调度以及一些支持性工作。包括,内存回收、gc等。

平常我们通过 new Thread() 创建的线程都被称为用户线程(非守护线程)。但是像JDK启动的线程,我们通过参数配置的线程都被称为守护线程。比如:useThread.start(); Thread.sleep(5);

当一个进程的非守护线程(用户线程)都执行结束了,这个进程也就跟着停止了,所有的守护线程也就跟着停止了。想要设置守护的线程的话,只需要 setDaemon(true) 就可以。
守护线程在开发中一般用不到,比如垃圾回收线程用的就是守护线程,还有在netty框架中,一般会创建堆外内存,这个时候就可以用守护线程来管理这些堆外内存。

实例:
当前代码直接执行的话,非守护线程有两个,一个main主线程,一个useThread线程。执行结果是main执行结束了之后,useThread线程一直在运行;代码如下:

public class DaemonThread {
	private static class UseThread extends Thread{
		@Override
		public void run() {
			try {
				while (!isInterrupted()) {
					System.out.println(Thread.currentThread().getName()
							+ " I am extends Thread.");
				}
				System.out.println(Thread.currentThread().getName()
						+ " interrupt flag is " + isInterrupted());
			} finally {
				//守护线程中finally不一定起作用
				System.out.println(" .............finally");
			}
		}
	}

	public static void main(String[] args)
			throws InterruptedException, ExecutionException {
		UseThread useThread = new UseThread();
		// useThread.setDaemon(true);
		useThread.start();
		Thread.sleep(5);
		// useThread.interrupt();
	}
}

执行结果:
在这里插入图片描述
可以看到 useThread 用户线程(非守护线程)一直在执行,不会结束。

但是我们把useThread线程设置成守护线程,再去执行,主线程执行结束之后,子线程也执行结束了。代码就是把上一份代码的注释打开,重新执行

useThread.setDaemon(true);

执行结果:
在这里插入图片描述
看输出结果,在 main 线程执行完成之后,useThread 守护线程也跟着结束了。

需要注意的是:
daemon守护线程被用作完成支持性工作,在JVM退出的时候,守护线程中的finally块并不一定会被执行。这是取决于CPU是否分配时间片给守护线程,分配了就执行,没分配就不执行。所以在构建守护线程的时候,不能依赖finally中的内容来确保执行关闭或者释放资源的逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值