进程是程序的一个运行实例;而线程则是CPU调度的基本单位。当前大部分的操作系统都支持多任务运行,这一特性让用户感到计算机可以同时处理很多事件。显然在只有一个CPU核心的情况下,这种“同时”是一种假象。它是操作系统采用分时的方法,为正在运行的多个任务分配合理的、单独的CPU时间片来实现的。假如当前系统中有五个任务,如果采用“平均分配”法,且时间片为10ms,则每个任务40ms才能执行一次,如果计算机的运行速度足够快的话,用户的感觉就是所有任务在同时运行。
在Java语言中所有调用了start()方法且还未执行结束的Thread类实例就代表了一个线程。Thread也是实现了Runable接口的“可执行代码”,Thread的创建方式有三种。
方式一:继承Thread的类
public class ThreadTest{
public static void main(String[] arg){
ThreadDemo thread=new ThreadDemo();
//调用start()创建线程
//创建线程后并不是立即执行多线程的代码,什么时候运行由操作系统决定
thread.start();
}
//定义Thread的子类并重写run()方法,run()方法的方法体就是线程要完成的任务
public class ThreadDemo extends Thread{
@Override
public void run() {
System.out.print("create a thred");
}
}
}
方式二:实现Runable接口
public class ThreadTest{
public static void main(String[] arg){
Thread thread = new Thread(new ThreadDemo());
thread.start();
}
//定义类并实现Runable接口,run()方法的方法体就是线程要完成的任务
public class ThreadDemo implements Runnable {
@Override
public void run() {
System.out.print("create a thred");
}
}
}
方式三:实现Callable接口
Callable接口是属于Executor框架中的功能类,它可以在任务结束后提供一个返回值,并且能够抛出异常,Runable接口不具备这样的能力。
public class ThreadTest{
public static void main(String[] arg){
ThreadDemo demo = new ThreadDemo();
//创建一个线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//提交任务,获取到Future对象,Future对象表示异步计算的结果,它提供了检查计算是否完成的方法
Future future = service.submit(demo);
try {
//调用get()方法获取运行结果,在调用get()方法的时候线程就会阻塞,直到call()方法执行结束
System.out.print(future.get());
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//定义类并实现Callable接口,call()方法的方法体就是线程要完成的任务
public class ThreadDemo implements Callable {
@Override
public Object call() throws Exception {
return "create a thread";
}
}
}
线程是一次性用品,当run方法执行完毕后就废弃,等着GC进行回收。因此线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException异常,这是一种运行时异常。多次调用start方法被认为是编程错误。
线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度和抢占式线程调度。
线程的执行时间由线程本身来控制,线程把自己的工作执行完成以后,要主动通知系统切换到另一个线程上。
优点:实现简单,线程的切换操作对线程本身来说是可知的,所以没有什么线程同步的问题
缺点:线程的执行时间不可控制,如果一个线程有问题一直不告诉系统进行线程切换,就会一直阻塞在那里。
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。
优点:线程的执行时间系统是可控的,也不会有一个线程导致整个进程阻塞的问题。Java使用的线程调度方式就是抢占式调度方式。
虽然Java线程调度是系统自动完成的,但是我们还是可以让系统给某些线程多分配一点执行时间,另外一些则可以少分配一点。这项操作可以通过线程的优先级来完成。当两个线程同时处于Ready状态时,优先级越高的线程越容易被系统选择执行。
public class ThreadTest{
public static void main(String[] arg){
Thread thread1 = new Thread(new ThreadDemo());
Thread thread2 = new Thread(new ThreadDemo());
//设置优先级,优先级越高的线程越容易被系统选择执行
thread1.setPriority(7);
thread2.setPriority(1);
thread1.start();
thread2.start();
}
//定义类并实现Runable接口,run()方法的方法体就是线程要完成的任务
public class ThreadDemo implements Runnable {
@Override
public void run() {
System.out.print("create a thred");
}
}
}
在Java语言中对于线程定义了6种状态,在任意的一个时间点,一个线程只能有且只有一种状态。
wait函数的原型如下:
public final void wait(long millis)
public final native void wait(long millis, int nanos)
public final native void wait()
可以看到调用wait方法的时候可以传递Timeout参数,因此wait函数的使用可以分为两种情况
和其他接口方法不同,这三个函数是由Object类定义的,也就意味着它们是所有类的共有函数。当某个线程调用一个Object的wait()方法以后,系统就要在这个Object中记录这个请求,因为调用者很可能不止一个,所以可以使用列表的形式逐个添加它们。当后期的唤醒条件满足时,Object既可以使用notify来唤醒列表中的一个等待线程,也可以使用notifyAll来唤醒列表中所有的线程。
值得注意的是调用者只有成为Object的monitor后,才能调用它的wait方法,而成为一个对象的monitor有以下3中途径:
interrupt方法可以用来请求中断线程,当一个线程调用interrupted方法时,线程的中断标识位会被置位,线程会不时的检测这个中断标识位,以判断线程是否应该被中断。如果线程正在被阻塞在某个Object的wait上,或者join()、sleep()方法中,那么线程会被唤醒,中断状态会被清除,同时抛出InterruptedException异常。
join方法有如下几个原型
public final void join()
public final void join(long millis, int nanos)
public final void join(long millis)
如下代码所示:
Thread thread1 = new Thread(runable1);
Thread thread2 = new Thread(runable2);
thread1.start();
thread1.join();
thread2.start();
以上的代码能够达到的目的是:只有当thread1线程执行完成以后,才会执行后面的thread2.start()。这样就保证了两个线程的顺序执行。如果是带有参数的join()则多了一个限制,即假如在规定时间内thread1线程没有执行完成,也会执行thread2.start(),以防线程无限等待从而拖垮程序。
这个方法是项目中使用最广泛的。wait是等待某个Object,而sleep则是等待时间,一旦设置的时间到了就会被系统自动唤醒。
public static void sleep(long millis)
public static void sleep(long millis, int nanos)
暂停当前线程,让出线程占用CPU执行时间。这个暂停时间是极其短暂的。如果在这时间内其他线程仍然没有抢占到CPU的执行时间,则当前的线程会重新抢占CPU的执行时间。
有的时候应用中需要一个长期驻留的服务程序,但是不希望其影响应用退出,就可以设置为守护线程,如果JVM发现只有守护线程存在的时候,将结束进程。
Thread thread=new Thread(test);
//必须在start()方法之前设置
thread.setDaemon(true);
thread.start();