线程概述
一个电脑运行的应用程序是一个进程,一个进程至少有一个线程,一个进程可以包含多个线程
Java的集合有线程安全与线程不安全的集合,线程安全指的是同步(排队执行,效率低但是安全),线程不安全指的是异步(同时执行,效率高但是数据不安全)
线程的实现方式分为并发与并行,并发指的是两个或多个事件在同一时间段内发生,并行指两个或多个事件在同一时刻发生
线程调度
1分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程占用CPU的时间
2抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度
创建新执行线程有三种方法。
第一种方法是将类声明为Thread的子类。
该子类重写Thread类的run方法。在run方法中设置线程任务。启动多线程:创建一个实现类对象,并且调用start()方法,来启动多线程
第二种方法是实现Runnable接口
实现步骤
1创建一个Runnable接口的实现类
2在实现类中重写Runnable接口的run方法,设置线程任务
3创建一个Runnable接口的实现类对象(也称为任务对象)
4创建Thread类对象,构造方法中传递Runnable接口的实现类对象(也可以描述为:创建了一个线程,并为其分配一个任务)
5调用Thread类中的start方法,开启新的线程,执行run方法(执行这个任务)
第三种方式-Callable(带返回值的线程)
Callable常用的方法
get():获取线程执行的结果
isDone():判断子线程是否执行完毕
task.cancel():取消Callable的任务执行
Thread类的常用方法
01-获取线程名称
获取线程的名称:(有两种方法)
1使用Thread类中的方法getName():使用它可以获取当前线程的名称
String getName() :返回值为String,返回该线程的名称
2可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
static Thread currentThread()
currentThread()方法,它是一个静态的方法,它的返回值就是一个Thread
02设置线程名称
两种方式:
1使用Thread类中的方法setName();
void setName(String name):改变线程名称,使之与参数name相同
2创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类Thread给子线程起一个名字
Thread(String name):分配新的Thread对象
03让线程睡眠:sleep()
public static void sleep(long millis):是当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),毫秒数结束之后,线程继续执行
04Thread类的常用方法-设置线程的优先级
setPriority(int newPriority):更改此线程的优先级
getPriority:获取此线程的优先级
线程可以拥有的最大优先级MAX_PRIORITY,还有最低优先级和默认优先级,可以去API中查看,这三个优先级可以通过setPriority()方法,控制线程抢到时间篇的几率
解决线程安全问题-线程同步技术
有三种方式完成同步操作:
1同步代码块
2同步方法
3Lock锁机制
1同步代码块
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步代码块就是一个代码块,一对大括号,里面放可能出现线程安全的代码,代码块前面要加关键字synchronized(同步的意思),后面加一个锁对象(同步锁),把对象锁住
2同步方法
定义一个方法,方法使用synchronized修饰,这就是同步方法,保证A线程执行该方法的时候,其他线程只能在方法外面等着
格式:
public synchronized void mthod( ){
可能会产生线程安全问题的代码
}
同步方法的安全锁是谁?
对于非static方法,同步锁就是this(谁调用方法,谁就是this)
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)
3Lock锁
Lock锁也叫同步锁,将加锁与释放锁方法话了,如下:
public void lock( ):加同步锁
public void unlock( ):释放同步锁
线程状态 | 导致状态发生条件 |
New(新建) | 线程刚被创建,但是并未启动。还没调用start方法 |
Runnable(可运行) | 线程可以在java虚拟机中运行,可能正在运行自己代码,也可能没有,这取决于操作系统处理器 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程状态变成Runnable状态 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep和Object.wait |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡 |
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,让自己放弃cpu的执行,进入到Waiting状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
1顾客和老板线程,必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
2同步使用的锁对象,必须保证是唯一的
3只有锁对象才能调用wait和notify方法
Object类中的方法:
void wait():在其他线程调用此对象的notify()方法或notifyAll()方法前,导致当前线程等待
void notify():唤醒在此对象监视器上等待的单个单元。唤醒后会继续执行wait()方法之后的代码
线程池Executor
线程池了解即可
未来开发中,我们主动使用线程池会特别少,因为后续我们作为javaEE开发工程师,后端程序本身就是基于多线程的,已经有池缓存的概念了,就不需要我们再加线程池了
线程池的概述
线程一般经历以下四个阶段:创建线程->创建任务->执行任务->关闭线程
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
1降低资源消耗
2提高相应速度
3提高线程的可管理性
Java中的四种线程池
1缓存线程池(非定长线程池)
2定长线程池
3单线程线程池(就一个线程)
4周期性任务定长线程池(定长线程池,它可以设置触发条件)
01缓存线程池
02定长线程池
03单线程线程池
04周期任务定长线程池
Lambda标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成:
一些参数
一个箭头
一段代码
格式:
(参数类型 参数名称) -> {代码语句}
格式说明:
( ):接口中抽象方法的参数列表:无参数则留空;多个参数则用逗号分隔
->:是新引入的语法格式,代表指向动作,传递的意思,把参数传递给方法体
{}:大括号内的语法与传统方法体要求基本一致,重写接口的抽象方法的方法体