实现方式
简单介绍一下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
来源:oschina
链接:https://my.oschina.net/u/942558/blog/384655