一、多线程的实现方式
- 继承Thread类的方式进行实现
- 实现Runnable接口的方式进行实现
- 利用Callable接口和Future接口方式实现
| 优点 | 缺点 | |
|---|---|---|
| 继承Thread类 | 编程简单,可以直接使用Thread类中的方法 | 可以扩展性较差,不能再继承其他的类 |
| 实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程较为复杂,不能直接使用Thread类中的方法 |
| 实现Callable接口 | 同上 | 同上 |
二、常见的成员方法
| 方法名称 | 说明 |
|---|---|
| String getName() | 返回此线程的名称 |
| void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
| static Thread currentThread() | 获取当前线程的对象 |
| static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
| setPriority(int newPriority) | 设置线程的优先权 |
| final int getPriority() | 获取线程的优先级 |
| final void setDaemon(boolean on) | 设置为守护线程 |
| public static coid yield() | 出让线程 / 礼让线程 |
| public static void join() | 插入线程 / 插队线程 |
-
基本操作
-
守护线程
-
出让线程
-
插入线程
三、线程的安全问题
- 需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
- 安全问题:相同的票出现了多次、出现超出范围的票
- 原因:线程执行时,有随机性。
- 解决方法:同步代码块、同步方法、Lock锁
1. 同步代码块
把操作共享数据的代码锁起来。
synchronized(锁) {操作共享数据的代码}
特点:
- 锁默认打开,有一个线程进去了,锁自动关闭
- 里面的代码全部执行完毕,线程出来,锁自动打开
2. 同步方法
就是把synchronized关键字加到方法上
修饰符 synchronized 返回值类型 方法名(方法参数){...}特点:
- 同步方法是锁住方法里面的所有的代码
- 锁对象不能自己指定:非静态(this)、静态:当前类的字节码文件对象
3. Lock锁
- 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更加清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
- Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作,Lock中提供了获得锁和释放锁的方法。
void lock(): 获得锁
void unlock(): 释放锁- Lock是接口不能直接实例化,这里采用他的实现类ReentrantLock来实例化ReentrantLock的构造方法。
ReentrantLock(): 创建一个ReentrantLock的实例
四、死锁
1. 死锁产生的原因
- 互斥、不可剥夺、请求保持、循环等待。
- 常见的死锁产生的原因:锁嵌套。
2. 生产者消费者
| 方法名称 | 说明 |
|---|---|
| void wait() | 当前线程等待,直到被其他线程唤醒 |
| void notify() | 随机唤醒单个线程 |
| void notifyAll() | 唤醒所有线程 |
3. 等待唤醒机制(阻塞队列方式实现)
4.线程的状态
五、线程池
- 创建一个池子,池子中是空的
- 提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子。下回再次提交任务时,不需要创建新的线程,直接复用已有的线程
- 但是如果提交任务时,池子中没有空闲的线程,也无法创建新的线程,任务就会排队等待。
1. 线程池的代码实现
Excutors:线程池的工具类通过调用方法返回不同类型的线程池对象。
| 方法名称 | 说明 |
|---|---|
| public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池 |
| public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
2. 以前写多线程的弊端
- 用到线程的时候就创建
- 用完之后线程消失