本篇内容比较基础,适合初学者阅读。
从操作系统层面来说,进程是应用程序的动态执行过程,进程代表着一个程序的一次执行。进程同样也是操作系统资源管理和调度的基本单位。线程是对进程的进一步划分,一个进程可以有多个线程。线程是程序执行的最小单位。
当运行一个java程序时,系统会产生一个java进程。了解jvm的一定知道,每个java进程拥有独立的内存区域,包括堆、方法区、虚拟机栈等。
java进程中的线程共享堆等区域外,拥有属于自己的虚拟机栈。也就是说java中的线程除了共享进程内存空间外,还有自己的独立空间。
class MyThread extends Thread{
@Override
public void run(){
// 线程执行代码
}
}
通过继承Thread类并重写run方法来使用线程。编写好类后,只需要创建该类的实例对象,再调用start()方法即可启动线程。
MyThread t = new MyThread();
t.start();
class MyRunnable implements Runnable{
@Override
public void run(){
// 线程执行代码
}
}
Thread t = new Thread(new MyRunnable());
t.start();
通过实现Runnable接口以及重写run()方法使用线程。
因为Runnable接口只有一个方法,并且有注解@FunctionalInterface,所以可以用lambda表达式简化。
Thread t = new Thread(()->{
// 线程执行代码
});
t.start();
Runnable接口的run方法没有返回值,这在一些场景并不适用。所以在线程有数据返回式可以使用Callable创建线程。
FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 线程执行
return 2;
}
});
Thread t = new Thread(task);
t.start();
// 线程执行结束前在此阻塞
System.out.println(task.get());
通过实现Callable接口并创建FutureTask对象可以创建带有返回值的线程。
注意:在线程结束前调用FutureTask的get方法会导致调用者阻塞,直到线程结束并返回结果。
因为创建线程和释放线程都有一定的开销,而且过多的线程会降低代码可读性导致难以管理。线程池能够很好地解决这些问题。
一个线程池中能够维持多个线程,向线程池提交一个任务后,线程池会自动分配线程执行。这些线程不会因为任务完成而消亡,这样就避免了频繁创建和销毁线程,同时由于所有线程都集中在一个池中,这样也更利于管理。
// 单个线程 线程池,只有一个线程
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 固定大小线程池,参数规定了线程数量
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
// 缓存线程池,没有核心线程,每次创建新线程执行任务
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
这里暂时只介绍这三种最简单的创建方式,很明显常用的应该是FixedThreadPool。
在阿里巴巴的java编码规范中有提到尽量不使用Executors类创建线程,而是使用ThreadPoolExecutor创建。这里暂时不介绍ThreadPoolExecutor,它的详细介绍以及各种线程池的详细介绍将在另一篇文章展示。
// Runnable
fixedThreadPool.submit(new Runnable() {
@Override
public void run() {
// 线程执行代码
}
});
// Callable
Future<Integer> future = fixedThreadPool.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// 线程执行代码
return null;
}
});
System.out.println(future.get());
与直接创建线程类似,向线程池提交任务也分为有返回和无返回两种。分别实现Callable和Runnable接口即可。
使用Callable的时候要注意,submit方法会返回一个Future对象,通过这个Future对象的get方法可以获得到线程的返回结果。不过与FutureTask相同,如果线程没有执行完成,会导致get的调用者阻塞。
如上图所示,java的线程有6种状态,分别是新建(NEW)、可执行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、计时等待(TIMED_WAITING)、终止(TERMINATED)。
接下来将介绍每种状态,以及状态之间的转换。
新建状态是线程一开始的状态,在new Thread后线程将进入该状态。
该状态表示线程可以执行,即CPU可以调度该线程。在调用thread的start方法后线程会进入该状态。
其实RUNNABLE状态包含了两种状态,RUNNING和READY。RUNNING状态表示线程正在执行,READY状态表示线程可执行,在等待CPU调度。
这两种状态的转换如下(*):
阻塞状态时线程竞争锁失败后进入的状态,出现在synchronized竞争锁失败的时候。
等待状态与阻塞状态不同,阻塞是竞争锁失败导致的,而等待是主动进入的。比如调用了wait()、park()、join()等使线程停止的方法。
计时等待是在WAITING的基础上加入了超时机制。它除了通过notify等方式退出外还可以在计时到一定时间时退出。
常见的TIMED_WAITING有sleep()、wait(long timeout)、park(long timeout)、join(long timeout)等
当线程的run方法执行完成,或者主线程结束时,线程会进入终止状态。
Q:sleep和wait的区别
原文:https://www.cnblogs.com/xxjay/p/14986437.html