目录
多线程
同步和异步
程序,进程,线程
多线程的优势
并行,并发
线程优先级
线程优先级的获取和设置
线程调度
调度策略
线程分类
Thread类
Thread类的特点
Thread类的构造方法
Thread类的API
线程创建
JDK5.0之前的线程创建方式
方式一:继承于Thread类
方式二:实现Runnable接口
两种创建方式的异同
联系
相同点
不同点
JDK5.0新增的线程创建方式
方式三:实现Callable接口
如何理解实现Callable接口的方式创建多线程比实现Runable接口创建多线程方式强大?
联系
区别
方式四:线程池
线程池的API
线程生命周期
线程休眠
线程让步
线程串联
线程终止
线程同步
基本概念
多线程的安全问题
解决方案?
方式一:同步代码块(synchronized关键字修饰代码块)
方式二:同步方法(synchronized关键字修饰方法)
方式三:Lock(锁)
synchronized与Lock的异同
线程通信
sleep()和wait()的异同
练习:实现奇数和偶数交叉打印
练习:实现宠物:打工(消耗体力,消耗清洁度) 洗澡(增加清洁度) ?休息(增加体力)
练习:五个人买三张票
多线程
同步和异步
同步:当用户提交一个任务时,由于需要获取更多资源信息,任务不能立即完成,导致其他任务必须等待
异步:当用户提交多个任务时,每次只能执行一个任务,而当前任务由于需要获取更多资源信息,当前无法继续运行,那么保存好当前任务的状态,并切换开始下一个任务
程序,进程,线程
程序(program):用某种语言编写的,一组计算机能识别和执行的指令(一段可以保存在磁盘中的,静态的代码)。
进程(process):正在执行中的程序或程序的一次执行过程,在内存中产生该程序的实例,是一段动态的过程(资源分配的单位)。
线程(thread):一个进程中包含多个同时执行的任务,每个任务都称为一个独立的线程,是一段程序内部的一条执行路径(调度和执行的单位)。
- 每一个线程都有独立的运行栈和程序计数器
- 一个Java应用程序,至少存在三个线程:主线程,垃圾回收线程,异常处理线程,其中,垃圾回收线程和异常处理线程都是守护线程
?
多线程的优势
- 提高应用程序的响应速度
- 提高计算机系统CPU的利用率
- 改善程序结构(将既长又复杂的进程分为多个线程,独立运行)
并行,并发
- 并行:多个任务在一个CPU的多个内核或多个CUP中同时执行
- 并发:在单位时间内,一个CPU的一个内核中执行多个任务,每个任务交叉执行
线程优先级
- MAX_PRIORITY:10
- NORM_PRIORITY:5(默认优先级)
- MIN_PRIORITY:1
线程优先级的获取和设置
- getPriority():获取线程的优先级
- setPriority():设置线程的优先级
线程创建时会继承父线程的优先级。
概率上,高优先级的线程会抢占低优先级的线程的CPU执行权,但并不意味着只有当高优先级的线程执行完以后,低优先级的线程才能执行。
线程调度
调度策略
- 时间片(分时调度):线程轮流获得CPU的使用权,并平均分配每个线程占用的CPU时间片
- 抢占式(抢占式调度):高优先级的线程抢占CPU,如果线程的优先级相同,随机选择一个线程占用CPU
同优先级线程,组成队列(先进先出),使用时间片策略。
不同优先级线程,使用优先调度的抢占式策略。
线程分类
- 守护线程:一个线程为另一个线程服务,并且随着该线程终止而停止(垃圾回收线程)
- 用户线程
区别:JVM的退出。
如果当前的JVM中全是守护线程,那么JVM将会退出。
Thread类
Java语言的JVM通过java.lang.Thread 类来体现允许程序运行多个线程。
Thread类的特点
- 每一个线程都是通过某个特定Thread对象的run()来完成操作,通常把run()的主体称为线程体
- 通过Thread对象的start()启动对应的线程,而非直接调用run()
Thread类的构造方法
- Thread():创建新的Thread对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
- Thread(Runnable target, String name):创建新的Thread对象
Thread类的API
- Thread(String name):String name:线程的名字
- Thread(Runnable target):Runnable target:实现Runable接口的类的对象
- Thread(Runnable target, String name) :String name:线程的名字,Runnable target:实现Runable接口的类的对象
- static Thread currentThread():获取当前线程
- long getId():获取线程的ID
- String getName():返回线程的名称
- static void sleep(long millis):设置线程休眠,long millis:休眠的时间,单位:毫秒
- void setName(String name):设置线程的名字
线程创建
JDK5.0之前的线程创建方式
方式一:继承于Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类中的run(),线程任务声明于run()内
- 创建一个继承于Thread类的子类的对象
- 对象调用start()启动线程
方式二:实现Runnable接口
- 创建一个实现Runable接口的实现类
- 实现类中实现Runable接口中抽象方法run(),线程任务声明于run()内
- 创建实现类的对象
- 将实现类的对象作为参数,创建Thread类的对象
- Thread类的对象调用start()启动线程
- 调用run()只是普通的方法调用
- 线程的执行不仅仅取决于JVM,还依赖于OS,调用start()是通知JVM线程进入就绪状态,但会不会立即执行,需要JVM进一步通知OS,由OS内部来进行实现和分配
两种创建方式的异同
联系
Thread类实现了Runable接口
相同点
- 都需要重写run()
- 都将要执行的代码声明在run()中
- 都需要调用Thread类中的start()来启动线程
不同点
- 继承Thread类:线程代码存放Thread子类的run()中
- 实现Runnable接口:线程代码存在接口的子类的run()中
二者中,建议优先选择实现Runnable接口的方式创建
理由:
- 实现接口的方式没有类的单继承性局限
- 采用接口能够更好的实现数据共享
JDK5.0新增的线程创建方式
方式三:实现Callable接口
- 创建一个实现Callable接口的实现类
- 线程任务声明在call()内
- 创建Callable接口实现类的对象
- 将Callable接口实现类的对象作为参数,创建FutureTask类的对象
- 将FutureTask类的对象作为参数,创建Thread类的对象
- Thread类的对象调用start()启动线程
- 获取call()的返回值
如何理解实现Callable接口的方式创建多线程比实现Runable接口创建多线程方式强大?
联系
FutureTask类同时实现了Runable接口和Future接口。
FutrueTask是Futrue接口的唯一的实现类。
区别
- call()可以有返回值
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable接口支持泛型
方式四:线程池
线程池:一个容纳多个线程的容器。
- 创建线程池对象
- 创建Runable接口实现类对象或Callable接口实现类对象
- 关闭线程池
线程池的API
- corePoolSize:核心池的尺寸
- maximumPoolSize:最大线程数
- keepAliveTime:线程存活时间
线程池的优点
- 减少创建新线程的时间,提高响应速度
- 重复利用线程池中的线程,不需要每次都创建线程,降低资源消耗
- 便于线程的管理
线程生命周期
线程休眠
- 将正在运行线程从运行状态直接切换休眠状态,休眠时间到之后,线程切换为就绪状态
- static void sleep(long millis):设置线程休眠,long millis:休眠的时间,单位:毫秒? ? ? ?
线程让步
- 当前正在运行的线程调用了yield()方法之后,切换至就绪状态,然后继续获取时间片
- static void yield();
线程串联
- 当主线程调用子线程的join()方法时,意味着必须等子线程执行完毕之后,主线程才会开始执行
- void join();
- void join(long millis);
- void join(long millis, int nanos);
线程终止
- 调用stop方法。暴力终止,导致线程相关资源丢失,容易造成死锁
- 使用标识的方式终止线程
- 使用守护线程:void setDaemon(boolean on):on为true就是当前线程的守护线程
线程同步
基本概念
- 临界资源(共享数据):多个线程访问的同一个资源,该资源被多个线程所共享
- 互斥锁(同步监视器):俗称“锁”,任何一个类的对象都可以充当锁,当一个线程获取锁之后,那么其他线程无法访问该资源,只能等待锁被还回,获取锁之后,才能访问该资源
- 死锁(锁死):当多个线程访问临界资源时,由于其他原因导致获取锁的线程死亡或出现意外现象,导致锁没有还回,而其他线程又无法访问该资源
程序出现死锁现象后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续执行。
多线程的安全问题
- 多个线程执行的不确定性引起执行结果的不稳定
- 多个线程临界资源的共享,会造成操作的不完整性,会破坏数据
解决方案?
通过同步机制,来解决线程安全问题(即对操作临界资源的多线程,只能在其中一个线程获取临界资源并执行完毕释放后,其他线程才能获取并执行)。
方式一:同步代码块(synchronized关键字修饰代码块)
互斥锁与synchronized关键字联动。
用法:synchronized(互斥锁){ ? ? ? ?//需要被同步的代码; }
- 同步块尽量少包含一些代码,因为同步的方式虽然解决了线程的安全问题,但在操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率较低
- 同步块可以发生嵌套
- 同步代码块互斥锁:自己指定,this或类名.class
方式二:同步方法(synchronized关键字修饰方法)
如果操作临界资源的代码完整声明在一个方法中,可以将此方法声明为同步的。
用法:synchronized修饰方法? ? ? ? ? ? ? ? 等价于 ? ? ? ? ? synchronized(this){ ? ? ? ? ? ? ? //方法体的代码块 ? ? ? ? ? }?
- 非静态的同步方法,互斥锁:this
- 静态的同步方法,互斥锁:当前类本身
方式三:Lock(锁)
在JDK1.5以后,在并发包(java.util.concurrent)里面添加包locks,并提供Lock接口。
与synchronizedsynchronizedsynchronized的锁功能类似,但不同的是Lock需要手动开启锁和释放锁。
- java.util.concurrent.locks.Lock接口是控制多个线程对临界资源进行访问的工具
- ReentrantLock类实现了Lock接口,此类拥有与synchronized相同的并发性和内存语义
用法:
- 实例化ReentrantLock
- 调用lock():锁定方法
- 调用unlock():解锁方法
同步代码如果有异常,要将unlock()写入finally语句。
synchronized与Lock的异同
相同点
不同点
- synchronized是关键字,Lock是接口
- synchronized有代码块锁和方法锁,Lock只有代码块锁
- synchronized机制在执行完相应的同步代码块或同步方法后,会自动释放锁,而Lock需要手动的启动同步(lock()),结束同步也需要手动释放(unlock())
线程通信
wait(),notify(),notifyAll()都定义在java.lang.Object类中,都必须使用在synchronized代码块或synchronized方法中。
- wait():线程释放对象锁 进入阻塞等待池中
- notify():将阻塞等待池中的某一个线程唤醒
- notifyAll():将阻塞等待池中的所有个线程唤醒
sleep()和wait()的异同
相同点:一旦执行方法,都可以使当前线程进入阻塞状态。
不同点:
- 两个方法的声明位置不同:sleep()声明在Thread类中,而wait()声明在Object类中
- 两个方法的调用要求不同:sleep()可以在任何需要的场景中调用,而wait()必须使用在synchronized代码块或synchronized方法中
- 是否释放对象锁:如果两个方法都使用在synchronized代码块或synchronized方法中,sleep()不会释放锁,而wait()会释放锁
练习:实现奇数和偶数交叉打印
public class Odevity {
public static void main(String[] args) {
Num num = new Num(1);
Odd odd = new Odd(num);
Even even = new Even(num);
odd.start();
even.start();
}
}
class Num{
private int value;
public Num(int value) {
this.value = value;
}
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
class Odd extends Thread{
private Num num;
public Odd(Num num){
this.num = num;
}
@Override
public void run() {
while(true){
synchronized(num){
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(num.getValue() % 2 == 1){
System.out.println("奇数:"+num.getValue());
num.setValue(num.getValue()+1);
}
num.notifyAll();
try {
num.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Even extends Thread{
private Num num;
public Even(Num num){
this.num = num;
}
@Override
public void run() {
while(true){
synchronized(num){
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(num.getValue() % 2 == 0){
System.out.println("偶数:"+num.getValue());
num.setValue(num.getValue()+1);
}
num.notifyAll();
try {
num.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
练习:实现宠物:打工(消耗体力,消耗清洁度) 洗澡(增加清洁度) ?休息(增加体力)
public class QQpet {
public static void main(String[] args) {
Pet pet = new Pet(5,5);
Work work = new Work(pet);
Bath bath = new Bath(pet);
Rest rest = new Rest(pet);
work.start();
bath.start();
rest.start();
}
}
class Pet{
private int ph;
private int cl;
public Pet(int ph, int cl) {
this.ph = ph;
this.cl = cl;
}
public int getPh() {
return ph;
}
public void setPh(int ph) {
this.ph = ph;
}
public int getCl() {
return cl;
}
public void setCl(int cl) {
this.cl = cl;
}
}
class Work extends Thread{
private Pet pet;
public Work(Pet pet){
this.pet = pet;
}
@Override
public void run() {
while(true){
synchronized(pet){
try {
Thread.sleep(500);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(pet.getPh() > 0 && pet.getCl() > 0){
System.out.println("体力:"+pet.getPh()+" 清洁度:"+pet.getCl()+" 工作中!");
pet.setPh(pet.getPh()-1);
pet.setCl(pet.getCl()-1);
}
pet.notifyAll();
try {
pet.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Bath extends Thread{
private Pet pet;
public Bath(Pet pet){
this.pet = pet;
}
@Override
public void run() {
while(true){
synchronized(pet){
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(pet.getCl() == 0){
System.out.println("清洁度:"+pet.getCl()+" 洗澡中!");
pet.setCl(pet.getCl()+1);
}
pet.notifyAll();
try {
pet.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
class Rest extends Thread{
private Pet pet;
public Rest(Pet pet){
this.pet = pet;
}
@Override
public void run() {
while(true){
synchronized(pet){
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(pet.getPh() == 0){
System.out.println("体力:"+pet.getPh()+" 休息中!");
pet.setPh(pet.getPh()+1);
}
pet.notifyAll();
try {
pet.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
练习:五个人买三张票
public class BuyTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket(3);
One one = new One(ticket);
Two two = new Two(ticket);
Three three = new Three(ticket);
Four four = new Four(ticket);
Five five = new Five(ticket);
one.start();
two.start();
three.start();
four.start();
five.start();
}
}
class Ticket{
private int ticket;
public Ticket(int ticket) {
this.ticket = ticket;
}
public int getTicket() {
return ticket;
}
public void setTicket(int ticket) {
this.ticket = ticket;
}
}
class One extends Thread{
private Ticket ticket;
public One(Ticket ticket){
this.ticket = ticket;
}
@Override
public void run() {
synchronized(ticket){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ticket.getTicket() == 0){
System.out.println("车票售空!One没买到车票!");
Thread.interrupted();
}
if(ticket.getTicket()>0){
System.out.println("One已买到车票!"+" 票号:"+ticket.getTicket());
ticket.setTicket(ticket.getTicket()-1);
Thread.interrupted();
}
}
}
}
class Two extends Thread{
private Ticket ticket;
public Two(Ticket ticket){
this.ticket = ticket;
}
@Override
public void run() {
synchronized(ticket){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ticket.getTicket() == 0){
System.out.println("车票售空!Two没买到车票!");
Thread.interrupted();
}
if(ticket.getTicket()>0){
System.out.println("Two已买到车票!"+" 票号:"+ticket.getTicket());
ticket.setTicket(ticket.getTicket()-1);
Thread.interrupted();
}
}
}
}
class Three extends Thread{
private Ticket ticket;
public Three(Ticket ticket){
this.ticket = ticket;
}
@Override
public void run() {
synchronized(ticket){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ticket.getTicket() == 0){
System.out.println("车票售空!Three没买到车票!");
Thread.interrupted();
}
if(ticket.getTicket()>0){
System.out.println("Three已买到车票"+" 票号:"+ticket.getTicket());
ticket.setTicket(ticket.getTicket()-1);
Thread.interrupted();
}
}
}
}
class Four extends Thread{
private Ticket ticket;
public Four(Ticket ticket){
this.ticket = ticket;
}
@Override
public void run() {
synchronized(ticket){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ticket.getTicket() == 0){
System.out.println("车票售空!Four没买到车票!");
Thread.interrupted();
}
if(ticket.getTicket()>0){
System.out.println("Four已买到车票!"+" 票号:"+ticket.getTicket());
ticket.setTicket(ticket.getTicket()-1);
Thread.interrupted();
}
}
}
}
class Five extends Thread{
private Ticket ticket;
public Five(Ticket ticket){
this.ticket = ticket;
}
@Override
public void run() {
synchronized(ticket){
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
if(ticket.getTicket() == 0){
System.out.println("车票售空!Five没买到车票!");
Thread.interrupted();
}
if(ticket.getTicket()>0){
System.out.println("Five已买到车票!"+" 票号:"+ticket.getTicket());
ticket.setTicket(ticket.getTicket()-1);
Thread.interrupted();
}
}
}
}
|