目录
进程和线程
进程:是系统进行资源分配的基本单位。
线程:是程序执行,资源调度的最小单位。
关系:线程也叫轻量级进程,进程是线程的容器
线程的6种状态及状态转换
6种状态
本文特指Java语言中的线程状态及其转换,和操作系统级别的(进程)线程状态及其转换有所区别
先看一下java中Thread类的源码定义的6种状态,读源码很重要,读源码的注释更重要。
public enum State {
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
TERMINATED;
}
- 新建(New):创建后尚未启动的线程处于这种状态。
- 运行(Runnable):Runnable包括了操作系统线程状态中的Running(执行态)和Ready(就绪态)。处于此状态的线程有可能正在执行,也可能等待CPU为它分配执行时间。
- 无限期等待(Waiting):处于这种状态的线程不会被分配CPU执行时间,他们要等待被其他线程显式唤醒。举例见上面源码注释
- 限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式唤醒,在一定时间之后它们会由系统自动唤醒。举例见上面源码注释。
- 阻塞(Blocked):阻塞状态和等待状态的区别在于:阻塞状态在等待获取到一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生;而等待状态则是在等待一段时间,或者唤醒动态的发生。在线程等待进入同步区域的时候,线程将进入阻塞状态
- 结束(Terminated):已经终止线程的线程状态,线程已经结束运行。
状态转换关系图
新建线程
使用new关键字创建一个线程对象,并且调用其start()方法即可。线程类Thread,有一个run()方法。
start()方法就会新建一个线程并让此线程执行run()方法。
package test8;
public class Main {
public static void main(String[] args) {
Thread t1= new Thread(){
@Override
public void run(){
System.out.println("Hello");
}
};
t1.start();
}
}
一个普通类要成 线程类的几种办法(自定义线程)
- 1.普通类继承(extends)Thread类,重写run()方法。考虑到Java是单继承的,继承本身是一种宝贵的资源,所以此方法不常用。
- 2.普通类实现(implements)Runnable接口,重写run()方法。该方案较为常用。
package test8;
public class MyThread implements Runnable {
@Override
public void run(){
System.out.println("我是一个自定义的线程类创建的对象");
}
}
package test8;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.start();
}
}
- 3.一个普通类实现Callable<T>接口,并重写call()方法。注意:在使用此线程类的时候需要结合FutureTask实现使用,用于接收运算结果
package test9;
import java.util.concurrent.Callable;
public class MyThread implements Callable<String> {
@Override
public String call() throws Exception{
return "我是自定义线程产生的一个对象";
}
}
package test9;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
//执行Callable方式,需要FutureTask实现,用于接收运算结果
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 4.创建线程池的方式创建线程,在这里不多做赘述,可以参考我的其他关于线程池的文章。
终止线程
一般来说,线程执行完毕就会结束,无须手工关闭。但是我们在某些条件下需要手动控制线程结束,并不会让线程执行完毕自动结束。
Thread类提供了stop()方法。但是该方法已经被废弃,原因是因为该方法在结束线程时,会直接终止线程,并立即释放此线程所持有的锁。而锁的作用是保证数据的一致性。可想而知,该方法会引起一些数据不一致的问题
示例:
package test10;
/**
* 用户类
*/
public class User {
private int id;
private String name;
public User() {
id = 0;
name = "0";
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//...省略get set方法
}
package test10;
public class WriterThread implements Runnable {
private User user;
public WriterThread(User user) {
this.user = user;
}
@Override
public void run(){
while (true){
synchronized (user){
int v = (int)(System.currentTimeMillis()/1000);
user.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
user.setName(String.valueOf(v));
}
Thread.yield();
}
}
}
package test10;
public class ReaderThread implements Runnable {
private User user;
public ReaderThread(User user) {
this.user = user;
}
@Override
public void run() {
while (true){
synchronized (user){
if(user.getId() != Integer.parseInt(user.getName())){
System.out.println(user.toString());
}
}
Thread.yield();
}
}
}
package test10;
public class Main {
public static void main(String[] args) throws InterruptedException{
User user = new User();
ReaderThread readerThread = new ReaderThread(user);
Thread threadRead = new Thread(readerThread);
threadRead.start();
while (true){
WriterThread writeThread = new WriterThread(user);
Thread threadWrite = new Thread(writeThread);
threadWrite.start();
Thread.sleep(150);
threadWrite.stop();
}
}
}
在此种情况下,如何终止线程呢,一般采用标记变量的方式。
线程中断(重要)
线程中断是一种重要的线程协作机制,线程中断并不会使线程立即退出,而是给线程发送一个通知,告知目标线程退出。至于目标线程接到通知后如何处理,则完全由目标线程自行决定。
public void interrupt();//中断线程
public boolean isInterrupted();//判断是否被中断
public static boolean interrupted();//判断是否被中断,并清除当前的中断状态
interrupt()方法通知目标线程中断,也就是设置中断标志位,中断标志位表示当前线程已被中断了。
isInterrupted()方法判断当前线程是否被 中断(通过检查中断标志位)
interrupted()方法也可用来判断当前线程的中断状态,但同时会清除中断标志位
package test11;
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run(){
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println("当前线程被中断了");
break;
}
Thread.yield();
}
}
};
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
此例和终止线程的标记变量的方式比较类似,但是此功能更为强大,如果在循环体中, 出现了类似于wait()方法或者sleep()方法这样的操作,则只能通过中断来识别了。
等待(wait)和通知(notify)
为了支持多线程之间的协作,jdk提供了两个非常重要的和线程相关的方法:等待wait()方法和通知notify()方法,这两个方法不在Thread类中,而是在Object类中。
public final void wait() throws InterruptedException;
public final native void notify();
工作过程: 如果一个线程调用了object.wait()方法,那么它就会进入object对象的等待队列。这个等待队列中,可能会有多个线程,因为系统运行多个线程同时等待某一个对象。当object.notify()方法被调用时,它就会从这个等待队列中随机选择一个线程,并将其唤醒。需要注意,这个选择是不公平的。
注意:Object.wait()方法并不能随便调用,必须包含在对应的synchronized语句中。
wait()方法和notify()方法的工作流程细节
package test12;
public class ReadThread implements Runnable {
private Object object;
public ReadThread(Object object) {
this.object = object;
}
@Override
public void run() {
synchronized (object){
System.out.println("读线程开启");
try{
System.out.println(System.currentTimeMillis()+"读线程 start!");
object.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+"读线程 end!");
}
}
}
package test12;
public class WriteThread implements Runnable {
private Object object;
public WriteThread(Object object) {
this.object = object;
}
@Override
public void run() {
synchronized (object){
System.out.println(System.currentTimeMillis()+"写线程启动!");
object.notify();
System.out.println(System.currentTimeMillis()+"写线程 end!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package test12;
public class Main {
public static void main(String[] args) {
Object object = new Object();
Thread read = new Thread(new ReadThread(object));
Thread write = new Thread(new WriteThread(object));
read.start();
write.start();
}
}
wait和sleep的关系和区别:都可以让线程等待若干时间,除了wait()方法可以被唤醒外,另外一个主要区别就是wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。
挂起(suspend)和继续执行(resume)线程
不推荐使用的原因是因为挂起线程suspend()方法在导致线程暂停的同时,并不会释放任何锁资源。直到对应的线程进行了resume()方法操作,被挂起的线程才能继续。
替代方案:使用wait()和notify()以及标记变量。
package test13;
public class WriteThread implements Runnable {
private Object object;
volatile boolean suspendThread = false;
public void setSuspendThread(boolean suspendThread) {
this.suspendThread = suspendThread;
}
public void resumeThread(){
this.suspendThread = false;
synchronized (this){
notify();
}
}
public WriteThread(Object object) {
this.object = object;
}
@Override
public void run() {
for(int i=0;i<10;i++){
synchronized (this){
while (suspendThread){
try{
wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
synchronized (object) {
System.out.println("写线程在写内容");
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.yield();
}
}
}
package test13;
public class ReadThread implements Runnable {
private Object object;
public ReadThread(Object object) {
this.object = object;
}
@Override
public void run() {
for (int i=0;i<10;i++) {
synchronized (object) {
System.out.println("读线程在读内容");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Thread.yield();
}
}
}
package test13;
public class Main {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
WriteThread writeThread = new WriteThread(object);
Thread write = new Thread(writeThread);
ReadThread readThread = new ReadThread(object);
Thread read = new Thread(readThread);
write.start();
read.start();
Thread.sleep(1000);
writeThread.setSuspendThread(true);
System.out.println(System.currentTimeMillis()/1000+"写线程挂起两秒");
Thread.sleep(2000);
System.out.println(System.currentTimeMillis()/1000+"继续执行写线程");
writeThread.resumeThread();
}
}
等待线程结束(join)和谦让(yield)
join使用的场景:一个线程的输入可能非常依赖另一个线程或者多个线程的输出,此时,这个线程就需要等待依赖线程执行完毕,才能继续执行。
yield使用的场景:它会让当前线程让出CPU。如果一个线程不那么重要,或者优先级特别低,而且又担心它会占用太多的CPU资源,那么可以在适当的时候调用此方法,给予其他重要线程更多的工作机会