总览
- 多线程概述
- 多线程的实现方式
- 线程控制
- 线程的声明周期
- 线程同步
- 死锁问题
- 线程间通信机制(等待唤醒机制)
- 线程池
- 定时器
- 与Collection间关系
1. 多线程概述
什么是多线程?为什么要使用多线程?
了解多线程必须先了解线程,要了解线程,必须先了解进程。因为线程是依赖于进程存在的。
多进程:一边玩游戏,一边听音乐。(多进程的意义:充分利用计算机资源)
单核CPU:在同一时刻只能执行一条指令。系统在不同进程之间进行高速的切换(时间片,一般是毫秒级),给人类一种错觉,好像计算机在同一时刻只执行一个程序
并发:在同一时间段内,执行多个程序,在同一时刻只执行一个程序
并行:在同一时刻执行多个程序
Java命令会启动JVM,即启动了一个进程(JVM 就是一个进程)
该进程会启动一个主线程,然后主线程调用某个类的main方法,所以main方法都是运行在主线程里。(之前我们写的基本都是单线程程序 )
JVM是单线程还是多线程?
JVM是多线程的,至少会创建一个main线程和 一个GC线程。
几个JVM? 每个Java进程 分配一jvm个实例
Java 字节码 解释器。管理内存。
- 进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。 - 线程:
进程的执行路径(任务)
一个进程如果只有一条执行路径,则称为 单线程程序。
一个进程如果有多条执行路径,则称为多线程程序(意义:提高程序的执行率)。
为什么要用多线程:
①. 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;
②. 进程之间不能共享数据,线程可以;
③. 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
④. Java语言内置了多线程功能支持,简化了java多线程编程。
2. 多线程的实现方式
一、继承Thread
二、实现Runnable接口
三、线程池 (实现Callable接口,带返回值
//1. 写一个类继承Thread
public class MyThread extends Thread{
@Override
public void run() {
// super.run(); 没有做任何事情
// 如果只是一些简单的任务,没有必要新建一个线程。因为线程的创建和销毁要消耗一定的系统资源。
// 线程里面执行的往往是一些比较耗时的任务。
// System.out.println("Hello, Thread");
// 2. 重写run方法
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ": " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread:
线程是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
方法一:
1. 写一个类继承Thread。
2. 重写run()方法。
3. 创建子类对象
4. 启动线程
几个小问题:
1. 为什么要重写run()方法
把线程要执行的代码封装到run()里面。
2. 启动线程使用的是那个方法?
start()
3. 线程能不能多次启动?
不能多次启动,如果启动多次会报 IllegalThreadStateException。
4. run()和start()方法的区别
run(): 封装线程要执行的代码,直接调用相当于普通的调用,不会创建新的线程。
start(): 新建一个线程,该线程执行run().
*/
public class ThreadDemo1 {
public static void main(String[] args) {
// 3. 创建子类对象
Thread thread = new MyThread();
// 4. 启动线程
// 直接执行run()方法相当于简单的方法调用
// thread.run();
// 启动线程:start(), start()会调用系统的API创建一个新的线程,该线程会执行该对象的run()方法
thread.start();
thread.start();
for (int i = 100; i < 2000; i++) {
System.out.println(i);
}
}
}
如何获取线程的名字:
构造方法:
Thread(String name)
String getName()
void setName(String name)
问题:如何获取主线程的名字呢?
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
有了方式一,为什么还需要方式二呢?
1. 解决Java的单继承的局限性
2. 便于多线程共享数据
3. 将任务和线程分离,降低耦合性。
3. 线程的控制、调度
线程睡眠
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
static void sleep(long millis, int nanos)
在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行)线程加入
void join()
等待该线程终止
void join(long millis)
等待该线程终止的时间最长为 millis 毫秒。
void join(long millis, int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。线程礼让
static void yield()
暂停当前正在执行的线程对象,并执行其他线程
当前线程放弃执行权,重新加入抢夺CPU的执行权的行列。-
守护线程
void setDaemon(boolean on)
如果on为true, 将该线程标记为守护线程。
守护线程:被守护的线程如果结束了,那么守护也会结束。问题:
谁守护谁?
b.setDaemon(true), 将b为守护线程, 守护的对象:哪个线程运行这行代码,就守护那个线程。 线程中断
void stop()
中断线程, 直接将线程干掉,进入死亡。(上课睡觉,被AK扫射
public void interrupt()
中断线程。会抛出InterriptedException, 走的异常处理的机制,后面的代码还可以执行。(上课睡觉,被敲醒)-
线程的优先级
void setPriority(int newPriority)
更改线程的优先级int getPriority()
返回线程的优先级默认为5
public static final int MAX_PRIORITY 10
public static final int MIN_PRIORITY 1
public static final int NORM_PRIORITY 5 -
线程调度
- 分时调度(雨露均沾)
- 抢占式调度(Java)
4. 线程的生命周期
- 新建
- 就绪
- 运行
- 死亡
-
阻塞
- 一般阻塞
- 等待阻塞
- 同步阻塞
5. 线程的安全问题
电影院售票问题,引出线程安全问题。
如何解决线程安全问题?
- 同步代码块
synchronized() 实质上是锁机制 - 同步方法
synchronized- 普通成员方法:锁的对象是调用此方法的对象,即this
- 静态方法:锁的对象是字节码文件对象,class类对象
- 锁Lock接口
6. 死锁问题
死锁问题:
在同步代码块嵌套的时候,可能会出现多个线程相互等待的现象。
为了解决死锁问题,线程之间应该相互配合。
7. 线程间的通信机制
线程之间通信的机制(等待唤醒机制):
Object(锁):
void notify() 唤醒一个正在等待获取这个锁对象的线程(随机)
void notifyAll() 唤醒所有正在等待获取这个锁对象的线程
void wait() 当前线程进入阻塞状态,并释放锁对象,等待被唤醒。
void wait(long timeout) 当前线程进入阻塞状态,并释放锁对象,等待被唤醒。如果在这个时间内没有被唤醒,就自动醒来。
void wait(long timeout, int nanos)
思考,为什么不定义在 Thread 类中?
线程之间通信的媒介是锁对象,但是锁对象可以是任意对象。所以等待唤醒这些应该定义Object类中。
举个栗子:生产者 - 消费者模型
线程间还可以配合一起做某些事,如交替数数
8. 线程池
为什么会出现线程池?
启动一个新线程的成本是比较高的,因为它涉及到与操作系统进行交互。这种情况下使用线程池可以更好的提高性能,尤其当前程序需要创建大量的生存周期很短的线程时,更应该考虑线程池
概述
JDK5后出现的,养线程,当线程执行完任务后,不会销毁线程,而是在回到线程池中成为空闲状态,等待分配任务Executor:
void execute(Runnable command)-
Executors:
public static ExecutorService newCachedThreadPool()
// 创建一个默认线程池,都是临时工public static ExecutorService newFixedThreadPool(int nThreads)
// 创建一个指定数量初始线程的线程池,都是正式员工public static ExecutorService newSingleThreadExecutor()
// 创建单个线程的线程池,只养一个 -
ExecutorService(线程池) extends Executor:
- 提交任务:
void execute(Runnable command)
Future<T> submit(Callable<T> task)
Future<?> submit(Runnable task)
<T> Future<T> submit(Runnable task, T result)- 关闭线程池:
void shutdown()
启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
List<Runnable> shutdownNow()
试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
- 提交任务:
Callable<T>: 返回结果并且可能抛出异常的任务
T call()
Future<T>:
V get()
如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit)
如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
9. 定时器
Timer(定时器)、TimerTask(定时器任务)
概述
一种线程工具,以后台线程的方式去执行任务。可安排任务执行一次,或者定期重复执行。以后使用定时任务,常常用任务调度框架: quartz。而不会使用Timer。但其底层原理是一样的。方法
void schedule(TimerTask task, Date time)
// 在指定的时间执行指定任务,执行一次。
void schedule(TimerTask task, Date firstTime, long period)
// 在指定的时间第一次执行指定任务,每个多少毫秒,再重复的执行这个任务
void schedule(TimerTask task, long delay)
// 在指定延迟时间后,执行执行的任务,执行一次。
void schedule(TimerTask task, long delay, long period)
// 在指定延迟时间后第一次执行指定任务,每个多少毫秒,再重复的执行这个任务
void cancel()
// 终止此计时器,丢弃所有当前已安排的任务。TimerTask:
abstract void run() // 安排为一次执行或重复执行的任务。
10. Collection集合的线程安全
线程安全的集合:
Vector
HashTable
|-- Properties
StringBuffer
即使Vector是线程安全,但是我们也不使用它。
Collections:
static <T> List<T> synchronizedList(List<T> list) // 视图技术
static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
static <T> Set<T> synchronizedSet(Set<T> s)