JavaSE-多线程知识
1.线程、进程、程序的区别
- 程序是指令和数据的文件,被存储在磁盘或其它的数据存储设备中,也就是说程序是静态的代码。
- 进程是程序的一次执行过程,是系统运行程序的基本单位,程序是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
- 线程是比进程还要小的运行单位。一个进程最少要有一个线程。线程和进程最大的不同在于各进程基本上是独立的,每个线程有自己的程序计数器、虚拟机栈和本地方法栈。而各线程则不一定,因为同类的多个线程共享进程的堆和方法区资源,所以会相互影响。
- 进程有独立性和互斥性
线程有抢占式资源共享特性
2.什么是并发和并行
- 并发:同一时间片内轮流交替执行
- 并行:同一时间片内同时执行
3.创建线程的两种方式【jdk1.5之前】
1.继承Thread类
class MyThread extends Thread{
@Override
public void run(){
System.out.println("继承Thread类创建线程");
}
}
public class Demo{
public static void main(String[] args){
new MyThread().start();
}
}
2.实现Runnable接口
class MyThread implements Runnable{
@Override
public void run(){
System.out.println("实现Runnable接口创建线程");
}
}
public class Demo{
public static void main(String[] args){
new Thread(new Runnable()).start();
}
}
3.匿名内部类
public class Demo{
public static void mian(String[] args){
//匿名内部类:继承Thread类创建线程
new Thread(){
@Override
public void run(){
System.out.println("匿名内部类:继承Thread类创建线程");
}
}start();
//匿名内部类:实现Runnable接口创建线程
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("匿名内部类:实现Runnable接口创建线程");
}
}).start();
}
}
4.线程下面的方法
构造方法
Thread()分配一个新的Thread对象
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target, String name)分配一个新的Thread对象,并起名字
线程方法
static Thread currentThread()返回对当前正在执行的线程对象的引用
String getName()返回此线程的名称
void setName()将此线程的名称更改为等于参数name
int getPriority()返回此线程的优先级
void setPriority()更改此线程的优先级。设置优先并不一定优先,只是增加了执行的概率。最小值是1,最大值是10,默认是5
static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性
start()启动当前线程;调用当前线程的run()方法
run()通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
yield():释放当前cpu的执行权
join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
join()方法:join()在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
class Father implements Runnable {
@Override
public void run() {
Thread thread = new Thread(new Son());
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 100; i++) {
System.out.println("父线程:" + i);
}
}
}
class Son implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程:" + i);
}
}
}
public class Demo1 {
public static void main(String[] args) {
new Thread(new Father()).start();
}
}
5.实现线程同步的方式
同步方法
public synchronized void 方法名(){ 逻辑代码; }
同步代码块
synchronized(同步监视器){ 逻辑代码; }
6.死锁
-
什么是死锁
死锁是一种状态,当两个线程互相持有对方的资源的时候,却又不主动释放这个资源的时候。会导致死锁。这两个线程就会僵持住。代码就无法继续执行。
-
产生死锁必须具备以下四个条件
- 互斥条件:该资源任意一个时刻只由一个线程占用
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保
- 不剥夺条件:线程已获得的资源在未使用完之前不能被其它线程强行
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
class DeadLock implements Runnable{
private boolean flag;
private Object obj1;
private Object obj2;
public DeadLock(boolean flag, Object obj1, Object obj2){
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run(){
if(flag){
synchronized(obj1){
sout("拿到了锁1");
sout("等待锁2的释放...");
synchronized(obj2){
sout("->拿到了锁2")
}
}
}
if(flag){
synchronized(obj2){
sout("拿到了锁2");
sout("等待锁1的释放...");
synchronized(obj1){
sout("->拿到了锁1")
}
}
}
}
}
7.生命周期
新建状态------>就绪状态------>运行状态------>消亡状态
在就绪状态和运行状态中间有阻塞状态
运行状态------>阻塞状态【sleep()、wait()、join()、等待同步锁、suspend()】
阻塞状态------>就绪状态【sleep()时间到、notify()/notifyAll()、join()结束、获取同步锁、resume()】新建状态------>就绪状态【调用start()】
运行状态------>消亡状态【run()执行完毕】
8. Object类下面和线程有关的三个方法
-
这三个方法都涉及到线程通信
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器 notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个 notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
-
【面试题】sleep()和wait()的异同
- 相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
- 不同点:
- 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。而wait()必须使用在同步代码块或同步方法中
- 如果两个方法都是用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
9.生产者消费者模式
/**
* 生产者消费者模式【重点难点】
* 卖家:汽车厂商
* 买家:75名学生
* 张三想买一辆汽车,告诉汽车厂商我要买车。这个张三会进入到等待状态
* 等到汽车厂商造完以后,再通知张三来提车。如果汽车厂商有现车,张三就直接提车。
* 如果产品需要生产的话,消费者进入到阻塞状态
* 如果产品不需要生产的话,消费者直接购买
*/
class Goods {
private String name;
private double price;
private boolean isProduct;
public Goods(String name, double price, boolean isProduct) {
this.name = name;
this.price = price;
this.isProduct = isProduct;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public boolean isProduct() {
return isProduct;
}
public void setProduct(boolean product) {
isProduct = product;
}
@Override
public String toString() {
return "Goods{" +
"name='" + name + '\'' +
", price=" + price +
", isProduct=" + isProduct +
'}';
}
}
//生产者线程
class Producer implements Runnable {
private Goods goods;
public Producer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (goods) {
//isProduct为true 一直生产
if (goods.isProduct()) {
//造车,奇数造一种车, 偶数的话造另外一种车
if (count % 2 == 0) {
goods.setName("宝马");
goods.setPrice(200);
} else {
goods.setName("奔驰");
goods.setPrice(300);
}
System.out.println("生产者生产了:" + goods.getName() + ",价格为:" + goods.getPrice());
count++;
goods.setProduct(false);
goods.notify();
} else {
try {
System.out.println(1);
goods.wait();
System.out.println(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
//消费者线程
class Customer implements Runnable {
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true) {
synchronized (goods) {
//isProduct为false,不需要生产车,需要消费
if (!goods.isProduct()) {
System.out.println("消费者购买了:" + goods.getName() + ",价格为:" + goods.getPrice());
//购买完以后 商品没了 isProduct 是true 没有商品了
goods.setProduct(true);
goods.notify();
} else {
try {
System.out.println(3);
goods.wait();
System.out.println(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
/*
Goods goods = new Goods("大众", 100, false);
new Thread(new Producer(goods)).start();
new Thread(new Customer(goods)).start();
先进入生产者线程,isProduct=false,进入else分支,执行sout(1),执行wait(),生产者线程阻塞在这一行并释放锁。
消费者线程抢到锁,isProduct=false,进入if分支,执行sout(消费语句),修改标识,执行notify,
唤醒生产者线程[处于可运行状态],【此时消费者的锁没有释放】,继续消费者线程的while循环,此时isProduct=true,
进入else分支,执行sout(3),执行wait(),生产者线程阻塞在这一行并释放锁。生产者线程抢到锁,接着上次阻塞的地方继续运行,
执行sout(2)【此时生产者的锁没有释放】,继续生产者线程的while循环,此时isProduct=true,执行if分支,执行sout(生产语句)
修改标识,执行notify,唤醒消费者线程[处于可运行状态],【此时生产者的锁没有释放】,继续生产者线程中的while循环,此时isProduct=false
,进入else分支,执行sout(1),执行wait(),生产者线程阻塞在这一行并释放锁。。。。。。。。
1
消费者购买了:大众,价格为:100.0
3
2
生产者生产了:宝马,价格为:200.0
1
4
消费者购买了:宝马,价格为:200.0
3
2
生产者生产了:奔驰,价格为:300.0
1
4
消费者购买了:奔驰,价格为:300.0
3
2
*/
public class ProducerAndConsumer {
public static void main(String[] args) {
Goods goods = new Goods("大众", 100, false);
new Thread(new Producer(goods)).start();
new Thread(new Customer(goods)).start();
}
}
public class ProducerAndConsumer {
public static void main(String[] args) {
Goods goods = new Goods("大众", 100, false);
new Thread(new Producer(goods)).start();
new Thread(new Customer(goods)).start();
}
}
10.ThreadPoolExecutor 类分析
-
ThreadPoolExecutor类中第构造器
/** * ⽤给定的初始参数创建⼀个新的ThreadPoolExecutor。 */ public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
-
ThreadPoolExecutor 构造函数重要参数分析
- ThreadPoolExecutor3个最重要的参数:
- corePoolSize:核心线程数,线程数定义了最小可以同时运行的线程数量。
- maxmumPoolSize:当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。
- workQuene:当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中。
- ThreadPoolExecutor 其他常⻅参数
- keepAliveTime:当线程池中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime才会被回收销毁;
- unit:keepAliveTime参数的时间单位。
- threadFactory:exectuor创建新线程的时候会用到。
- hander:饱和策略。
- ThreadPoolExecutor3个最重要的参数:
-
饱和策略
ThreadPoolExecutor 饱和策略定义:如果当前同时运行的线程数量达到最大线程数量并且队列也已经被放满的时候,ThreadPoolTaskExecutor定义一些策略:
- ThreadPoolExecutor.AbortPolicy:抛出RejectExecutionException来拒绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。如果您的应用程序可以承受此延迟并且你不能丢弃任何一个任务请求的话,你可以选择这个策略:
- ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。
- ThreadPooExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求
举个例⼦: Spring 通过 ThreadPoolTaskExecutor 或者我们直接通过 ThreadPoolExecutor 的构造 函数创建线程池的时候,当我们不指定 RejectedExecutionHandler 饱和策略的话来配置线程池的 时候默认使⽤的是 ThreadPoolExecutor.AbortPolicy 。在默认情况下, ThreadPoolExecutor 将抛出 RejectedExecutionException 来拒绝新来的任务 ,这代表你将丢失对这个任务的处理。 对于可伸 缩的应⽤程序,建议使⽤ ThreadPoolExecutor.CallerRunsPolicy 。当最⼤池被填满时,此策略为我 们提供可伸缩队列。
11.线程池原理分析
public class Demo {
public static void main(String[] args) {
test();
}
public static void test() {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
//执行任务
for (int i = 0; i < 20; i++) {
int index = i;
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(index + "被执行,线程为:" + Thread.currentThread().getName());
}
});
}
threadPoolExecutor.shutdown();
}
}
通过代码输出结果可以看出:线程池每次会同时执⾏ 5 个任务,这 5 个任务执 ⾏完之后,剩余的 5 个任务才会被执⾏。
为了搞懂线程池的原理,我们需要⾸先分析⼀下 execute ⽅法。在 Demo 中我们使⽤ executor.execute(worker) 来提交⼀个任务到线程池中去,这个⽅法⾮常重要,下⾯我们来看看它的源码:
通过下图可以更好的对上⾯这 3 步做⼀个展示
我们在代码中模拟了 10 个任务,我们配置的核⼼线程数为 5 、等待队列容量为 100 ,所以 每次只可能存在 5 个任务同时执⾏,剩下的 5 个任务会被放到等待队列中去。当前的 5 个 任务之⾏完成后,才会之⾏剩下的 5 个任务。