线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
新建线程需要new一个线程对象然后start()启动即可。
Thread t1=new Thread();
t1.start();
在线程start()后它的内部有个run方法,start()方法会新建一个线程并让这个线程执行run方法。
需要注意的是:
Thread t1=new Thread();
// t1.start();
t1.run();
这个代码也能通过编译正常执行,但是它不会新建一个线程而是在当前线程调用run()方法,相当于串行代码。
如果想要输出此时应该重载run()方法,把需要运行的代码放进去:
public class ThreadDemo {
public static void main(String[] args) {
Thread t1=new Thread() {
@Override
public void run() {
System.out.println("运行");
super.run();
}
};
t1.start();
}
}
这里我是用了匿名内部类的方式重载run()方法然后打印“运行”。但是因为java是单继承的所以通过继承的方式去实现开启线程难免有些浪费,所以我推荐使用Runnable接口来实现相同的操作。
public class AThread implements Runnable{
@Override
public void run() {
System.out.println("运行");
}
}
public Thread(Runnable target);这个构造器需要传入一个Runnable接口的实例,在调用start()方法是,则会执行Runnable.run()方法。而默认的Thread.run()方法也是这么做的。
方法一:stop()方法(标注为废弃的方法)(不推荐,因为它会强制停止线程,可能会导致数据错乱。)
方法二:使用volatile变量做标记
package thread;
public class StopThreadUnsafe {
public static User u = new User();
public static class User {
private int id;
private String name;
public User() {
id = 0;
name = "0";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static class ChangeObjectThread extends Thread {
/**
* modify method to aoid Thread stop's effect
*
* @param args
*/
volatile boolean stopMe = false;
public void stopMe() {
stopMe = true;
}
@Override
public void run() {
while (true) {
// test
if (stopMe) {
System.out.println("exit by me");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
System.out.println(u.toString());
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
if (u.getId() != Integer.parseInt(u.getName())) {
System.out.println(u.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) {
new ReadObjectThread().start();
while (true) {
ChangeObjectThread t = new ChangeObjectThread();
t.start();
try {
Thread.sleep(1500);
t.stopMe();
break;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// try {
// Thread.sleep(150);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (false) {
// t.stop();// thread stop lead to data consistency for it is too rude
// } else {
// t.stopMe();// thread stop should use stopMe method for it is graceful
// }
}
}
}
因为volatile具有可见性和禁止指令重排序两个特性。
Thread类中有三个与线程中断相关的方法:
public void interrupt();//中断线程
public boolean isInterrupted();//判断是否被中断
public static boolean interrupted();//判断是否被中断,并清除当前中断状态
Thread.interrupt()方法是一个实例方法,它通知目标线程中断,设置中断标志位。中断标志位表示当前线程已经被中断。Thread.isInterrupted()方法也是实例方法,它通过检查中断标志位来进行判断是否中断。Thread.interrupted()也是用来判断当前线程的中断状态而且会清除当前线程的中断标志位状态。
如果只是单纯的调用Thread的interrupt()它可能并不会起什么作用。
package thread;
public class StopThreadUnsafe {
public static User u = new User();
public static class User {
private int id;
private String name;
public User() {
id = 0;
name = "0";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static class ChangeObjectThread extends Thread {
public void interrupr() {
this.interrupt();
}
@Override
public void run() {
while (true) {
// test
// if(Thread.currentThread().isInterrupted()) {
// System.out.println("已经中断");
// break;
// }
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
System.out.println(u.toString());
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
if (u.getId() != Integer.parseInt(u.getName())) {
System.out.println(u.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) {
new ReadObjectThread().start();
while (true) {
ChangeObjectThread t = new ChangeObjectThread();
t.start();
try {
Thread.sleep(1500);
t.interrupr();
break;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// try {
// Thread.sleep(150);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (false) {
// t.stop();// thread stop lead to data consistency for it is too rude
// } else {
// t.stopMe();// thread stop should use stopMe method for it is graceful
// }
}
}
}
运行后可以发现他并没停止运行任然在跑,这是因为我把中断处理的逻辑注释了,所以即使设置了中断状态它也没起什么作用。
如果想学中断后退出则可以将我的注释去掉。
package thread;
public class StopThreadUnsafe {
public static User u = new User();
public static class User {
private int id;
private String name;
public User() {
id = 0;
name = "0";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static class ChangeObjectThread extends Thread {
public void interrupr() {
this.interrupt();
}
@Override
public void run() {
while (true) {
// test
if(Thread.currentThread().isInterrupted()) {
System.out.println("已经中断");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
u.setName(String.valueOf(v));
System.out.println(u.toString());
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
if (u.getId() != Integer.parseInt(u.getName())) {
System.out.println(u.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) {
new ReadObjectThread().start();
while (true) {
ChangeObjectThread t = new ChangeObjectThread();
t.start();
try {
Thread.sleep(1500);
t.interrupr();
break;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// try {
// Thread.sleep(150);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (false) {
// t.stop();// thread stop lead to data consistency for it is too rude
// } else {
// t.stopMe();// thread stop should use stopMe method for it is graceful
// }
}
}
}
这种方式与添加标记的方式十分相似,但是他的中断功能更为强大,如果循环体中出现了wait()或者sleep()之类的操作只能通过中断进行识别。
Thread.sleep()函数:
public static native void sleep(long millis) throws InterruptedException;
Thread.sleep()方法会让当前线程休眠millis毫秒,它会抛出一个InterruptedException异常,所以要用程序进行捕获它,如果当前线程在休眠时被中断则会产生这个异常。
如:
package thread;
public class StopThreadUnsafe {
public static User u = new User();
public static class User {
private int id;
private String name;
public User() {
id = 0;
name = "0";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
}
public static class ChangeObjectThread extends Thread {
public void interrupr() {
this.interrupt();
}
@Override
public void run() {
while (true) {
// test
if(Thread.currentThread().isInterrupted()) {
System.out.println("已经中断");
break;
}
synchronized (u) {
int v = (int) (System.currentTimeMillis() / 1000);
u.setId(v);
try {
Thread.sleep(150);
} catch (InterruptedException e) {
System.out.println("中断了");
// Thread.currentThread().interrupt();
e.printStackTrace();
}
u.setName(String.valueOf(v));
System.out.println(u.toString());
}
Thread.yield();
}
}
}
public static class ReadObjectThread extends Thread {
@Override
public void run() {
while (true) {
synchronized (u) {
if (u.getId() != Integer.parseInt(u.getName())) {
System.out.println(u.toString());
}
}
Thread.yield();
}
}
}
public static void main(String[] args) {
new ReadObjectThread().start();
while (true) {
ChangeObjectThread t = new ChangeObjectThread();
t.start();
try {
Thread.sleep(2000);
t.interrupt();
// t.interrupr();
break;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// try {
// Thread.sleep(150);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// if (false) {
// t.stop();// thread stop lead to data consistency for it is too rude
// } else {
// t.stopMe();// thread stop should use stopMe method for it is graceful
// }
}
}
}
通过运行结果可以看到Thread.sleep()方法由于中断抛出了异常,但是它会清除中断标记,如果不处理那么在下次循环开始时就无法捕获这个中断所以要在异常处理中在此设置中断标记:
为了支持多线程协作,Object中有wait()和notify()方法。
public final void wait() throws InterruptedException;
public final native void notify();
当一个对象调用wait()方法后当前线程就会在这个对象上等待。等待结束的时机取决于其他线程调用obj.notify()方法。但是notify()方法只是随机唤醒一个线程,除了notify()方法Object里还有个notifyAll()方法唤醒所有线程。
需要注意的是Object.wait()方法和notify()方法都要在synchronzied语句块中,因为它们都需要首先获取目标对象的一个监视器。
现在看下交替打印ABC的方法:
package thread;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ABC {
public static class ThreadPrinter implements Runnable {
private String name;
private Object prev;
private Object self;
private ThreadPrinter(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {// 多线程并发,不能用if,必须使用whil循环
synchronized (prev) { // 先获取 prev 锁
synchronized (self) {// 再获取 self 锁
System.out.print(name);//打印
count--;
if(name.equals("C")) {
System.out.println();
}
self.notifyAll();// 唤醒其他线程竞争self锁,注意此时self锁并未立即释放。
}
//此时执行完self的同步块,这时self锁才释放。
try {
prev.wait(); // 立即释放 prev锁,当前线程休眠,等待唤醒
/**
* JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
ThreadPrinter pa = new ThreadPrinter("A", c, a);
ThreadPrinter pb = new ThreadPrinter("B", a, b);
ThreadPrinter pc = new ThreadPrinter("C", b, c);
new Thread(pa).start();
Thread.sleep(10);//保证初始ABC的启动顺序
new Thread(pb).start();
Thread.sleep(10);
new Thread(pc).start();
Thread.sleep(10);
}
}
首先会拿到上一个object对象的锁,然后获取自身的锁在打印后使用notifyAll()唤醒其他线程,然后由于prev.wait()使其等待,所以只有下一个对象才能被成功唤醒。
所以它能够顺序打印abc。
需要注意的是Object.wait()和Thread.sleep()方法都可以让线程等待指定的时间。但是它们的区别是wait()可以被唤醒,而且wait()方法会释放目标对象的锁,而Thread.sleep()方法不会释放任何资源。
Threadh还有两个接口是线程挂起和继续执行。这两个操作是一对相反的操作,被挂起的线程必须等到resume()操作后才能继续指定。但是它们已被标注为废弃方法,不推荐使用。
不推荐的原因是因为suspend()在导致线程暂停的同时,并不会释放任何锁资源。直到对应的线程进行了resume()操作,被挂起的线程才能继续执行。加入resume()操作在它suspend()前就执行了,这会导致它不释放锁资源并且从线程状态上看还是Runnable。(会导致线程进入类似死锁的状态)
当一个线程的输入依赖于另外的线程的输出时,此时这个线程需要等待依赖的线程执行完毕才能继续执行。
public final void join() throws InterruptedException;
public final synchronized void join(long millis) throws InterruptedException;
第一个join()方法表示无限等待,它会一直阻塞当前线程直到目标线程执行完毕。第二个方法则根据最大的等待时间进行判断如果超过这个时间则继续执行。
public class Join {
public volatile static int i = 0;
public static class AddThread extends Thread {
public void run () {
for(i=0;i<1000000;i++);
}
}
public static void main(String[] args) throws InterruptedException {
AddThread at = new AddThread();
at.start();
//如果注释了 join函数 那么得到的i可能是0或者一个非常小的数字。因为AddThread还没开始执行,i的值就已经被输出了。
//使用了join函数后,表示主线程愿意等待AddThread执行完毕,再执行。
// at.join();
System.out.println(i);
}
}
不加join为0。
加了join后则为最大值了,因为它会等待at线程执行完毕。
而join的本质是调用线程的wait()在当前线程的对象实例上。
当线程执行完成后,被等待的线程会在退出前调用notifyAll()通知所有等待线程继续执行。所以在应用程序中不要在Thread对象实例上使用wait()或者notify()等方法,这会影响系统API或被系统API影响。
public static native void yield();
这是一个静态方法,一旦执行,它会使当前线程让出CPU。但是有可能又被线程调度再次选中。
原文资料《实战Java高并发程序设计》