您的位置:首页 > 编程语言 > Java开发

不惑JAVA之JAVA基础 - 多线程

2016-05-05 14:35 435 查看

Thread 和 Runnable

这个没什么好说的,网上一大堆文章。主要记着:

使用继承Thread类方式实现;

使用实现Runnable接口方式实现。

开发中有时会使用下面这种用法:

new Thread(new Runnable() {
@Override
public void run() {
......
};
}).start();


线程的状态

在Java 5.0及以上版本中,线程的状态被扩充为新建、可运行、阻塞、等待、定时等待、死亡六种。线程的状态完全包含了一个线程从新建到运行,最后到结束的整个生命周期。线程状态的具体信息如下:

NEW(新建状态、初始化状态):线程对象已经被创建,但是还没有被启动时的状态。这段时间就是在我们调用new命令之后,调用start()方法之前。

RUNNABLE(可运行状态、就绪状态):在我们调用了线程的start()方法之后线程所处的状态。处于RUNNABLE状态的线程在JAVA虚拟机(JVM)上是运行着的,但是它可能还正在等待操作系统分配给它相应的运行资源以得以运行。

BLOCKED(阻塞状态、被中断运行):线程正在等待其它的线程释放同步锁,以进入一个同步块或者同步方法继续运行;或者它已经进入了某个同步块或同步方法,在运行的过程中它调用了某个对象继承自java.lang.Object的wait()方法,正在等待重新返回这个同步块或同步方法。

WAITING(等待状态):当前线程调用了java.lang.Object.wait()、java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三个中的任意一个方法,正在等待另外一个线程执行某个操作。比如一个线程调用了某个对象的wait()方法,正在等待其它线程调用这个对象的notify()或者notifyAll()(这两个方法同样是继承自Object类)方法来唤醒它;或者一个线程调用了另一个线程的join()(这个方法属于Thread类)方法,正在等待这个方法运行结束。

TIMED_WAITING(定时等待状态):当前线程调用了java.lang.Object.wait(long

timeout)、java.lang.Thread.join(long

millis)、java.util.concurrent.locks.LockSupport.packNanos(long

nanos)、java.util.concurrent.locks.LockSupport.packUntil(long

deadline)四个方法中的任意一个,进入等待状态,但是与WAITING状态不同的是,它有一个最大等待时间,即使等待的条件仍然没有满足,只要到了这个时间它就会自动醒来。

TERMINATED(死亡状态、终止状态):线程完成执行后的状态。线程执行完run()方法中的全部代码,从该方法中退出,进入TERMINATED状态。还有一种情况是run()在运行过程中抛出了一个异常,而这个异常没有被程序捕获,导致这个线程异常终止进入TERMINATED状态。

这些状态可以通过jps、stack获得当前线程状态。

举个例子

"Thread-1" prio=10 tid=0x08223860 nid=0xa waiting on condition [0xef47a000..0xef47ac38]
at java.lang.Thread.sleep(Native Method)
at testthread.MySleepingThread.method2(MySleepingThread.java:53)
- locked <0xef63d600> (a testthread.MySleepingThread)
at testthread.MySleepingThread.run(MySleepingThread.java:35)
at java.lang.Thread.run(Thread.java:595) </span>


上面信息显示:

* 线程的状态: waiting on condition

* 线程的调用栈

* 线程的当前锁住的资源: <0xef63d600>

wait(), notify(),sleep()详解

sleep()特性

当前线程进入停滞状态(阻塞当前线程),让出CUP的使用,目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;

sleep()是Thread类的Static(静态)的方法;

因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁),也是说不会释放对象锁

在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

wait()特性

wait()方法是Object类里的方法;

当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long

timeout)超时时间到后还需要返还对象锁);

其他线程可以访问;

wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。

wait()必须放在synchronized block中,否则会在program

runtime时扔出”java.lang.IllegalMonitorStateException“异常。

主要区别

这两个方法来自不同的类分别是Thread和Object;

最主要是sleep方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法;

wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)

synchronized(x){

       x.notify()

       //或者wait()   

}
  

sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

Join 详解

JDK是这样解释的

Join

public final void join() throws InterruptedException Waits for this thread to die. Throws: InterruptedException - if any thread has interrupted the current thread. The interrupted status of the current thread is cleared when this exception is thrown.

即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。

一般的使用方法:

Thread t = new AThread(); t.start(); t.join();


应用的场景

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。

源码解析

public final void join() throws InterruptedException {
join(0L);
}


进入join(0L)

public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}


JDK7源码如下,当前线程存活是isAlive()是true,所以一直接入while循环。

isAlive()方法的签名是:public final native boolean isAlive(),也就是说isAlive()是判断当前线程的状态。

关于死锁

死锁可能是多线程程序最常见的问题。当一个线程需要一个资源而另一个线程持有该资源的锁时,就会发生死锁。这种情况通常很难检测。但是,解决方案却相当好:在所有的线程中按相同的次序获取所有资源锁。例如,如果有四个资源 ―A、B、C 和 D ― 并且一个线程可能要获取四个资源中任何一个资源的锁,则请确保在获取对 B 的锁之前首先获取对 A 的锁,依此类推。如果“线程 1”希望获取对 B 和 C 的锁,而“线程 2”获取了 A、C 和 D 的锁,则这一技术可能导致阻塞,但它永远不会在这四个锁上造成死锁。

看一个经典的例子:

public class TestDeadLock implements Runnable{
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();
public static void main(String[] argv){
TestDeadLock td1 = new TestDeadLock();
TestDeadLock td2 = new TestDeadLock();
td1.flag = 1;
td2.flag = 0;
Thread t1 = new Thread(td1);
Thread t2 = new Thread(td2);
t1.start();
t2.start();
}
public void run(){
System.out.println("flag = "+ flag);
if(flag == 1){
synchronized (o1){
try{
Thread.sleep(500);
}catch(Exception e){
e.printStackTrace();
}
synchronized(o2){
System.out.println("1");
}
}
}
if(flag == 0){
synchronized(o2){
try{
Thread.sleep(500);
}catch(Exception e){
e.printStackTrace();
}
synchronized(o1){
System.out.println("0");
}
}
}
}
}


flag == 1 是获得o1锁,等待获得o2锁,但o2已经被flag==0代码获得;

flag == 0 是获得o2锁,等待获得o1锁,但o1已经被flag==1代码获得,都无法释放锁,所以就死锁了。

有兴趣的话可以看看下面这个blog:经典死锁案例-哲学家就餐

线程池

下个blog会分享

参考

1. Java Thread.join()详解

2. 详解sleep和wait的区别

3. sleep和wait有什么区别

4. IBM:Java 程序中的多线程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: