Java线程基本概念及操作

进程和线程

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。线程可以理解为轻量级进程,是程序执行的最小单位。一个进程可以包含一个或者多个线程。

线程的生命周期如下所示:

新建线程

方式一:继承Thread类,然后重写里面的run()方法。然后new一个对象,调用start()方法。

Thread t1 = new Thread(){
    @Override
    public void run(){
        System.out.println("Hello world!");
    }
};
t1.start();

方式二:MyThread实现Runnable接口,然后将MyThread对象作为Thread的构造器入参,创建Thread对象,然后调用start()方法。

Thread t1 = new Thread(new Runnable(){
    @Override
    public void run(){
        System.out.println("Hello Runnable!");
    }
});
t1.start();

终止线程

通常来说,当我们启动一个线程后,都会让线程运行完成之后自动结束。可以通过Thread.stop()方法暴力终止线程,但是强烈不建议这样用。因为这样会造成程序的不可预测性,从而导致数据的不一致,而且不可恢复。

线程中断

线程中断是一种重要的线程协作机制。线程中断并不会使线程立即退出,而是给线程发送一个通知,告诉目标线程你可以退出了。其中主要包含以下3个API:

void Thread.interrupt();//中断线程
boolean Thread.isInterrupted;//判断是否被中断
static boolean Thread.interrupted();//判断是否被中断,并清除当前中断状态

当通知线程中断时,线程本身需要对中断做处理,否则对于线程的执行没有任何影响。

    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;
					}
					try {
						Thread.sleep(2000);
					} catch (Exception e) {
						System.out.println("Interrupted when sleep");
						//设置中断状态
						Thread.currentThread().interrupt();
					}
					Thread.yield();
				}
			}
		};
		t1.setName("hugege");
		t1.start();
		Thread.sleep(2000);
		t1.interrupt();
		System.out.println("main end");
	}

等待和通知

说到线程的等待和通知,对应的基础的两个方法就是wait()、notify()方法。这两个方法并不在Thread当中,而是在Object中。当一个线程调用了object.wait(),那么它就会进入到object对象的等待队列。当object.notify()方法被调用时,就会从这个等待队列中随机选一个线程进行唤醒操作。并且这个过程是完全随机的。

object还有一个notifyAll()方法,这个方法是唤醒所有的线程,然后让这些线程去竞争执行,获得监视器对象的才可以继续往下执行。这里面需要注意的是不论是wait()还是notify(),执行前都需要获取到当前对象的监视器,也就是获得当前对象的锁。下面给出一个demo:

/**
 * wait()和notify()使用
 * 
 */
public class SimpleWN {
	
	static final Object object = new Object();
	public static class T1 extends Thread {
		@Override
		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 (Exception e) {
					e.printStackTrace();
				}
				System.out.println(System.currentTimeMillis()+":T1 end!");
			}
		}
	}
	
	public static class T2 extends Thread {
		@Override
		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 (Exception e) {
				}
			}
		}
	}
	
	public static void main(String[] args) {
		Thread t1 = new T1();
		Thread t2 = new T2();
		t1.start();
		t2.start();
	}

}

注意:Object.wait()和Thread.sleep()都可以让线程等待一段时间,Object.wait()是释放锁的,而Thread.sleep()是不释放锁的。

挂起和继续执行

这里面说线程挂起和继续执行分别指的是:suspend()和resume()方法。这里面挂起操作,是不释放锁资源的。查看JDK的文档会发现,这两个API已经被标记为过期了。所以在这里就不详细的介绍了。

等待线程结束和谦让

等待线程结束调用的方法是Thread.join(),JDK中提供了两个join方法:

final void join() throws InterruptedException;
final synchronized void join(long millis) throws InterruptedException;

如调用了t1.join(),则当前线程会无限等待t1线程,直到t1执行完成。第二个方法则增加了一个超时时间,超过了时间之后,则继续往下执行。其中t1.join()可以理解为,让t1加入到当前线程的串行执行。附demo如下:

public class JoinMain {
	
	public volatile static int i = 0;
	public static class AddThread extends Thread {
		@Override
		public void run() {
			for(i = 0; i < 100000000; i++);
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		AddThread at = new AddThread();
		at.start();
		at.join();
		System.out.println(i);
	}

}

注意:t1.join()方法的本质是让主线程wait()在 t1 的对象实例上,当t1线程执行完成之后,会调用notifyAll(),从而唤醒主线程。所以在这里不要随意调用Thread.wait()和notify()方法,有可能扰乱join等方法的执行。

还有一个方法是Thread.yield(),这个方法是静态方法。Thread.yield()表示让当前线程让出CPU,然后再共同竞争CPU资源。这样只会增加别的线程执行的机会,而并不是当前线程一定不执行。