更多内容可以访问我的个人博客。
(1)Java线程创建方式
方法一:继承Thread
类,作为线程对象存在(继承Thread对象)
public class CreatThreadDemo1 extends Thread{
/**
* 构造方法: 继承父类方法的Thread(String name);方法
* @param name
*/
public CreatThreadDemo1(String name){
super(name);
}
@Override
public void run() {
/*Thread类的interrupted方法,是来判断该线程是否被中断*/
while (!interrupted()){
System.out.println(getName()+"线程执行了...");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
d1.start();
d2.start();
d1.interrupt(); /*中断第一个线程*/
}
}
终止线程不允许用stop方法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像上面的代码一样。
方法二:实现runnable接口,作为线程任务存在
public class CreatThreadDemo2 implements Runnable {
@Override
public void run() {
while (true){
System.out.println("线程执行了...");
}
}
public static void main(String[] args) {
/*将线程任务传给线程对象*/
Thread thread = new Thread(new CreatThreadDemo2());
/*启动线程*/
thread.start();
}
}
Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里(new Thread(实现了Runnable接口的类)
)
方法三:匿名内部类创建线程对象
public class CreatThreadDemo3 extends Thread{
public static void main(String[] args) {
/*创建无参线程对象*/
new Thread(){
@Override
public void run() {
System.out.println("线程执行了...");
}
}.start();
/*创建带线程任务的线程对象*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行了...");
}
}).start();
/*创建带线程任务并且重写run方法的线程对象*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run 线程执行了...");
}
}){
@Override
public void run() {
System.out.println("override run 线程执行了...");
}
}.start();
}
}
执行结果:
线程执行了...
线程执行了...
override run 线程执行了...
方法四:创建带返回值的线程(FutureTask)
public class CreatThreadDemo4 implements Callable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4();
FutureTask task = new FutureTask(demo4); /*FutureTask最终实现的是runnable接口*/
Thread thread = new Thread(task);
thread.start();
System.out.println("我可以在这里做点别的业务逻辑...因为FutureTask是提前完成任务");
/*拿出线程执行的返回值*/
Integer result = task.get();
System.out.println("线程中运算的结果为:"+result);
}
/*重写Callable接口的call方法*/
@Override
public Object call() throws Exception {
int result = 1;
System.out.println("业务逻辑计算中...");
Thread.sleep(3000);
return result;
}
}
Callable接口介绍:
public interface Callable {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
返回指定泛型的call方法。然后调用FutureTask
对象的get方法得道call方法的返回值。
方法五:定时器(Timer、TimerTask)
import java.util.Timer;
import java.util.TimerTask;
public class CreatThreadDemo5{
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时器线程执行了...");
}
},0,1000); /*延迟0,周期1s*/
}
}
方法六:线程池创建线程
import java.util.concurrent.ExecutorService;
public class CreatThreadDemo6 {
public static void main(String[] args) {
/*创建一个具有10个线程的线程池*/
ExecutorService threadPool = Executors.newFixedThreadPool(10);
long threadpoolUseTime = System.currentTimeMillis();
for (int i = 0;i<10;i++){
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程执行了...");
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多线程用时"+(threadpoolUseTime1-threadpoolUseTime));
/*销毁线程池*/
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
(2)线程状态说明
- Java源码中Thread类中的内部枚举类型State一共声明了六种状态:
1. NEW: 新建状态,线程对象已经创建,但尚未启动。实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
**2. RUNNABLE:**就绪状态,可运行状态,调用了线程的start方法,已经在java虚拟机中执行,等待获取操作系统资源如CPU,操作系统调度运行。
- 2.1 就绪状态
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()
方法,此线程进入就绪状态。
当前线程sleep()
方法结束,其他线程join()
结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()
方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
- 2.2 运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
3. BLOCKED:堵塞状态。线程等待锁的状态,等待获取锁进入同步块/方法或调用wait后重新进入需要竞争锁
4. WAITING:等待状态。等待另一个线程以执行特定的操作。调用以下方法进入等待状态。 Object.wait()
, Thread.join()
,LockSupport.park
,处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
5. TIMED_WAITING: 线程等待一段时间。调用带参数的Thread.sleep()
, objct.wait()
,Thread.join()
,LockSupport.parkNanos
,LockSupport.parkUntil
会进入这种状态,处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
6. TERMINATED:进程结束状态。当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
(3)Thread中的方法
(1)start方法
API中对于该方法的介绍:
调用start()方法会使该线程开始执行,Java 虚拟机调用该线程的
run
方法。
结果是两个线程并发地运行;当前线程(从start
方法调用返回)和另一个线程(执行其run
方法)。
多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动。
用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
(2)run方法
API中对该方法的介绍:
如果该线程是使用独立的Runnable
运行对象构造的,则调用该Runnable
对象的run
方法;否则,该方法不执行任何操作并返回。Thread
的子类应该重写该方法。
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
- 调用start方法方可启动线程,而直接调用run方法只是Thread的一个普通方法调用,还是在主线程里执行。
(3)join方法
Thread类中的join()
方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
join方法也可以传递一个参数给它,t1.join(10)
表示主线程线程会等待t1线程10毫秒,10毫秒过去后,主线程和t1线程之间执行顺序由串行执行变为普通的并行执行,join(0)等价于join()。
join方法必须在线程start方法调用之后调用才有意义,join方法在start方法前调用时,并不能起到同步的作用,即join没有作用,等到start方法执行完后,两个线程依旧交替执行。
- join()方法实现原理
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");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join方法是通过调用线程的wait方法
来达到同步的目的的。例如,A线程中调用了B线程的join方法,则相当于A线程调用了B线程的wait方法,在调用了B线程的wait方法后,A线程就会进入阻塞状态,当B线程执行完(或者到达等待时间),B线程会自动调用自身的notifyAll方法唤醒A线程,从而达到同步的目的。
(4)yield方法
yield()
的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
- yield()和wait()方法的比较
① wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”。
② wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁(即只让出cpu时间片)。
(5)sleep方法
sleep()
的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行
- sleep() 与 wait()的比较
wait()的作用是让当前线程由“运行状态”进入“等待(WAITING)状态”的同时,而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(TIMED_WAITING)状态”。 但是,wait()会释放对象的同步锁,而sleep()则不会释放锁。
(6)interrupt方法和线程中断方式
interrupt()
的作用是中断本线程(不会终止处于“运行状态”的线程,而是通过将线程的中断标记设为true来终止线程)。
本线程中断自己是被允许的;其它线程调用本线程的interrupt()方法时,会通过checkAccess()
检查权限。这有可能抛出SecurityException
异常。
如果本线程是处于阻塞状态:调用线程的wait(), wait(long)或wait(long, int)会让它进入等待(阻塞)状态,或者调用线程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清除并且会收到一个InterruptedException
异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程;调用interrupt()会立即将线程的中断标记设为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException的异常。
如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时;线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
如果不属于前面所说的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为“true”。
中断一个“已终止的线程”不会产生任何操作。
- 中断线程的方式
Thread中的stop()和suspend()方法,由于固有的不安全性,已经建议不再使用!Why is Thread.stop
deprecated?
使用interrupt()终止线程:
- **interrupt():**在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。至于那个线程何去何从,由具体的代码实现决定。注意:interrupt()并不会终止处于“运行状态”的线程!它会将线程的中断标记设为true。
- isInterrupted():用来返回当前线程的中断状态(true or false)。
- interrupted():是一个Thread的static方法,用来恢复中断状态,即将中断标记重新设为false
(1)终止处于“阻塞状态”的线程
当线程由于被调用了sleep(), wait(), join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException
异常。将InterruptedException放在适当的位置就能终止线程,形式如下:
@Override
public void run() {
try {
while (true) {
/* 执行任务...*/
}
} catch (InterruptedException ie) {
/* 由于产生InterruptedException异常,退出while(true)循环,线程终止!*/
}
}
注意:对InterruptedException的捕获务一般放在while(true)循环体的外面,这样,在产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理(添加break语句)。
(2)终止处于“运行状态”的线程
通常,我们通过“标记”方式终止处于“运行状态”的线程。其中,包括“中断标记”和“额外添加标记”。
① 通过“中断标记”终止线程
@Override
public void run() {
while (!isInterrupted()) {
/*执行任务...*/
}
}
isInterrupted()
是判断线程的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时;可以调用线程的interrupt()
方法,使用线程的中断标记为true,即isInterrupted()
会返回true。此时,就会退出while循环。
② 通过“额外添加标记”
private volatile boolean flag= true;
protected void stopTask() {
flag = false;
}
@Override
public void run() {
while (flag) {
/* 执行任务...*/
}
}
线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
- 综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:
@Override
public void run() {
try {
/* 1. isInterrupted()保证,只要中断标记为true就终止线程。*/
while (!isInterrupted()) {
/* 执行任务...*/
}
} catch (InterruptedException ie) {
/* 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。*/
}
}
(4)Object类中的wait、notify、notifyAll方法
首先看以下示例代码:
代码定义了一个3秒后唤醒等待线程的NotifyThread线程类,定义了一个用以等待的WaitThread类,定义了一个flag数组对象用来充当等待的对象(flag需要进行修改判断的原因是防止notifyAll方法执行完后还有线程执行wait方法,比如线程1、2运行至wait方法,而线程3因未知原因阻塞了一会,这时NotifyThread线程执行完了notifyAll方法,则线程3无法被唤醒)。在main方法中同时启动一个Notify线程和三个wait线程。
public class NotifyTest {
private String flag[] = {"true"};
class NotifyThread extends Thread {
public NotifyThread(String name) {
super(name);
}
public void run() {
try {
sleep(3000);/* 推迟3秒钟通知*/
System.out.println("3s later");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (flag) {
flag[0] = "false";
flag.notifyAll();
}
}
};
class WaitThread extends Thread {
public WaitThread(String name) {
super(name);
}
public void run() {
synchronized (flag) {
while (flag[0] != "false") {
System.out.println(getName() + " begin waiting!");
long waitTime = System.currentTimeMillis();
try {
flag.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
waitTime = System.currentTimeMillis() - waitTime;
for(int i = 0;i< 3;i++) {
System.out.println(getName() + " wait time :" + waitTime);
}
}
System.out.println(getName() + " end waiting!");
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("Main Thread Run!");
NotifyTest test = new NotifyTest();
NotifyThread notifyThread = test.new NotifyThread("notify01");
WaitThread waitThread01 = test.new WaitThread("waiter01");
WaitThread waitThread02 = test.new WaitThread("waiter02");
WaitThread waitThread03 = test.new WaitThread("waiter03");
notifyThread.start();
waitThread01.start();
waitThread02.start();
waitThread03.start();
}
}
运行流程讲解:4个线程开始并行执行,3个等待线程中的某一个(比如waiter01)先获取到flag对象的锁,输出waiter01 begin waiting!,执行到wait方法,释放flag锁,开始等待,3个等待线程中的另外两个中某一个(比如waiter03)获取到flag锁,输出waiter03 begin waiting!
,然后执行到wait方法,释放flag锁,开始等待,余下的一个等待线程也一样。唤醒线程等待3秒之后,获取flag锁,调用notifyAll方法唤醒所有等待flag对象的线程,3个等待线程都被唤醒,依次竞争flag对象锁,依次输出结果。
输出结果:
Main Thread Run!
waiter01 begin waiting!
waiter03 begin waiting!
waiter02 begin waiting!
3s later
waiter02 wait time :3000
waiter02 wait time :3000
waiter02 wait time :3000
waiter02 end waiting!
waiter03 wait time :3000
waiter03 wait time :3000
waiter03 wait time :3000
waiter03 end waiting!
waiter01 wait time :3000
waiter01 wait time :3000
waiter01 wait time :3000
waiter01 end waiting!
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify() – 唤醒在此对象监视器上等待的单个线程。
notifyAll() – 唤醒在此对象监视器上等待的所有线程。
wait() – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) – 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
1. wait()方法
让当前线程进入等待状态,同时,wait()会让当前线程释放它所持有的锁。
2. notify()方法
notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现(不确定)。
3. notifyAll()方法
唤醒在此对象监视器上等待的所有线程。
- 为什么wait,notify,notifyAll定义在Object中?
一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。