线程的基本操作
新建线程
java中建立线程可以有两种方式,分别是继承Thread类和实现Runnable接口。
public static class CreateThread extends Thread{
@Override
public void run() {
System.out.println("Oh, I am CreateThread");
}
}
public class CreateThread implements Runnable {
@Override
public void run() {
System.out.println("Oh, I am Runnable");
}
}
开启线程
开启线程的方式分为两种start(),run()
Thread t1 = new Thread();
t1.start()
Thread t2 = new Thread();
t2.run()
两者的区别为:
start()是开启一个新的线程并执行run()函数,run()只在当前线程之中串行执行run()的代码。
终止线程
通常情况线程执行完毕后就会结束,不需要手动关闭,例如绝大部分的worker类型的线程。但是有些服务端的后台线程会常驻系统(死循环方式)。
JDK中提供了Thread.stop()方法,不过是一个废弃的方法,原因是:stop()方法过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。例如:Thread.stop()方法在结束线程的时候,会直接终止线程,并且会立即释放这个线程所持有的锁。而这些锁恰恰是用来维持对象一致性的。
更好的结束线程的方式是使用一个变量标识是否需要结束,或者使用中断的方式:
//每次都是执行完一个循环体之后再退出
//线程何时退出由程序员决定
public static class ChangeObjectThread extends Thread {
volatile boolean stopme = false;
public void stopMe(){
stopme = true;
}
@Override
public void run() {
while (true) {
if (stopme){
System.out.println("exit by stop me");
break;
}
//do something
}
}
}
//这里使用了中断判定来退出
public static class ChangeObjectThread extends Thread {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()){
System.out.println("exit with interrupted");
break;
}
//do something
}
}
}
线程中断
与线程中断有关的,有三个方法:
public void Thread.interrupt() //中断线程
public boolean Thread.isInterrupted() //判断是否被中断
public static boolean Thread.interrupted() //判断是否被中断,并清除中断标志,static
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run(){
while(true){
// if(Thread.currentThread().isInterrupted()){
// System.out.println("Interruted!");
// break;
// }
Thread.yield();
}
}
};
t1.start();
Thread.sleep(2000);
t1.interrupt();
}
在这里虽然对t1进行了中断,但是在t1中并没有中断处理的逻辑(注释部分),因此即使t1线程被设置了中断状态,但是这个中断不会发生任何作用。
等待(wait)和通知(notify)
为了支持多线程之间的协作,JDK提供了两个非常重要的接口线程等待wait()方法和通知notify()\notifyAll()方法。这两个方法并不是在Thread类中的,而是输出Object类。
这也意味着任何对象都可以调用这两个方法。
public class SimpleWN {
final static Object object = new Object();
public static class T1 extends Thread{
public void run()
{
synchronized (object) {
System.out.println(System.currentTimeMillis()+":T1 start! ");
try {
System.out.println(System.currentTimeMillis()+":T1 wait for object ");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(System.currentTimeMillis()+":T1 end!");
}
}
}
public static class T2 extends Thread{
public void run()
{
synchronized (object) {
System.out.println(System.currentTimeMillis()+":T2 start! notify one thread");
object.notify();
System.out.println(System.currentTimeMillis()+":T2 end!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
Thread t1 = new T1() ;
Thread t2 = new T2() ;
t1.start();
t2.start();
}
}
上述的代码如果去除掉synchronized块,代码将无法运行。因为出现了java.lang.IllegalMonitorStateException。
首先,这儿要非常注意的几个事实是:
1)任何一个时刻,对象的控制权(monitor)只能被一个线程拥有。无论是执行对象的wait、notify还是notifyAll方法,必须保证当前运行的线程取得了该对象的控制权(monitor)
2)如果在没有控制权的线程里执行对象的以上三种方法,就会报java.lang.IllegalMonitorStateException异常。
3)JVM基于多线程,默认情况下不能保证运行时线程的时序性
基于以上几点事实,我们需要确保让线程拥有对象的控制权。也就是说在T1中执行wait方法时,要保证T1对object有控制权;在T2中执行notify方法时,要保证T2对object有控制权。
挂起(suspend)和继续执行(resume)线程
suspend()和resume()是一对相反的操作,在JDK中已经是废弃的方法。不推荐suspend()去挂起线程的原因是因为suspend()在导致线程暂停的同时,并不会去释放任何资源。
更为严重的是如果resume()在suspend()之前就执行了,那么被挂起的线程可能很难有机会被继续执行。并且线程所持有的资源将不会被释放。
等待结束(join)和谦让(yield)
和字面意思一样。
线程组
public class ThreadGroupName implements Runnable {
public static void main(String[] args) {
//建立一个线程组
ThreadGroup tg = new ThreadGroup("PrintGroup");
//创建线程的时候将线程加入到线程组,使用了Thread的构造函数:
//public Thread(ThreadGroup group, Runnable target, String name)
Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
t1.start();
t2.start();
//线程组中活动线程的总数,但是由于线程是动态的,所以这个只是一个估计值
System.out.println(tg.activeCount());
//列出线程组中的线程
tg.list();
}
@Override
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName()
+ "-" + Thread.currentThread().getName();
while (true) {
System.out.println("I am " + groupAndName);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
守护线程
守护线程是一种特殊的线程,它是系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。与之相对应的是用户线程,用户线程可以认为j是系统的工作线程。如果用户线程全部结束,这也意味这这个程序实际上无事可做了。守护线程要守护的对象已经不存在了,那么整个应用程序就自然应该结束。因此,当一个Java应用内,只有守护线程的时候,Java虚拟机就会自然退出。
简单看下守护线程的使用:
public class DaemonDemo {
public static class DaemonT extends Thread{
public void run(){
while(true){
System.out.println("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t=new DaemonT();
//必须在start()之前设置为守护线程
t.setDaemon(true);
t.start();
Thread.sleep(2000);
}
}
注意:守护线程Thread.setDaemon(true)必须要在线程start()之前设置,否则抛出java.lang.IllegalThreadStateException。设置无效,也不影响原来线程作为工作线程继续工作
线程优先级
在java中,使用1到10表示线程优先级(数字越大优先级越高)。一般可以使用Thread类中定义的三个静态标量来表示:
线程优先级
Thread.setPriority()例子