我们都知道现在硬件水平越来越强,执行速度也越来越快。为充分利用CPU资源,我们可以通过多线程来执行分片任务提升整体的执行性能。
如何创建线程
在Java中通过new Thread().start()方法来开启一个线程,实际是调用系统层的native方法
private native void start0();
Java使用线程常用方式有四种:
1、继承
Thread
类,Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法2、实现
Runnable
接口,如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable接口3、实现
Callable
接口,重写call()方法,可以返回一个 Future类型的返回值。4、通过线程池来使用线程,将任务交给线程池执行
public class CreateThreadDemo {
public static void main(String[] args){
// 单继承
new ThreadDemo().start();
// 实现runnable
new Thread(new RunnableDemo()).start();
// futureTask获取返回值
FutureTask futureTask = new FutureTask(new CallableDemo());
new Thread(futureTask).start();
//get()方法获取返回值
System.out.println(futureTask.get());
}
// 缺点:单继承
public static class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("ThreadDemo:" + Thread.currentThread());
}
}
// 不用担心单继承,但没有返回值
public static class RunnableDemo implements Runnable {
@Override
public void run() {
System.out.println("RunnableDemo:" + Thread.currentThread());
}
}
//
public static class CallableDemo implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("CallableDemo:" + Thread.currentThread());
return "return CallableDemo:" + Thread.currentThread();
}
}
}
本质上都是调用Thread类的start0()方法来启用
线程的生命周期
1、新建状态(NEW) 当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配 内存,并初始化其成员变量的值
2、就绪状态(RUNNABLE):当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和 程序计数器,等待调度运行。
3、运行状态(RUNNING):如果处于就绪状态的线程获得了CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
4、阻塞状态(BLOCKED):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。 直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状 态。
阻塞的情况分三种: 等待阻塞(o.wait->等待对列): 运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue) 中。 同步阻塞(lock->锁池) 运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线 程放入锁池(lock pool)中。 其他阻塞(sleep/join) 运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时, JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入可运行(runnable)状态。
5、线程死亡(DEAD) 线程会以下面三种方式结束,结束后就是死亡状态。
正常结束 1. run()或 call()方法执行完成,线程正常结束。 异常结束 2. 线程抛出一个未捕获的 Exception 或 Error。 调用 stop 3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用
线程的6种状态
1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
3、阻塞(BLOCKED):表示线程阻塞于锁。
4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
6、终止(TERMINATED):表示该线程已经执行完毕。
如何关闭线程
1、设置退出标志,使线程正常退出
public class ThreadSafe extends Thread {
public volatile boolean exit = false;
public void run() {
while (!exit){
//do something
}
}
}
定义了一个退出标志,在定义exit时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只能由一个线程来修改 exit 的值。
2、使用interrupt()方法中断线程,在线程中使用isInterrupted()来判断是否存在中断标志
-
线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时, 会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。 阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让 我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的,一定要先捕获 InterruptedException 异常之后通过break来跳出循环,才能正常结束run方法。
-
线程未处于阻塞状态:使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。
public class ThreadSafe extends Thread {
public void run() {
while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出
try{
Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出
}catch(InterruptedException e){
e.printStackTrace();
break;//捕获到异常之后,执行 break 跳出循环
}
}
}
}
3、使用stop方法强行终止线程
程序中可以直接使用 thread.stop()来强行终止线程,安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出ThreadDeatherror的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误
sleep与wait区别
1、对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于 Object 类中的
2、sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然 保持者,当指定的时间到了又会自动恢复运行状态。
3、在调用 sleep()方法的过程中,线程不会释放对象锁。
4、而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此 对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
守护线程
标签:状态,调用,Thread,并发,线程,方法,public From: https://www.cnblogs.com/luojw/p/181387531、守护线程--也称“服务线程”,他是后台线程,它有一个特性,即为用户线程提供公共服务,在没有用户线程可服务时会自动离开。
2、守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
3、通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程 的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
4、在Daemon线程中产生的新线程也是Daemon的。
4、生命周期:守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周 期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出