什么是线程
- 现代操作系统在运行一个程序时,会为其创建一个进程。但是现代操作系统调度的最小单元是线程。
- 一个进程由多个线程组成,一个线程也可以创建线程。
- 线程拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。
- 处理器在这些线程上高速切换(时间片轮转法等调度算法),让使用者感觉到这些线程在同时执行。(Thread.sleep(1000),并不代表线程睡眠1000毫秒,有可能操作系统调度来不及执行此线程所以可能存在睡眠时间比1000毫秒多的情况)
什么是进程
- 进程是操作系统中运行的一个任务
- 进程中所包含的一个或多个执行单元成为线程。
- 进程拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。
- 线程只能归属于一个进程并且它只能访问该进程所拥有的资源。
进程与线程的区别
- 一个线程至少有一个线程,线程的划分尺度小于进程,使得多线程程序的并发度高。
为什么要使用多线程
- 更多的处理器核心
- 更快的响应时间
- 更好的编程模型
线程的使用场景
1.一个程序中需要同时完成多个任务
2. 使得多线程可以更快完成情况
并发的原理
多个线程“同时”运行只是感官上的一种表现。事实上是并发运行的。OS将时间划分为很多时间片段(时间片),尽可能均匀分配给每一个线程,获得时间片的线程会被CPU执行。而其他线程全部等待。但是时间片轮转的非常快。宏观上都运行,这种叫并发
线程状态
- NEW:初始状态,线程被构建,但是未调用start()方法。
- RUNABLE:运行状态,操作系统中的“就绪”,“运行”
- BLOCKED:线程因为某种原因放弃了cpu 使用权,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得时间片运行。阻塞的情况分三种:
1)等待阻塞:线程调用object.wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列,如果当前的线程拥有锁会释放锁。(object.wait(),等待阻塞)
2)同步阻塞:运行的线程在获取同步锁时,若该同步锁被别的线程占用,则会把该线程放入同步队列中**(锁竞争阻塞)**
3)其他阻塞:线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,该线程为阻塞状态。(睡眠,IO阻塞) - WAITING:等待状态,线程调用object.wait()线程进入等待状态,该线程需要等待其他线程做出通知或者中断做出相应操作
- TIME_WAITING:超时等待状态,不同于WAITING状态是在超过规定时间,还没有被通知或者中断则自行返回
- TERMINATED:线程已经执行完毕
创建线程的方式
- 继承Thread类
启动线程是调用start()方法,run方法时线程要执行的任务。当线程调用start()线程则进入RUNING,一旦获得cpu时间,run方法就会被调用
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
//或者内部匿名实现类
new Thread(){
@Override
public void run() {
while (true){
System.out.println("现在运行的线程是:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("现在运行的线程是:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 实现Runnable接口并重写run方法
public class ThreadDemo1 {
public static void main(String[] args) {
Thread runnableThread = new Thread( new Runnable(){
@Override
public void run() {
while (true){
System.out.println("现在运行的线程是:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
runnableThread.start();
}
}
- 实现Callable接口:可以得到线程执行的返回值,可以抛出异常
public class ThreadDemo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 将Callable封装到FutureTask
// FutureTask也是一种Runnable(也实现了Runnable)
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
// get方法会阻塞调用的线程
System.out.println("线程返回的值"+ futureTask.get());
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"starting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()+ "over...");
return sum;
}
}
Callable接口接受一个泛型作为接口中call方法的返回值类型。
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
创建实现Callable接口的对象,将其注册到FutureTask类中,然后将FutureTask类的实例注册进入Thread中运行。最后调用FutureTask中的get方法获取线程的返回值。
public class FutureTask<V> implements RunnableFuture<V>{
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
}
这里值得注意的是,使用callable的流程。FutureTask实现了RunnableFuture接口,RunnableFuture又继承了Runnable接口(接口可以继承接口)。
Thread类
无论怎样实现多线程,都需要调用Thread类中的start方法去启动线程。
Tread有一个构造方式是传入Runnable类型的参数,而FutureTask实现了 RunnableFuture接口,而 RunnableFuture 接口又继承了Runnable接口和 Funture接口,因此我们可以将FutureTask 的一个实例当做是一个Runnable接口的实例传入Thread来启动线程。
Thread类Runnable接口Callable接口以及FutureTask的关系图
- 继承Thread类
- 实现Runnable接口并重写run方法
- 实现Callable接口
守护线程
守护线程与前台线程在表现上没有区别。thread.setDaemon(true);即可开启守护线程。当进程里的所有前台进程结束时候,进程就会结束。即守护线程强制结束。GC垃圾回收即运行在守护线程上,当程序运行过程中会持续产生垃圾,当程序运行结束守护线程也运行完成。