一:java多线程的JVM运行模型
首先当一个单线程转变成一个多线程运作时,我们需要对JVM的内存模型有一些基本的了解。首先我们来看一下在多线程的情况下JVM如何运行线程方式,如图所示:
网上很多对这个运行模型讲解的不是很透彻,这里我们做一个简单说明,主要分为以下几步:
(1)每一个线程复制出一个内存副本(图中以共享变量为主),也就是缓存出一个应用层次的副本,每一个线程都是在本线程 的副本中进行操作,避免每一个线程都要到内存中操作,占用内存空间(这是多线程的优势,也是产生多线程的问题的高 发区)。
(2)每一个线程完成或者在一个时间点同步副本至主内存中。
二:多线程的线程协作
从上面的介绍可以看出每一个线程多事由自己独立的运存副本,在没有同步之前线程与线程之间是独立的。那么我们如何在多个线程运行时去调度不同线程的状态(注:这里的调度区别于在A线程中控制B线程的运行状态,这里涉及到java的NIO编程模式-Netty)。java提供了2种方式:一是Object的wait()等方法和基于线程的sleep()方法等、
首先在谈到这些方法之前提到一个很多面试会遇到的问题:为什么wait()是Object的方法,而不是线程的方法?希望带着这个问题去往下阅读。
(1)Object的wait方法
这个方法是指当前线程进入阻赛状态即线程挂起,先上一段代码看一下吧。
package test;
/**
* 多线程的Object的wait方法
* @author monxz
*
*/
public class WaitMethod {
public void waitTest(){
System.out.println("======wait开始==========");
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("======wait结束==========");
}
public static void main(String[] args) {
WaitMethod WaitMethod =new WaitMethod();
new Thread(new Runnable() {
@Override
public void run() {
WaitMethod.waitTest();
}
}).start();
}
}
然而,这段运行结果尽然抛出异常,如下图所示:百度了一下,该异常的主要就是说没有获取到对象的Monitor锁造成的,java对于这种异常就是在方法添加synchronized关键字来改善,这里代码就是不上传(在waitTest()上添加该关键字)
(2)Object的notify/notifyAll()方法
该方法主要是针对(1)中的线程进入阻赛状态下的线程花旗,前者唤醒单个线程,或者唤醒全部休眠线程。这个方法主要就是针也是基于Object对象的Monitor所实现的。具体代码就不用上传了。
(3)Thread的sleep()方法
sleep方法主要是让方法进入一个线程暂停,这里上一个实例代码:
package test;
/**
* 多线程的Thread的sleep方法
* @author monxz
*
*/
public class sleepMethod {
public void sleepTest(){
System.out.println("======wait开始==========");
System.out.println("======wait结束==========");
}
public static void main(String[] args) {
sleepMethod WaitMethod =new sleepMethod();
new Thread(new Runnable() {
@Override
public void run() {
WaitMethod.sleepTest();
}
}).run();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=======over===========");
}
}
其实,很明显可以看出来,sleep()是在一个线程开始或者结束进行调用方法的这里明显就区别于wait方法了(即调用wait方法必须依赖于Object对象及其方法,未定义一个对象是无法使用wait()方法的。而sleep方法只依赖于Thread,即使没有对象只要使用Thread去调用即可),下面该出一个说明代码:
package test;
/**
* 多线程的Thread的sleep方法
* @author monxz
*
*/
public class sleepMethod2 {
public void sleepTest(){
System.out.println("======wait开始==========");
System.out.println("======wait结束==========");
}
public static void main(String[] args) {
System.out.println("=======begin===========");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("=======over===========");
}
}
(3)Thread的yield()方法
这个方法是指让多个线程进行交替运行,先上一个代码吧:
package test;
/**
* 多线程的Thread的sleep方法
* @author monxz
*
*/
public class yieldMethod implements Runnable{
@Override
public void run() {
for(int i = 0 ;i<5 ; i++ ){
System.out.println(Thread.currentThread().getName() +"线程运行结果:"+ i );
Thread.yield();
}
}
public static void main(String[] args) {
yieldMethod yieldMethod =new yieldMethod();
Thread t1 = new Thread(yieldMethod, "线程一");
Thread t2 =new Thread(yieldMethod,"线程二");
t1.start();;
t2.start();;
}
}
这段代码理想运行结果是应该如下图所示,但是很尴尬的是还会出现以下的状态,后来百度了以下原因是,虽然该方法允许多个线程交替运行,但是这个交替过程不可控,也就是出现以下救过都是正确的,所以一般这个方法很少用,如果要用一定要控制好外部条件:
(4)Thread的Join方法
Join()方法就是讲多线程的方法转成同步方法,从一开始的JVM图中可以看出一般情况下,多线程的运行是互不影响的,那么如果需要将互不影响改为同步,也就是说A线程一定是在B线程结束才运行的,那么A线程运行结果同步到主内存,然后B线程复制副本再运行等等,那么就必须依赖于同步锁关键字,这里查看join的源码发现join方法实际上是用wait()来实现的。join方法有一个学名,父线程等待子线程运行结束在运行。
三:Object方法和Thread方法的区别于总结
(1)Object方法需要依赖于对象的Monitor锁实现,所以必须要先定义一个对象。而Thread方法可以直接使用线程Thread来调用方法,这以很好的回答了在本文中间提出的问题了。
(2)Object方法的wait方法是会释放对象的Monitor对象锁的,而Thread并不会。