文章目录
线程进程的概念
程序的概念: 是为完成特定任务,用某种语言编写的一组指令的集合,简单来说就是我们写的代码
进程的概念:
- 进程是指运行中的程序,比如我们使用 QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系统将为迅雷分配新的内存空间
- 进程正在运行的一个程序。是动态过程,有它自身的产生,存在和消亡的过程
线程
- 线程由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
比如你开了个进程,你有下载任务1,那就是一个线程。还有下载任务2,就是线程2
最大那个就是进程,下面7个是它的线程
单线程:同一个时刻,只允许一个线程
多线程:同一个时刻,可以执行多个线程,比如:一个 qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发:同一个时刻,多个任务交替执行,造成一种 “貌似同时” 的错觉,简单的说,单核 CPU 实现的多任务就是并发
并行:同一时刻,多个任务同时进行。多核 CPU 可以实现并行,也可能并发和并行同时进行
线程状态转换图
这里直接拿了B站韩顺平老师的图
- New(新建)
新建一个线程对象
- Ready(就绪状态)
线程调用 start 方法,就变为就绪状态
- Running(运行状态)
线程被调度,从就绪状态变为运行状态
- Timed_Waiting(第一种等待状态)
有期限等待,如果线程在指定的时间范围内没有被其他线程唤醒(例如,通过notify()或notifyAll()方法),那么它会超时并自动停止等待,继续执行后续的代码。
- Waiting(第二种等待状态)
无期限等待,只有当其他线程调用了同一个对象的notify()或notifyAll()方法来唤醒它时,该线程才会从等待状态恢复,继续执行后续的代码。
- Blocked(阻塞状态)
阻塞状态是指线程因为某些原因无法继续执行,需要等待其他条件满足才能继续执行。例如,当一个线程调用了某个对象的同步方法或同步代码块时,如果锁被拿了,那就先阻塞,直到锁被释放,才转就绪状态
- Terminated(退出状态)
下面说一下什么时候锁会释放:
- 同步方法或代码块执行完毕
- 同步方法或代码块中遇到 break, return
- 同步方法或代码中出现了未处理的 Error 或 $Exception,导致异常结束
- 同步方法或代码块中执行了 wait() 方法,线程暂停了,会释放锁
注意:sleep(), yield() 不会 释放锁
线程的使用
注意执行 main 方法也是开一个线程
直接继承Thread 方式
public class Main {
public static void main(String[] args) {
//创建对象
Test test = new Test();
//开启线程
test.start();
}
}
class Test extends Thread {
@Override
public void run() {
System.out.println("Crazy!!!");
}
}
实现 Runnable接口 方式
public class Main {
public static void main(String[] args){
//创造对象
Test test = new Test();
//创建线程代理(底层是静态代理模式)
Thread thread1 = new Thread(test);
//启动线程
thread1.start();
}
}
class Test implements Runnable {
@Override
public void run() {
System.out.println("Crazy!!!");
}
}
实现 Callable 接口 方式
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创造对象
Test test = new Test();
//用于管理多线程运行的结果
FutureTask<Integer> ft = new FutureTask<>(test);
//启动线程
Thread thread = new Thread(ft);
//线程启动
thread.start();
//利用 ft 获取返回值
Integer num = ft.get();
System.out.println(num);
}
}
class Test implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("Crazy!!!");
return 10;
}
}
总结:
- 由于 java 是单继承的,所以 继承了其他类就不能继承 Thread 类了
- Runnable 接口更加适合多线程共享一个资源的情况,而且避免了单继承的限制
- 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 void yield():暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程
-
public static void join():等待其他线程终止,先进入等待,直到另一个线程运行结束,才由阻塞转为就绪状态
-
interrupt:中断线程,但没真正中断,一般用于中断正在休眠的线程
-
notify():唤醒等待的单个线程
-
notifyAll():唤醒等待的所有线程
setPriority
取值范围为 1~10,
yield
让当前运行的线程回到就绪状态,这时候所有优先级一样的线程重新争夺运行权概率还是一样的,所以往往有时候不能让步
interrupt
发送中断信号,让线程在等待状态的时候可以抛出异常结束线程
public class Main {
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("h" + i);
}
//这里直接让下面睡眠20000毫秒抛出异常,直接中断
test.interrupt();
}
}
class Test extends Thread {
@Override
public void run() {
while(true) {
for (int i = 1; i < 5; i++) {
System.out.println("hello");
}
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println("等待状态被interrupt");
}
}
}
}
join
插队,先执行插入的线程所有任务。
public class Main {
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.start();
for (int i = 0; i <= 20; i++) {
Thread.sleep(1000);
System.out.println("主线程:" + i);
if (i == 5) {
System.out.println("主线程 礼让 子线程");
/*
这里主线程礼让子线程,子线程执行完毕,才轮到主线程执行
*/
test.join();
System.out.println("主线程继续执行");
}
}
test.interrupt();
}
}
class Test extends Thread {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("子线程: " + i);
}
}
}
setDaemon
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束,就是所有非守护线程
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束,常见的守护线程 就是 垃圾回收机制,特点是当所有
public class Main {
public static void main(String[] args) throws InterruptedException {
T t = new T();
//如果我们希望当main线程结束,子线程可以自动结束
//只需设置成守护线程
t.setDaemon(true);
t.start();
for (int i = 1; i <= 5; i++) { //main线程
System.out.println("HEHE");
Thread.sleep(1000);
}
}
}
class T extends Thread {
@Override
public void run() {
for (; ;) {
try {
Thread.sleep(500); //休眠50毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Crazy!!!");
}
}
}
线程的同步机制
因为有时候,不同步可能会出问题,比如三个线程同时卖票,他们可能同时都卖出一张票,但是,票总数量才减少一张,实际减少三张这种问题
当由一个拿到锁,其他线程就会被阻塞,直到锁释放,他们才会重新回到就绪状态
同步代码块实现:
锁对象一定要保证唯一:一般可以用 对象名.class 字节码,有些例子会用 static Object obj = new Object()
public class Main {
public static void main(String[] args) {
SellTicket sellTicket03 = new SellTicket();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
synchronized (SellTicket.class) {
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
}
同步方法实现
锁对象不能自己指定
- 非静态:this
- 静态:当前类的字节码文件对象
public class Main {
public static void main(String[] args) {
SellTicket sellTicket03 = new SellTicket();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;//控制run方法变量
//1.同步方法
//2.这时锁在 this 对象
public synchronized void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一共同步方法
}
}
}
lock 锁实现
- 创造一个锁
- 上锁
- 解锁
可以手动指定上锁位置,释放锁的位置
public class Main {
public static void main(String[] args) {
SellTicket sellTicket03 = new SellTicket();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket implements Runnable {
Lock lock = new ReentrantLock();//造锁
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public void run() {
while (true) {
lock.lock(); //上锁
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
lock.unlock(); //解锁
}
}
}
注意这里 break 和 return 不会 解锁,所以要注意 unlock
线程池
不用线程池的弊端
用到线程就创建,用完就消失
线程池原理
- 创建一个池
- 提交任务时,线程池会创建新的线程对象,任务执行完毕,线程归还线程池,下次再次提交任务时,不需要再创建新的线程,直接复用已有的线程
- 但是如果提交任务时,池中没有空闲线程,也无法创建新的线程,任务就会排队等待
系统的线程池
创造线程池
- public static ExecutorService new CachedThreadPool():创建一个没有上限的线程池
- public static ExecutorService newFixedThreadPool(int nThreads):创建有上限的线程池
public class Main {
public static void main(String[] args) throws InterruptedException {
//1. 获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2. 提交任务:执行完任务会还回去
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
//3. 销毁线程池
//pool1.shutdown();
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
//for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" /*+ i*/);
//}
}
}
自定义线程池
构造方法的参数
- 核心线程数:不能小于0
- 线程池中最大线程数量(包括临时线程):最大数量 >= 核心线程数
- 空闲时间(值):不能小于0
- 空闲时间(单位):用 TimeUnit 指定
- 阻塞队列:不能为 null
- 创建线程的方式:一般用工具类的方法Executors.privilegedThreadFactory(),//创建线程工厂
- 执行的任务过多时的解决方案 不能为null
核心线程都在占用,且队列已经满了,就会创建临时线程,临时线程的数量就是 核心线程数量 - 核心线程数
public class Main {
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3, //核心线程数量
6, //最大线程数量
60,//空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3)/LinkBlockingQueue<>()//任务队列,Array指定长度,Link的无限增长
Executors.privilegedThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略/静态内部类
);
}
}
阻塞队列
实现类
ArrayBlockingQueue:
底层时数组,有界,要指定长度
LinkedBockingQueue
底层时链表,最大值为 int 的最大值
线程池多大合适
- CPU 密集型运算:最大并行数 + 1
- I/O密集型运算:
最大并型数 * 期望的 CPU 利用率 * 总时间(CPU时间 + 等待时间)/ CPU 计算时间
用 thread dump 工具可以查 cput 计算时间和等待时间