目录
● yield()和join()方法简介
● 线程间的共享
● 线程间的协作
yield()和join()方法简介
yield()方法:使当前线程让出CPU占有权(线程让步),但让出的时间是不可设定的。也不会释放锁资源,所有执行yield()的线程有可能在进入到可执行状态后马上又被执行。
join()方法:把指定的线程加入到当前线程(线程插队),可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的join()方法,直到线程A执行完毕后,才会继续执行线程B 。
线程间的共享
java支持多个线程同时访问同一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
对象锁和类锁:对象锁是用于对象实例方法,或者一个对象实例上的。类锁是用于类的静态方法或者一个类的class对象上的额。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以,不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
注意:类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class对象。类锁与对象锁之间也是互不干扰的。
对象锁的使用:
/**
* @ author xiaozhou
* @ Date 2019/4/8
*/
public class SynTest {
private long count = 0;
private Object obj = new Object(); //作为一个锁
/**
* 下面的三种写法效果是一样的
*/
public void incCount1() {
synchronized (obj) {
//业务逻辑
}
}
/**
* 锁对象
* 锁的是当前类的实例this
*/
public synchronized void incCount2() {
//业务逻辑
}
public void incCount3() {
synchronized (this) {
//业务逻辑
}
}
}
类锁的使用:
/**
* 类锁,实际是锁类的class对象
*/
private static synchronized void SynClass() {
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end...");
}
private static Object obj = new Object();
//因为obj是静态的,在全虚拟机只有一份,所以这里类似于类锁
private void synStaticObject() {
synchronized (obj) { // 类似于类锁
SleepTools.second(1);
System.out.println("synClass going...");
SleepTools.second(1);
System.out.println("synClass end...");
}
}
代码中已经加了较为详细的注释,所以对此不再做解释。
线程间的协作
线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行的又是另外一个线程。前者是生产者,后者就是消费者,这种模式隔离了 “做什么”(what)和 “怎么做”(How),简单的办法是让消费者线程不断地循环检查变量是否符合预期在while循环中设置不满足的条件,如果条件满足则退出while循环,从而完成消费者的工作。(关于对生产者消费者的理解我本打算自己写一篇文章但是在发现一篇文章,让你彻底弄懂生产者--消费者问题之后,我自认为我没有人家理解的透彻,即便写文章也没有人家写的好,所以在此向大家推荐一下,自己也学习了。)但是生产者--消费者模型我认为存在以下两个问题:
(1)难以确保及时性
(2)难以降低开销。如果降低睡眠的时间,比如休眠1毫秒,这样消费者能更加迅速地发现条件变化,但是却可能消耗更多的处理器资源,造成了无端的浪费。
等待/通知机制(wait/notify/notifyAll)
等待/通知是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify()/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
1. wait()
调用该方法的线程进入waiting状态,只有等待另外线程的通知或被中断才会返回,在调用此方法之前,线程必须要获得该对象的对象监视器锁,所以只能在同步方法或同步代码块中调用wait()方法。调用wait()方法之后当前线程会释放对象的锁。
wait(long timeout)
超时等待一段时间,参数是毫秒,也就是等待传入参数的毫秒后,如果没有等到通知或被中断就会超时返回。
wait(long timeout, int nanos)
对于超时时间更为精确的控制,第二个参数是纳秒级的。
2. notify()
任意从等待在该对象上的线程中挑选一个进行通知,使得调用wait()方法的线程从等待状态切换到就绪状态,等待有机会再次获取到锁,从而使得调用wait()方法的线程能够从wait()方法处退出。调用notify()后,当前线程不会马上释放对象锁,而是要等到程序执行完同步块后,当前线程才会释放锁。没有获取到锁的线程重新进入waiting状态。
3. notifyAll()
使所有等待在该对象上的线程从等待状态退出,进入可运行状态。
下面我们通过一个案例来演示wait/notify的使用:
案例:两个线程一个线程打印1-52的值,另外一个线程打印A-Z的值。
打印格式为:12A 34B 56C 78D........
首先我们来分析一下,根据案例要求,两个线程一个打印数字,一个打印A-Z的字母。好,现在我们一步步来实现:
1. 首先打印数字的线程实现:
public class Number implements Runnable {
private Object object;
public Number(Object obj) {
this.object = obj;
}
@Override
public void run() {
synchronized (object) {
for (int i = 1; i < 53; i++) {
//代码块1
if (i >= 1 && i % 2 == 1) {
System.out.print(" "+i);
}
//代码块2
if (i % 2 == 0) {
System.out.print(i);
object.notifyAll();
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
我觉得这段代码中需要解释的就是注释中的两个代码块。以下的解释我们都以打印的12A为例。
代码块1就是做了输出1的逻辑判断,包括后面打印的3、5等都是可以通过这个条件给限定的。
代码块2就是做了输出2的逻辑判断,这里是能被2整除的数字的输出。输出之后,我们需要通知另外一个线程去做打印字母A的操作,所以这个线程就要调用wait()方法等待另一个线程做完输出字母A的操作之后发出通知再继续按照要求打印数字。
2. 现在我们来看一下另外一个打印字母的线程的实现。
public class CharacterThread implements Runnable {
Object object;
public CharacterThread(Object obj) {
this.object = obj;
}
@Override
public void run() {
synchronized (object) {
for (char i = 'A'; i <= 'Z'; i++) {
System.out.print(i);
object.notifyAll();
try {
if (i < 'Z'){
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
for循环中首先打印每一个字母,在打印完成后就需要通知打印数字的线程使其下一个数字。所以在打印完一个字母之后当前线程就暂时不能继续打印字母了,需要等到打印数字的线程发送通知过来才能继续打印。当然这些逻辑都是建立在i<'Z'基础之上的。
3. 然后在Main()方法中调用一下:
public class PrintInfoDemo3 {
public static void main(String[] args) {
Object obj = new Object();
new Thread(new NumberThread(obj)).start();
new Thread(new CharacterThread(obj)).start();
}
}
运行结果:
证明wait()方法释放锁与notify()不释放锁
1. wait()方法释放锁:
public class WaitThread implements Runnable {
private Object obj;
public WaitThread(Object object) {
this.obj = object;
}
@Override
public void run() {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + "Begin wait()");
obj.wait();
System.out.println(Thread.currentThread().getName() + "End wait()");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
main()方法中调用
public static void main(String[] args) {
Object object = new Object();
WaitThread waitThread1 = new WaitThread(object);
WaitThread waitThread2 = new WaitThread(object);
new Thread(waitThread1).start();
new Thread(waitThread2).start();
}
运行结果是:
如果wait()方法不释放锁,那么Thread-0就不会进入同步代码块中执行打印语句,因此,证明wait()方法是会释放锁的。
2. notify()方法不释放锁的证明
新建一个线程重写run()方法中的逻辑
public class NotifyThread1 implements Runnable {
private Object obj;
public NotifyThread1(Object object) {
this.obj = object;
}
@Override
public void run() {
synchronized (obj) {
try {
System.out.println("Begin wait(),ThreadName = " + Thread.currentThread().getName());
obj.wait();
System.out.println("End wait(), ThreadName = " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
再新建一个线程重写run()方法逻辑
public class NotifyThread2 implements Runnable {
private Object obj;
public NotifyThread2(Object object) {
this.obj = object;
}
@Override
public void run() {
try {
synchronized (obj) {
System.out.println("Begin notify(), ThreadName = " + Thread.currentThread().getName());
obj.notify();
Thread.sleep(5000);
System.out.println("End notify(), ThreadName = " + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
然后在main()方法中调用
public static void main(String[] args) {
Object object = new Object();
NotifyThread1 notifyThread1 = new NotifyThread1(object);
NotifyThread2 notifyThread2 = new NotifyThread2(object);
new Thread(notifyThread1).start();
new Thread(notifyThread2).start();
}
我们看一下运行结果:
根据运行结果可以分析:如果notify()方法会释放锁,那么在Thread-1调用notify()后,Thread.sleep(5000)期间一定会有其他线程介入同步代码块,看运行结果分明是等Thread-1休眠完成之后才继续执行的,所以证明notify()方法不能释放锁。
这篇文章到这里也就算是完成了,主要介绍了一下线程间的共享和协作。下一篇文章我将继续学习多线程相关的知识。
相关文章阅读