Java 线程的一些基本知识
线程
1. 并发与并行
- 并发:只两个或者多个事件在同一个时间段内发生。
- 并行:只两个或者多个事件在同一个时刻发生(同时发生)。
2.线程和进程
-
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
3. 多线程实现的两种方式
多线程实现的两种方式:
-
① 自定义一个线程类, extends Thread,重写 run 方法,创建自定义的线程对象,开启线程 start()。
-
② 自定义一个任务类, implements Runnable,重写 run 方法,创建自定义的任务对象,通过任务对象, 构造一个线程对象,开启线程 start()。
或者以上两种方式的匿名内部类改写,均可实现多线程。
public class ThreadDemo01 {
public static void main(String[] args) {
//继承
MyThread01 myThread01 = new MyThread01();
myThread01.start();
//接口实现类
MyThread02 task = new MyThread02();
Thread t = new Thread(task);
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("main:" + i);
}
}
}
public class MyThread02 implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("接口多线程:" + i);
}
}
}
public class MyThread01 extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("继承多线程:" + i);
}
}
}
注意:不是哪个线程先start, 就先执行哪个线程, 线程的执行顺序, 是不固定的。
4. 线程常用方法
线程Thread的构造方法:
1.new 自定义线程类(): 自定义类的构造方法, 随意
2.new Thread(): 无参构造器
3.new Thread(String): String->指定的线程名
4.new Thread(Runnable): Runnable->线程任务
5.new Thread(Runnable, String): Runnable->线程任务, String->指定的线程名
线程Thread常用API:
1.static Thread currentThread(): 获得当前正在执行的线程对象
2.String getName(): 获得线程对象的名字, 线程在创建时可以指定名字, 也可以默认分配名字
3.int getPriority(): 返回此线程的优先级
void setPriority(int): 设置线程的优先级
优先级: 1~10
改变CPU分配时间片的概率
4.boolean isDaemon(): 测试这个线程是否是守护线程
void setDaemon(boolean): 设置这个线程是守护线程
守护线程 - 前台线程
当所有的前台线程结束, 守护线程也会自动结束
GC 就是守护线程
5.static void sleep(long): 线程休眠指定时间
会有一个已检查异常, 所以必须要 try-catch
6.void join(): 等待调用这个方法的线程结束, 再继续后续代码
会有一个已检查异常, 所以必须要 try-catch
7.static void yield(): 主动放弃cpu的时间片
5. 线程同步
5.1 同步代码块
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
synchronized使用原则: 尽量将少的代码加锁,可以只将一部分代码加锁, 同步代码锁, 需要借助一个对象。
加锁的对象: 可以是任意对象, 只需要确保多个同步的线程, 对象唯一就可以了。
通常使用this对象。
对象锁: 一个对象只能加一把锁, 并且只能同时被一个线程持有。
public void saleTicket() {
// synchronized (this) {
synchronized (object) {
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
count--;
}
System.out.println(Thread.currentThread().getName()+"卖完一张票");
}
}
5.2 同步方法
在方法上加锁, 称为同步方法锁。锁在方法上, 实际上就是在this对象上加锁
public synchronized void saleTicket() {
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName()+"正在出票: " + count);
count--;
System.out.println(Thread.currentThread().getName()+"卖完一张票");
}
5.3 Lock锁
实现类: ReentrantLock lock = new ReentrantLock();
加锁: 锁对象.lock();
解锁: 锁对象.unlock();
private ReentrantLock lock = new ReentrantLock();
public void saleTicket() {
// 加上锁
lock.lock();
if (count == 0) {
throw new RuntimeException("票卖完了!");
}
System.out.println(Thread.currentThread().getName() + "正在出票: " + count);
count--;
// 打开锁
lock.unlock();
System.out.println(Thread.currentThread().getName() + "卖完一张票");
}
6. 线程状态
线程状态 | 导致状态发生的条件 |
---|---|
NEW(新建) | 线程刚被创建,但是未启动。还没有调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟中的运行的状态,可能正在运行自己的代码,也可能没有,这取决于操作系统的处理器。 |
Block(锁阻塞) | 当一个线程视图获取一个对象锁。而该对象被其他的线程所持有,则该线程进入Block状态;当该线程持有锁时,该线程变成Runable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的方法一样Thread.sleep、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者英文没有捕获的异常终止了run方法而死亡。 |
线程通信: 共享资源
wait() 和 notify()
notify() - 每次只能唤醒一个线程, 只能唤醒等待时间久的那个线程
notifyAll() - 唤醒所有正在等待的线程
wait() -> 只能被notify() 或者 notifyAll() 唤醒
wait(long) -> 到时间以后, 自动醒来
7. 线程池
7.1 线程池的概念
一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。
7.2 线程池的好处
-
① 降低资源消耗,减少了创建和销毁线程的次数,每个工作线程都可以重复利用。
-
② 提高响应速度,当任务到达时,任务可以不需要等创建线程就可以立即执行任务。
-
③ 提高线程的可管理性,可以根据系统的承受能力,调整线程池中工作线程的数量,防止消耗过多的内存
7.3 线程池的工作原理
-
① 线程池判断核心线程池里的线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务,如果核心线程池里的线程都在执行任务,则进入下一个流程。
-
② 线程池判断工作队列是否已满,没满,则将新提交的任务存储在这个工作队列里,满了,进入下一个流程。
-
③ 线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。
7.4 线程池的常用方法
newCachedThreadPool(): 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
newFixedThreadPool(int nThreads): 创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
newScheduledThreadPool(int corePoolSize): 创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。
newSingleThreadExecutor(): 创建一个使用从无界队列运行的单个工作线程的执行程序。