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

java多线程3(线程的阻塞和生命周期)

2017-01-15 18:41 405 查看
上一篇文章中我们看到了多个线程的顺序执行和非顺序执行的情况,我们知道现在大多jvm都是抢占式的所以出现非顺序的情况是正常的,而为了某些需求我们也可以同过线程阻塞来实现线程的顺序执行,下面我们就看看看线程阻塞的方式有哪些,他们之间有什么联系有什么区别。

一、线程休眠、cpu资源放弃以及线程等待方法

1、sleep(long millis)

(1)、Thread类的sleep方法就是让当前正在运行的线程休眠指定时间,到达指定时间后线程被唤醒,唤醒后该线程处于可运行状态,又和其他线程争抢cpu资源。其实当我们的线程调用start方法后线程就被启动了,这时线程告诉了线程调度器我准备好了,可以随时运行,同样还有很多可运行的线程也在等待,但是cpu资源有限很多还没有抢到cpu资源所以很多线程包括刚启动的这个线程一值处在可运行状态,当他抢到cpu资源后也就是开始运行了即处于运行状态,这时如果线程中有调用sleep方法就会把这个线程抢占的cpu资源释放并将这个线程放到休眠队列中,这时线程是处于休眠状态的,这个状态的线程是不会和其他的处于可运行状态的线程会抢cpu资源,直到到达指定的时间该被唤醒从休眠状态再次进入可运行状态,这时又和其他可运行线程挣抢cpu资源了。

所以调用sleep方法线程的一个过程:运行(不运行也就调用不了sleep)、休眠(调用了sleep)方法、唤醒后处在可运行状态、再次运行、结束

(2)、Thread类的静态方法sleep()在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响,还有调用的时间等,只能说大概是这个时间!

(3)、sleep(long millis,int nanos)
和sleep(long time)是一样的只是指定了纳秒数,精确到了纳秒级别。

(4)、sleep方法是静态方法,只能控制当前正在运行的线程。

(5)、建议将sleep方法放在run方法中运行,保证其他线程的运行,当前线程的停止。

(6)、sleep方法在阻塞的时候不释放资源锁(锁会在之后学习),在统一资源共享的时候如果不释放锁则其他线程只能等待,所以有点不太好。

(7)、看看线程sleep方法的执行过程:



从上图我们看到,当线程执行sleep方法后,该线程会休眠(这里说阻塞好像不太妥)一段时间,在这期间一直是处于休眠状态,不会和其他cpu抢资源,直到时间点被唤醒,该线程又进入到可运行状态,在次进入等待获取cpu资源的这样的一个状态。

(8)、sleep()方法demo

package test3;

public class ExtendThread extends Thread {
@Override
public void run(){
try {
System.out.println(Thread.currentThread().getName());
//阻塞100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程创建,任务执行 Thread!");
}
}


package test3;

public class ImplRunnable  implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("线程创建,任务执行 Runnable!");
}
}


创建线程,启动线程:

package test3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MainTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExtendThread  thread1 = new ExtendThread();
ImplRunnable thread2= new ImplRunnable();
Thread  thread3 = new Thread(thread2);
thread1.setName("thread1");
thread3.setName("thread3");
thread1.start();
thread3.start();
}
}
执行结果:

thread1
thread3
线程创建,任务执行 Runnable!
线程创建,任务执行 Thread!
我们对thread1线程进行了sleep(100)所以我们的thread3每次都是优先于thread1执行完,但是注意即使不使用sleep()方法也有可能会出现这样的执行结果,因为java线程的执行机制就是抢占式的所以怎样的执行结果都是有可能的,只是使用了sleep(long time) 方法后每次都是这个结果,还有一定要注意输出也是线程不安全的,所以在竞争比较多的情况下在两个System.out.println();也会出现异步情况。

2、yield(这里列出来了,但是yield不具有阻塞功能)

(1)、Thread类的yield方法就是释放当前占用的cpu资源,而没有进入阻塞队列中而仅仅是释放了占有的cpu资源,因为它没有让线程阻塞所以释放后该它很有可能会立马再次获得cpu资源。

