Java线程图文总结

二次信任 提交于 2020-04-15 09:52:56

【推荐阅读】微服务还能火多久?>>>

实现方式

简单介绍一下Java多线程实现方式,有以下三种:

1、继承Thread类

2、实现Runnable接口

3、使用ExecutorService、Callable、Future实现有返回结果的多线程

区别是前两种执行完之后不带返回值,最后一种带返回值,其中最常用为前两种。


线程的状态

java线程的整个生命周期有5个状态:新建,就绪,运行中,阻塞,结束。

5个状态之间的关系将结合下图理解:

上图为java线程生命周期期间的各种命运,下面介绍常见的几种命运。

命运一

新线程创建成功,调用start()进入就绪状态,即进入待运行的线程池中等待,等待获取CPU的使用权。当获得CPU使用权,该线程从就绪状态进入运行状态。运行过程中,运气好的,一次运行就把所要执行的任务执行完毕,线程结束;命运不好的,运行中途被CPU暂停运行,重新回到就绪状态,等待分配,然后再等待进入运行期,直到最后运行完毕,最后结束。


命运二

新线程创建成功,进入就绪状态,获取了CPU使用权,处于运行状态。这里意外出现,该线程执行了sleep、yield、join三者其中一个命令。sleep、join需要被暂停执行一段时间,线程进入阻塞状态。休息时间到,再重新进入就绪状态;而yield是从运行状态直接跳会就绪状态。当到了就绪状态后再重新等待CPU调度,重新进入运行期。run -> block -> run -> block.... 此种状态会持续,一直到该线程的任务执行完毕。sleep、yield、join三者区别如下:

sleep:CPU暂停当前线程运行,同时让就绪状态(待运行池中)优先级较低的一个线程运行。当前线程被暂停后,会处于阻塞状态n秒(n由开发人员设定),时间到了之后,会自动回到就绪状态,等待CPU重新调度,重新从刚刚暂停的地方运行。注意,若代码块中包含了对象的锁,在睡眠的过程中是不会释放掉对象的锁的,其他线程是不能访问到共享数据的。

join:将指定的线程执行完成后,再运行当前线程剩下的任务。典型的例子是将两个交替执行的线程合并顺序执行。请结合下面代码理解:

public static void main(String[] args) throws IOException {
	final Thread aThread = new Thread(new Runnable() {
		@Override
		public void run() {
			for (int i = 0; i < 5; i++) {
				System.out.println("a:" + i);
			}
		}
	});
	
	Thread bThread = new Thread(new Runnable() {
		@Override
		public void run() {
			try {
				aThread.join();
			} catch (InterruptedException e) {
			}
			
			for (int i = 0; i < 5; i++) {
				System.out.println("b:" + i);
			}
		}
	});
	
	bThread.start();
	aThread.start();
	
	try {
		bThread.join();
	} catch (InterruptedException e) {
	}
	
	for (int i = 0; i < 5; i++) {
		System.out.println("c:" + i);
	}
}

输出结果:

a:0
a:1
a:2
a:3
a:4
b:0
b:1
b:2
b:3
b:4
c:0
c:1
c:2
c:3
c:4

如果把代码中的join部分去掉,就不能保证a、b、c的输出顺序。

yield:实质是当前正在运行的线程直接回到就绪状态,而不用进入阻塞状态等待,且只会选择优先级相同的线程进入运行状态。若待运行的线程池中,没有当前线程优先级相同的线程,或者当前线程又被选中运行,则当前线程还是会继续运行,会误造成yield无法达到目的的效果。


命运三

线程a成功进入创建、就绪、运行流程,运行过程中,需要访问资源i,而此时资源i正在被另外的线程b访问并且上锁了,此时线程a就会暂停运行,进入资源i的锁池,等待线程b释放资源i的锁。当线程a获得资源i的锁时,会从资源i的锁池中进入就绪状态(待运行池),等待调度。以下为示例代码:

public static void main(String[] args) throws IOException {
	final Object obj = 1;
	Thread aThread = new Thread(new Runnable() {
		@Override
		public void run() {
			synchronized(obj){
				for (int i = 0; i < 3; i++) {
					System.out.println("线程a在使用obj...");
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
					}
				}
			}
		}
	});

	Thread bThread = new Thread(new Runnable() {
		@Override
		public void run() {
			synchronized(obj){
				for (int i = 0; i < 3; i++) {
					System.out.println("线程b在使用obj...");
					try {
						Thread.sleep(500);
					} catch (InterruptedException e) {
					}
				}
			}
		}
	});
	bThread.start();
	aThread.start();
}

输出结果是:

线程b在使用obj...
线程b在使用obj...
线程b在使用obj...
线程a在使用obj...
线程a在使用obj...
线程a在使用obj...

两个线程都start()之后,两个线程随机先后访问到obj对象,有可能是a先访问obj,有可能是b先访问obj。我的结果就是b先访问obj,拿到obj的锁,之后线程a无法立即访问到obj,a就进入obj的锁池中等待;当b中被锁的代码块跑完且释放锁,a拿到obj的锁,重新进入就绪状态,等待分配运行。


命运四

线程a成功的创建、就绪、运行,运行过程中调用obj.wait(),a就从run状态进入到obj的等待池,等待其他线程调用obj.notify;当其他线程调用notify之后,线程a从obj的等待池中,进入到obj的锁池,等待获得锁以重新进入就绪状态恢复运行。下面具体解析一下wait、notify、notifyAll的作用:

wait:在已经获得obj的锁的前提下,主动释放obj的锁,释放后当前线程会进入obj的等待池,即开始休眠。另外,当调用了wait之后,并不是立即释放对象obj的锁,而是在相应的synchronized()语句块执行结束后才真正释放。

notify的作用是从对象obj的等待池中随机抽取一个线程放到对象obj的锁池中,让该线程继续在锁池中等待锁,成功拿到锁后,等待分配执行。

notifyAll的作用跟notify类似,只不过是把对象obj的等待池中的所有的线程全部取出,放到对象obj的锁池中。

需要明确的一点:wait、notify、notifyAll均为在当前线程获取对象的锁的前提下执行的,所以这三个操作都必须在synchronized()块中执行。

举个例子:线程a、线程b、线程c需要协同工作,线程b、c的任务需要在a线程的任务完成后才能开始。那就是说a在完成任务后,需要通知b和c恢复工作。这种情况就可通过wait,notifyAll来实现协同工作。请看以下代码:

public static void main(String[] args) {
	final Object object = new Object();
	Thread a = new Thread() {
		public void run() {
			System.out.println("a start");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
			}
			synchronized (object) {
				System.out.println("a finish, notify b and c");
				object.notifyAll(); //wait,notify,notifyAll必须在synchronized块里面使用
			}
		}
	};
	
	Thread b = new Thread() {
		public void run() {
			synchronized (object) {
				System.out.println("b is starting, waiting for a finish...");
				try {
					object.wait();
				} catch (InterruptedException e) {
				}
				System.out.println("b end");
			}
		}
	};
	
	Thread c = new Thread() {
		public void run() {
			synchronized (object) {
				System.out.println("c is starting, waiting for a finish...");
				try {
					object.wait();
				} catch (InterruptedException e) {
				}
				System.out.println("c end");
			}
		}
	};

	a.start();
	b.start();
	c.start();
}

其中一次的输出结果:

a start
b is starting, waiting for a finish...
c is starting, waiting for a finish...
a finish, notify b and c
c end
b end

因为线程在锁池中是被随机抽取的,所以不可能保证b,c哪个先运行。上面的结果就是c先被抽取。

上述全部讨论的前提是线程运行中没有遇到exception的情况,若遇上了exception,就直接end。


以上为本人对Java线程的理解,是基于线程的浅层部分展开讨论,欢迎指正。若想深入了解线程,可以看看java.util.concurrent包下的类。本人之前写过一个基于多线程实现的远程监控程序,有兴趣可参考一下:

http://my.oschina.net/ericquan8/blog/383882



易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!