线程概念
线程就是进程(进程就是在运行过程中的程序)的执行单元。过多内容不再赘述。这里注意一下java的线程是映射到操作系统的线程的,在执行线程操作的时候实际上是需要调用操作系统的方法的
创建线程的四种方式
方式一
继承Thread类
public class MyThread extends Thread
注意:
- 一个线程调用两次start()方法会抛出java.lang.IllegalThreadStateException的异常,也就是start()只可以被调用一次
因为线程在启动的时候会有几种状态,每种状态改变的时候都会修改threadStatus的值,所以第二次启动的时候发现threadStatus的值已经改变了不是0了,就会抛出IllegalThreadStateException的异常
- 看start0()方法的时候,发现没有方法体,只有方法名,且是native的,说明是本地方法,是调用c语言的方法
- 不可以手动调用run()方法
方式二
实现Runnable()接口
public class MyThread implements Runnable
注意:
- 接口方式避免单继承的局限性
- 子类通过实现Runnable中的接口,实现自己的逻辑,而Thread则负责资源的调度与辅助业务(类似于代理模式)
方式三
实现Callable接口
public class MyThread implements Callable<String> {
@Override
public String call() {
// do something...
return "";
}
public static void main(String[] args) {
Callable<String> callable =new MyThread();
FutureTask <String>futureTask=new FutureTask<>(callable);
Thread mThread=new Thread(futureTask);
mThread.start();
}
}
这种方式在java 5中新增的方式,实现Callable接口可以有返回值,同时可以抛出异常,但是前两种方式就不具备这种特点,(虽然说不可以抛出异常但是可以通过UncaughtExceptionHandler获取到异常),Callable接口就好比Runable接口的升级版,call()方法作为线程执行的具体逻辑可以有返回值。
但是Callable对象不能直接构造线程,需要通过FutureTask实现类将Callable包装后才能开启线程,因为FutureTask同时实现了Future接口和Runnable接口,而Future接口可以接收Callable。
方式四
线程池方式,这种方式在这里就简单的说一下,后面会专门开篇文章详细介绍线程池
public static void main(String[] args) {
ExecutorService ex = Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++) {
ex.submit(new Runnable() {
@Override
public void run() {
for(int j=0;j<10;j++) {
System.out.println(Thread.currentThread().getName()+j);
}
}
});
}
ex.shutdown();
}
线程的六种状态
在java.lang.Thread.State中可以详细看到一共有六种状态
直接就在网上找了张图,然后说明一下每种状态都代表什么意思
- 初始(NEW):单纯的创建了一个线程,还没有调用start()方法
- 运行(RUNNABLE):就绪(READY)和运行中(RUNNING)两种状态笼统的称为“运行”。线程在创建之后调用start()方法时,可能不会立即就获得CPU执行权力,而是出于等待获得CPU执行权力的状态,此时线程位于可运行线程池中,等待被线程调度选中,获得CPU执行权力,这时就是就绪状态(READY)。处于就绪状态的线程在获得CPU执行权力之后就会变为运行中状态(RUNNING)
- 阻塞(BLOCKED):表示因为存在锁的原因而阻塞
- 等待(WAITING):因为调用wait()方法,导致线程处于等待状态,这种状态下需要其他线程调用notify()/notifyAll()方法来结束
- 超时等待(TIMED_WAITING):类似于WAITING的状态,但是可以设定超时时间,在超过超时时间后可以自行结束等待状态
- 终止(TERMINATED):表示线程已经执行完毕
线程的六种状态–详细说明
初始状态
通过上面四种方式创建线程后,还没有调用start()方法,线程就进入了初始状态。
运行状态–就绪状态
就绪状态只是说线程有资格运行,但是调度程序没有挑选这个线程,那么就一直是就绪状态(有一点类似缓冲区,在实际执行前的一个缓冲区,可能是为了防止多个线程同时执行,而不知道应该执行哪个线程)
运行状态–运行中状态
调度程序从就绪状态的线程池中选择一个线程去获得CPU执行权限,执行具体的线程逻辑。这个是线程进入到运行中状态的唯一方式
阻塞状态
线程因为等待获取锁的时候而处于阻塞状态。具体详细说明可以看我的这篇文章白话Java锁–synchronized关键字
等待状态
处于等待状态的线程不会被分配CPU执行时间,需要去被显式的唤醒,否则会处于无限等待的状态
超时等待状态
处于这种的等待状态的线程同样不会被分配CPU执行时间,不过不需要无限期的去等待其他线程显示的唤醒,而是在达到一定时间后会被自动唤醒
终止状态
当线程run()方法执行完毕之后,或者主线程执行完毕时,就认为终止了。而且执行完毕了之后不能再次start(),不能复生,否则会抛出java.lang.IllegalThreadStateException异常
线程的六种状态–如何进行转换
初始状态–>运行状态
调用线程的start()方法即可
public synchronized void start()
运行状态–>超时等待状态
- 调用Thread.sleep(long millis)方法,让线程暂缓执行,等到预计设定好的时间之后再恢复执行。主要作用就是给其他线程执行的机会。
public static native void sleep(long millis) throws InterruptedException;
- 休眠会交出CPU执行权限,让CPU去执行其他任务
- 进入休眠状态的线程不会释放锁,即当前线程持有某个对象锁的时候,调用sleep后,其他线程无法获取这个对象锁(这个需要与wait方法做区分,因为wait方法会释放持有的对象锁)
- 调用Object.wait(long millis)方法,让获得到CPU执行权限的线程放弃权限,进入到超时等待状态,进行等待其他线程的唤醒或者等待预先设定好的时间
public final native void wait(long timeout) throws InterruptedException;
- 进入超时等待的线程会释放锁
- 调用Thread.join(long millis)方法,join方法是指如果在主线程中调用这个方法就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。
public final synchronized void join(long millis) throws InterruptedException
- 实际上就是调用join方法的线程等待被调用的线程执行完毕之后才开始执行,可以设定超时时间,从而进入超时等待的状态
- 同样的因为join进入超时等待的线程不会释放锁,其他线程无法获取这个锁对象(与sleep类似)
- 调用JUC包中的方法,在本篇文章里先不对JUC包中的内容进行讲解。
运行状态<–超时等待状态
- 调用Object.notify()/notifyAll()方法,notify()方法会唤醒某个因为调用wait()方法而处于等待队列的线程,是随机的,notifyAll()方法可以唤醒所有处于等待队列中的线程
public final native void notify();
public final native void notifyAll();
- 唤醒的线程可以处于运行状态去重新竞争对象锁
- 超时时间到同样会将线程转化为运行状态
- 调用JUC包中的方法
运行状态–>等待状态
与超时等待类似
- 调用Object.wait()方法,让获得到CPU执行权限的线程放弃权限,进入到等待状态,进行等待其他线程的唤醒
public final void wait() throws InterruptedException {
wait(0);
}
- 进入等待的线程会释放锁
- 调用Thread.join()方法,join方法是指如果在主线程中调用这个方法就会让主线程休眠,让调用join()方法的线程先执行完毕后再开始执行主线程。
public final void join() throws InterruptedException {
join(0);
}
- 实际上就是调用join方法的线程等待被调用的线程执行完毕之后才开始执行,调用线程因为等待从而进入等待状态
- 同样的因为join进入等待的线程不会释放锁,其他线程无法获取这个锁对象(与sleep类似)
- 调用JUC包中的方法,在本篇文章里先不对JUC包中的内容进行讲解。
运行状态<–等待状态
- 调用Object.notify()/notifyAll()方法,notify()方法会唤醒某个因为调用wait()方法而处于等待队列的线程,是随机的,notifyAll()方法可以唤醒所有处于等待队列中的线程
public final native void notify();
public final native void notifyAll();
- 唤醒的线程可以处于运行状态去重新竞争对象锁
- 调用JUC包中的方法
运行状态–>阻塞状态
线程在进入synchronized方法或者synchronized块的时候,因为其他线程以及获得到对象锁,导致这个线程无法获得到对象锁,从而为了等待对象锁而处于阻塞状态。
运行状态<–阻塞状态
处于阻塞状态的线程,因为获得到了对象锁,重新去竞争CPU执行权,处于运行状态。
就绪状态–>运行中状态
通过系统的调度线程调度,让处于就绪状态的线程去获取CPU执行权限,处于运行中状态
就绪状态<–运行中状态
- 处于就绪状态的线程,因为CPU时间片使用完了导致进入到就绪状态,重新去竞争CPU执行权
- 调用yield()方法让出执行权而进入到就绪状态。作用:让相同优先级的线程轮流执行,但不保证一定会轮流执行。实际上无法保证达到让步的目的,因为让步的线程可能还会被调度线程选中。
public static native void yield();
- yield()方法和sleep()方法类似,不会释放锁,但yield()方法不能控制具体交出CPU的时间
- yield()方法只能让拥有相同优先级的线程获得CPU的执行机会
- yield()方法不会让线程进入到阻塞状态
线程终止
线程终止有三种方式:
方式一
设置标记位,让线程正常停止
class MyThread implements Runnable{
//设置标记位
private boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while(flag)
{
// do somthing...
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread);
thread1.start();
myThread.setFlag(false);
System.out.println("代码结束");
}
}
方式二
使用stop()方法强制使线程退出,但是该方法不安全,已经废弃了
class MyThread implements Runnable{
//设置标记位
private boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while(flag)
{
// do somthing...
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread);
thread1.start();
thread1.stop();
System.out.println("代码结束");
}
}
那么为什么stop()方法不安全呢?
因为stop()方法会释放有线程获得的所有锁,当一个线程在执行stop()方法的时候,线程会立即停止,假如在执行同步方法的时候,执行了一半,线程就被stop()了,那么就会造成数据的不完整性,所以stop()方法不安全,已经废弃,不建议使用。
方式三
使用Thread的interrupt()方法中断线程
class MyThread implements Runnable{
//设置标记位
private boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while(flag)
{
// do somthing...
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException{
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread);
thread1.start();
thread1.interrupt();
System.out.println("代码结束");
}
}
interrupt()方法其实类似于第一种方法,也是改变中断状态,不会立即终止一个正在运行的线程,在调用interrupt()方法的时候会给线程设置一个为true的中断标识,设置之后,会根据线程的状态执行后续的操作
- 如果线程当前状态处于非阻塞状态,仅仅将线程的中断标识设置为true
- 如果当前线程处于阻塞状态,在设置中断标识为true后,又调用了wait()、sleep()、join()方法而进入等待状态,那么线程中断标志位会被重新设置为false,并抛出InterruptedException异常
- 如果在中断时,线程处于非阻塞状态,则将中断标识设置为true,但是又调用了wait()、sleep()、join()方法而进入等待状态,那么线程中断标志位会被重新设置为false,并抛出InterruptedException异常
调用interrupted()方法的时候实际上只是设置了线程中断的标识,并根据线程后续的执行状态来决定是否抛出异常。根据中断标识的具体值来决定线程如何退出。