(2)、同样yield方法也是Thread类的静态方法所以不要去执行让某个线程释放cpu资源这样的事而应该是让当前线程释放cpu资源所以我们要在run方法中使用。

(3)、下面我们看看一个运行中的线程调用yield方法以及执行yield方法后的整个流程:



yield使得线程放弃当前获得的cpu资源,但是不会使线程阻塞即线程仍处于可执行状态,随时肯能再次获得cpu资源,调用yield的效果等价于调度程序认为该线程已执行了足够时间从而转到另一个线程。

(4)、yield()方法demo

package test3;

public class ExtendThread extends Thread {
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
Thread.yield();
System.out.println("线程创建,任务执行 Thread!");
}
}


package test3;

public class ImplRunnable  implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println("线程创建,任务执行 Runnable!");
}
}


创建线程,启动线程

package test3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MainTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExtendThread  thread1 = new ExtendThread();
ImplRunnable thread2= new ImplRunnable();
Thread  thread3 = new Thread(thread2);
thread1.setName("thread1");
thread3.setName("thread3");
thread1.start();
thread3.start();
}
}
执行结果:/8520

thread1
thread3
线程创建,任务执行 Runnable!
线程创建,任务执行 Thread!


thread1
thread3
线程创建,任务执行 Runnable!
thread1:30806711166046
thread1:30806712040195
线程创建,任务执行 Thread!


当然这也说不了问题,但是调用了yield()方法后这种情况出现的概率就比较大了,还有注意两个Sytem.out.println()中也有可能会出现异步现象。

注意:sleep(long  time) 和yield方法都是Thread类的静态方法,所以说你不能说让某个线程休眠只能让当前线程来休眠。

3、join

(1)、join方法不是Thread类的静态方法,所以可以指定让某个线程先执行,在让其他线程执行。

(2)、join方法的作用就是让当前执行的线程阻塞直到调用join方法的线程结束或者让当前执行的线程阻塞指定时间,到那个时间后阻塞结束而不管调用join方法的线程是否运行结束,所以使用join方法我们能让异步的线程同步化即同步执行。

(3)、join方法的源码:

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");
}
//如果没有指定则默认是0,0表示要等到调用线程结束才唤醒执行线程。
if (millis == 0) {
//如果执行调用线程对象的join方法的线程还是活着,则调用wait()方法让这个线程挂起。
while (isAlive()) {
wait(0);
}
} else {
//如果指定了时间
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
//线程等待指定时间,到指定时间后线程就自动处于runnable状态
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
从源码我们能看到,join方法内部是使用wait()来实现线程的阻塞的。

(4)、join方法使用demo

package test3;

public class ExtendThread extends Thread {
@Override
public void run(){
System.out.println(Thread.currentThread().getName());
}
}


不使用join()的情况下:

package test3;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MainTest {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
int i = 0;
Thread[] threads = new Thread[50];
while (i < 50) {
Thread t = new ExtendThread();
t.setName(t + "");
threads[i] = t;
i++;
}
for (int j = 0; j < 50; j++) {
threads[j].start();
//threads[j].join();
}
}
}


执行结果:

Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-3,5,main]
Thread[Thread-30,5,main]
Thread[Thread-29,5,main]
Thread[Thread-26,5,main]
Thread[Thread-25,5,main]
Thread[Thread-7,5,main]
Thread[Thread-22,5,main]
Thread[Thread-21,5,main]
Thread[Thread-8,5,main]
Thread[Thread-18,5,main]
Thread[Thread-14,5,main]
Thread[Thread-17,5,main]
Thread[Thread-10,5,main]
Thread[Thread-15,5,main]
Thread[Thread-41,5,main]
Thread[Thread-6,5,main]
Thread[Thread-13,5,main]
Thread[Thread-19,5,main]
Thread[Thread-20,5,main]
Thread[Thread-9,5,main]
Thread[Thread-2,5,main]
Thread[Thread-45,5,main]
Thread[Thread-5,5,main]
Thread[Thread-24,5,main]
Thread[Thread-23,5,main]
Thread[Thread-16,5,main]
Thread[Thread-12,5,main]
Thread[Thread-33,5,main]
Thread[Thread-11,5,main]
Thread[Thread-37,5,main]
Thread[Thread-4,5,main]
Thread[Thread-38,5,main]
Thread[Thread-27,5,main]
Thread[Thread-42,5,main]
Thread[Thread-34,5,main]
Thread[Thread-49,5,main]
Thread[Thread-31,5,main]
Thread[Thread-28,5,main]
Thread[Thread-46,5,main]
Thread[Thread-32,5,main]
Thread[Thread-35,5,main]
Thread[Thread-36,5,main]
Thread[Thread-39,5,main]
Thread[Thread-40,5,main]
Thread[Thread-43,5,main]
Thread[Thread-44,5,main]
Thread[Thread-47,5,main]
Thread[Thread-48,5,main]


使用join方法:

package test3;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MainTest {
public static void main(String[] args) throws InterruptedException,
ExecutionException {
int i = 0;
Thread[] threads = new Thread[50];
while (i < 50) {
Thread t = new ExtendThread();
t.setName(t + "");
threads[i] = t;
i++;
}
for (int j = 0; j < 50; j++) {
threads[j].start();
//同步执行
threads[j].join();
}
}
}


执行结果:

Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-2,5,main]
Thread[Thread-3,5,main]
Thread[Thread-4,5,main]
Thread[Thread-5,5,main]
Thread[Thread-6,5,main]
Thread[Thread-7,5,main]
Thread[Thread-8,5,main]
Thread[Thread-9,5,main]
Thread[Thread-10,5,main]
Thread[Thread-11,5,main]
Thread[Thread-12,5,main]
Thread[Thread-13,5,main]
Thread[Thread-14,5,main]
Thread[Thread-15,5,main]
Thread[Thread-16,5,main]
Thread[Thread-17,5,main]
Thread[Thread-18,5,main]
Thread[Thread-19,5,main]
Thread[Thread-20,5,main]
Thread[Thread-21,5,main]
Thread[Thread-22,5,main]
Thread[Thread-23,5,main]
Thread[Thread-24,5,main]
Thread[Thread-25,5,main]
Thread[Thread-26,5,main]
Thread[Thread-27,5,main]
Thread[Thread-28,5,main]
Thread[Thread-29,5,main]
Thread[Thread-30,5,main]
Thread[Thread-31,5,main]
Thread[Thread-32,5,main]
Thread[Thread-33,5,main]
Thread[Thread-34,5,main]
Thread[Thread-35,5,main]
Thread[Thread-36,5,main]
Thread[Thread-37,5,main]
Thread[Thread-38,5,main]
Thread[Thread-39,5,main]
Thread[Thread-40,5,main]
Thread[Thread-41,5,main]
Thread[Thread-42,5,main]
Thread[Thread-43,5,main]
Thread[Thread-44,5,main]
Thread[Thread-45,5,main]
Thread[Thread-46,5,main]
Thread[Thread-47,5,main]
Thread[Thread-48,5,main]
Thread[Thread-49,5,main]


通过两种执行方式我们看到,如果使用join方法就可以让异步的线程同步执行,我看分析一下demo,为什么会同步执行。

for (int j = 0; j < 50; j++) {
threads[j].start();
//同步执行
threads[j].join();
}


五十个线程,如果不使用join()方法也就是直接启动五十个线程,而我们知道线程是抢占式获取的,所以五十个中的任何一个都有可能首先获取cpu资源而执行,当执行到一半的时候有可能又释放了资源,所以其他的线程又获取了,就这样一直抢直到这五十个线程结束。而如果我们在启动后调用了join()方法,整个过程是在main线程中我们启动了一个线程,如果启动的这个线程获取了资源就执行,如果没有抢到则main线程会一直执行,当执行到join()的时候我们看了源码的,他就会让main线程阻塞直到启动的这个线程执行完,当启动的这个线程执行完后main线程再次进入可运行状态,获取cpu再次去启动一个线程接着执行join,这种流程循环50次直到循环结束,整个过程就是一个个的线程的启动执行、执行结束在启动另一个线程在执行、执行结束……

这就是使用join来实现线程的顺序执行,然而从源码中我们看到,join方法的内部是使用wait方法来实现线程的阻塞的。下面我们就来看看object类的wait()方法。

4、wait()方法,这个方法是Object类的即相当于任何一个对象都有这个方法。

我们知道wait方法和notify、notifyall方法是Object类的方法,但是wait、notify、notifyall方法要在synchronized方法或代码块中使用,者篇文章我们不会具体讲线程的同步只是简单的了解一下wait()方法。

(1)、在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待。

(2)、wait方法阻塞线程后,会释放synchronized对象锁,其他的线程可以获取这个对象锁,是否还记的上面的sleep(long time)方法,sleep方法阻塞线程是不会释放对象锁的。

5、await 方法

(1)、await方法是Condition接口的方法,其实await的使用也是在Lock 中使用和wait在synchronized 中使用是一样的。所以await和wait 是一样的同样signal和notify是一样的、notifyall 和signalAll方法是一样的。

(2)、我们可以创建多个Condition实例,灵活的使用await、signal和signalAll方法来实现灵活的阻塞和唤醒,具体的学习还是留到后面的Lock锁的学习那一节的内容中。

ok 到这里我们线程的阻塞方式就完了,简单的总结一下阻塞方式:

(1)、使用Thread类的sleep(long  time)方法来实现线程的休眠,但是线程的休眠期间不释放对象锁。

(2)、使用Thread类的yield()方法,这个方法严格的来说不算线程的阻塞,因为调用他的时候不会出现线程的阻塞,而只是释放cpu资源从运行状态进入到了可运行状态。

(3)、Object类的wait()方法以及Condition的await()方法的使用都是有限制的即在synchronized方法或代码块中使用以及Lock中使用。

(4)、join()方法,join()方法的主要用途就是线程的同步执行但是join()方法内部还是使用了wait()方法来实现线程的阻塞。

6、线程的阻塞synchronized方法/代码块的使用

(1)、我们知道synchronized方法或代码块实现线程的互斥同步,当多个线程访问同一个共享资源时,如果有synchronized方法或代码块则需要获取对应的对象锁,但是一个对象只有一个对象锁,如果A线程获得了对象锁则其他线程在A线程释放锁之前就处于阻塞状态。

二、线程的生命周期

 从开始到现在其实我们已经接触了线程生命周期的个个阶段,下面我们来看看线程的生命周期。

1、一个线程的生命周期包括这么几个阶段:线程的创建、线程的启动(线程处于可运行状态)、线程的运行(线程获得了cpu资源,正在执行任务)、线程的阻塞、线程的结束(正常结束、不正常结束)

2、线程生命周期图:



(1)、线程创建后如果不调用start()方法启动线程,则这个线程是不会被执行的。

(2)、线程创建后调用start()方法启动线程后,不一定就能获得cpu资源,因为现在的操作系统大都都是抢占式的,所以线程也是异步的。

(3)、yield方法是不会阻塞的只是释放了cpu资源。

(4)、线程的run()方法是驱动执行任务的,如果run方法结束则这个线程的任务也就结束了。

(5)、通过上一篇的学习我们发现run()方法是被jvm调用执行的,也因此才有了多线程,如果我们在程序中自己调用run()方法,则执行这个任务的线程是main线程而不是jvm的线程。

线程的生命周期,其中阻塞线程的唤醒方式总的来说有三种即sleep、notify\notifyall、signal\signalAll三种方法,其中的sleep方法就没什么可说的等待指定时间后会被系统自动唤醒,而剩下的两种方式,除了等待指定时间后被系统唤醒后还有就是调用notify\notifyAll、以及signal\signalAll方法来唤醒,这里有个问题当同一个共享资源有多个线程访问并且有多个处于阻塞状态时如果使用notify则只会随机唤醒一个等待同一个共享资源的阻塞线程,而如果使用notifyAll则会唤醒等待同一共享资源的所有阻塞线程。

注意: (1)、同一共享资源 (2)、随机一个同一共享资源 (3)、所有同一共享资源  (4)、所有的前提就是同一共享资源,
如果不是同一共享资源的线程是不会被唤醒的,如果被唤醒那也就乱了……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: