JAVA多线程:yield/join/wait/notify/notifyAll等方法的作用(二)

前言

讨论这个问题前,需要先了解以下知识点!

1、等待队列(blocked)中的线程不参与cpu竞争,就绪队列(runable)中的线程才会参与cpu竞争。
2、CPU竞争策略有多种,Unix使用的是时间片算法,Windows属于抢占式。

a)时间片:
所有的进程排成一个队列。操作系统按照他们的顺序,给每个进程分配一段时间,即允许该进程运行的时间。

b)抢占式操作系统
就是说如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU 。因此可以看出,在抢 占式操作系统中,操作系统假设所有的进程都是“人品很好”的,会主动退出 CPU 。 

一、start()用来启动一个线程

调用该方法后,相应线程就会进入就绪状态,也就是runable状态,等待某个时机被CPU调用。
声明:wait() 、notify()、notifyAll()是Object类的方法,不是Thread类的方法。

二、yield()让线程让出CPU的执行(不释放锁)

该方法与sleep()类似,不能由用户指定暂停多长时间,它的作用是放弃当前CPU资源,让给其他线程去使用,但是放弃时间不确定。

它并不会让线程进入阻塞状态,而是让线程回到就绪状态,随时等待重新得到CPU的执行。

实际中无法保证yield()达到让步目的,因为让步的线程还有可能会再次被线程调度程序选中。

注意:

1.yield()并不会释放锁,仅仅是暂时交出了占用CPU执行权限。

2.yield()的线程有可能在进入到可执行状态后马上又被执行。

3.yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。

通常配合Thread.activeCount()使用:

while(Thread.activeCount()>1) {
	Thread.yield();//保障前面的线程都执行完
}

注:这里的1指的是主线程,如果你的main方法中new了一个线程类4个实例对象,分别启动这4各类的实例,那么就至少会有4个主线程(这里就需要用4去判断,所有子线程是否执行完毕) 

使用场景示例

public class VolatileTest {
	public volatile int num = 0;

	public synchronized void increase() {
		num++;
	}

	public static void main(String[] args) {
		final VolatileTest test = new VolatileTest();
		for (int i = 0; i < 10; i++) {
			//开启10个子线程,对num同时操作
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++) {
						test.increase();
					}
				}
			}.start();
		}

		while(Thread.activeCount()>1) {
			Thread.yield();//保障前面的线程都执行完
		}
		System.out.println(test.num);
		System.out.println(Thread.activeCount());
	}
}

三、join()会释放Thread的锁不会释放对象锁

简单的说:如果在main方法中,调用其他线程的join方法,则main方法的主线程A就需要给其他线程B让步,让B先执行完,主线程A才能继续执行,相当于阻塞主线程,直到被唤醒。

注:阻塞状态,它是让“外层的主线程”从 RUNNABLE 变为 WAITING 状态(因为join的底层就是wait())。

join(long millis)方法:执行当前A线程交出CPU执行权限millis后,再和其它线程一起竞争CPU资源。

如果在A主线程中,一次join了多个其他线程B/C/D,则被join的多个子线程执行的先后顺序不影响,都是并行执行。

注意:

1、如果有两个线程同时调用另外一个线程的join方法,会有一个线程成功得到锁,而另外一个则必须等待,进入阻塞状态,而在得到锁之后,才会执行join方法。

2、join底层使用wait,wait会释放锁,但是wait释放的是Thread的锁,所以Join在设计的时候也只是会释放Thread的锁,不会释放线程对象的锁,点击:进入代码示例。

如果看完,依然有疑问:那就单独列出来讨论一下。

join底层调用的是wait(),而wait是Object的方法,wait本身是会释放锁(彻底交出CPU的执行权),所以 Thread 的join() 方法是否会释放锁?答案是会!

点击进入:狂抓!join()方法到底会不会释放锁,给你彻底介绍清楚(带示例)

四、sleep()进入阻塞状态,让其他线程得到执行机会(不释放锁)

该方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,让其他线程得到执行机会。但是sleep()方法不会释放“锁标志”,其他线程仍然不能访问被锁定的共享数据。

点击进入:sleep(0)、sleep(1)、sleep(1000)的区别

举例:

1、拿 "点火->烧水->煮面"来说事儿,而当A点完火之后,并不想不立即烧水,要休息一段时间再烧,煮面的主动权是由A来控制(不释放锁)。

2、再拿看三个人看电视来说,A只是想休息一下,并不想交出遥控(锁),此时调用sleep(10),指定个休息时间;如果来了个管理员,需要让A交出遥控器,需要调用wait(10)方法,调用wait的同时,A立刻交出遥控,在wait的10毫秒期间,不参与遥控的竞争,10毫秒后几个人重新争夺遥控(锁)。

1.它需要捕获InterruptedException异常

如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出,而wait,notify和notifyAll不需要捕获异常。

2.不会释放对象锁,其他线程无法访问这个对象

sleep方法不会释放该线程所拥有的资源(例如:锁),也就是说如果当前线程持有对某个对象的锁,其他线程也无法访问这个共享对象。

五、Object类的三个方法wait(),notify()和notifyAll()

wait 与 notify/notifyAll 方法必须在synchronized 同步代码块中使用,即要先对调用对象加锁,不放在synchronized 中则会在程序运行时抛出“java.lang.IllegalMonitorStateException”异常。

ReenTrantLock.newCondition()获取一个Condition类对象,然后Condition的await(),signal()以及signalAll()分别对应上面的三个方法。ReenTrantLock是Lock接口的实现类。

1.wait()指定时间内不参与锁的竞争(释放锁)

到时间会自动唤醒;否则就需要 notify / notifyAll 去唤醒。

它和sleep的重要区别是,sleep是Thread类的方法,wait是Object的方法。wait调用时,需写在synchronized代码块内。

拿看电视来举例,三个人看电视,锁就是遥控器。

A正在拿遥控器,一旦调用wait时,意味着A立刻交出遥控器(释放锁),至于交给B还是C,有JVM来控制。

那么,拿到遥控器的,可以每隔10秒,换一个电视台,这间隔的10秒就需要sleep实现,因为遥控仍在他手上,并没有交出遥控(sleep不释放锁)。

2.notify()和notifyAll()叫醒一个或所有线程参与锁的竞争

notify() 只会唤醒一个线程,notifyAll() 会唤醒所有的线程。

notifyAll() 调用后,会将全部线程由等待池等待队列)移到锁池就绪队列),然后参与锁的竞争,竞争成功则意味着获取到了CPU的执行权(不一定会立刻被CPU执行),如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

总结

这些方法相互之间总是容易混淆,重点记忆两点:

1、他们的父类不同:

wait(),notify()和notifyAll()三个方法属于Object类,只有用在synchronized代码同步块中才有意义。其他几个方法都属于Thread线程。

2、关于锁的获取是释放

wait会释放锁,yield、join()、park()、都不释放锁,需要注意的是join不释放对象锁,释放thread的锁(更多,点击进入) 

尾言

学好多线程,这些都是绕不过去的点,只能深挖后一个一个突破,加油!

猜你还可能会对以下内容感兴趣:

1、 JAVA多线程:synchronized理论和用法 | Lock和ReentrantLock Volatile 区别和联系(一)

2、JAVA多线程:sleep(0)、sleep(1)、sleep(1000)的区别(四)

3、JAVA多线程:join()方法到底会不会释放锁,给你彻底介绍清楚(三)

你可能感兴趣的:(Java编程开发,#,java多线程,java,开发语言,后端,java多线程)