开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情
概念
- 程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
- 进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程,具有生命周期。
- 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 并行:多个CPU同时执行多个任务。
- 并发:一个CPU (采用时间片) “同时” 执行多个任务。
多线程是并发还是并行?
在CPU资源比较充足的时候,一个进程内的多线程,可以被分配到不同的CPU资源,这是并行;
在CPU比较繁忙,资源不足的时候,操作系统只为一个含有多线程的进程分配一个CPU资源,这是并发。
所以并行和并发都有可能,取决于操作系统分配的CPU。
线程的调度
同优先级线程使用时间片策略(简单来说就是执行a一会会,再执行b一会会)。
对高优先级,使用优先调度的抢占式策略:
线程创建时继承父线程的优先级。低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用。
getPriority():返回线程优先值setPriority(int newPriority):改变线程的优先级
线程的创建
方式一:继承Thread类
- 定义子类继承Thread类。
- 子类中重写Thread类中的
run()方法。 - 创建子类对象,即创建了线程对象。
- 调用线程对象
start()方法。
方式二:实现Runnable接口
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的
run()方法。 - 把子类对象作为参数传入给Thread类的构造器中。
- 调用Thread类的
start()方法。
相对于方式一的好处:
- 避免了单继承的局限性。
- 多个线程可以共享同一个接口实现类的对象,适合多个程序处理同一份数据。
方式三:实现Callable接口
-
定义子类实现Callable接口。
-
子类中重写
call()方法,可以有返回值。 -
把子类对象作为参数传给FutureTask的构造器中。
-
将FutureTask对象作为参数传入给Thread类的构造器中。
- FutrueTask是Futrue接口的唯一的实现类。
- FutureTask同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
-
调用Thread类的
start()方法。 -
通过FutureTask对象的
get()方法获取call()的返回值
相对于方式二的好处:
call()可以有返回值,可以通过FutureTask类获取返回结果,支持泛型。call()可以抛出异常。
方式四:线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors。
-
ExecutorService:线程池接口。常见子类ThreadPoolExecutor。
void execute(Runnable command):执行任务/命令,没有返回值,用来执行Runnable。<T> Future<T> submit(Callable<T> task):执行任务,有返回值,用来执行Callable。
-
Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池。
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池。Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池。Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池。
常用方法
static Thread currentThread():返回当前线程void start(): 启动线程,并执行对象的run()方法String getName():返回线程的名称void setName(String name):设置该线程名称static void yield():暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程join():当前线程调用其他线程的join()方法,当前线程将被阻塞,直到其他线程执行完为止static void sleep(long millis):睡眠millis毫秒数,睡眠时间内线程是阻塞状态
线程的生命周期
线程同步
-
同步代码块
- 自己指定锁(同步监视器)
-
同步方法
- 非静态方法的同步监视器是this
- 静态方法的同步监视器是当前类本身,.class
-
Lock锁
- 从JDK 5.0开始,可以通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- ReentrantLock 类实现了Lock,可以调用
lock()和unlock()进行加锁和释放锁
Lock和同步(synchronized)的对比:
Lock是手动开启和关闭锁;synchronized自动释放锁。
同步释放锁的情况:
- 同步方法、同步代码块执行结束。
- 同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
- 同步代码块、同步方法中出现了异常。
- 同步代码块、同步方法中执行了当前线程对象的
wait()方法,当前线程暂停,并释放锁。
同步监视器(锁)
- 任意对象都可以作为同步锁。
- 确保使用共享数据的多个线程共用一把锁。
死锁问题
不同的线程分别拿着对方的锁,都在等待对方释放自己需要的锁。
解决:尽量避免嵌套同步。
线程通信
wait():使当前线程进入阻塞状态,并释放锁。notify():唤醒被wait()的一个线程,如果有多个线程,则唤醒优先级高的。notifyAll():唤醒被wait()的所有线程。
以上3个方法定义在Object类中,必须在同步代码块或同步方法中。必须是锁调用,否则报IllegalMonitorStateException异常。
sleep() 和 wait()的异同:
相同点:都可以使当前线程进入阻塞状态。
不同点:
- Thread类中声明
sleep(),Object类中声明wait()。 wait()必须在同步代码块或同步方法中调用。wait()会释放同步监视器。
ThreadLocal
ThreadLocal是线程的局部变量,也就是说一个ThreadLocal对象只有自身线程可以访问。
-
Object get():获取该线程局部变量的值 -
void set(Object value):给该线程局部变量赋值。 -
public void remove():将当前线程局部变量的值删除。 -
protected Object initialValue():返回该线程局部变量的初始值。