初始多线程
程序
- 按照一定的逻辑编写的代码,存储到文件中,文件存放到磁盘---静态状态
进程
- 字面理解-正在进行中的程序
- 资源:内存+CPU
- 程序从启动到结束的过程称为进程
- cpu在某一时刻只能执行其中一个进程,而cpu在做告诉切换动作
- 每个进行执行时间不固定,多个进程抢夺cpu的执行权
线程
-
属于进行,是进行中一个可以独立运行执行单元(执行顺序,执行路径、代码片段)
-
一个进程最少有一个线程,一个进程有多个可以独立运行的执行单元---多线程
-
代码之所以是单线程,以为只有一个执行单元
-
执行单元都放在main方法中---主线程
-
实现效果
- 一个进程中,出现多个执行路径,各自执行各自的
-
cpu进行切换:实则切换的各个进程中的线程
-
在main中创建其他线程--子线程
-
子线程要运行的代码封装到run方法中
多线程目的
-
提高cpu使用率,提高程序执行效率
-
注
- cpu有极限,不能无限开启线程
并行
- 前提:多个cpu
- 多个任务同时发起,在某个时刻,每个任务都在执行
并发
- 单个cpu,多个任务同时发起,在某个时刻,一个任务在执行,其他任务等待cpu
创建线程
java是面向对象思想,任何事物都可以叫做对象,因此线程也是一类事物,就会有对应的对象来进行描述,java.lang.Thread类。
创建线程方式
-
1、继承Thread类
-
步骤
- 1.定义类继承Thread,子类就是一个线程类
-
2.重写run方法
-
3.创建子类对象
-
4.启动线程,调用start方法
-
-
2、实现接口Runnable
-
步骤
- 1.定义类实现Runnable接口,重写接口中的run方法 (run方法中就是线程任务)
-
2.创建实现类对象
-
3.创建Thread对象,将实现类对象作为参数传给Thread的构造方法
-
4.调用start方法,启动线程
-
-
3、实现接口Callable
-
步骤
- 1.定义类实现Callable ,重写call方法,方法的返回值是V类型 (线程任务)
-
2.创建实现类对象
-
3.创建FutureTask对象,并将实现类对象作为参数传递给构造方法
-
4.创建线程对象,并将FutureTask对象作为参数传递给构造方法
-
5.调用start方法,启动线程
-
FutureTask实现了Runnable接口,类中提供get方法,可以获取call方法的返回值,即获取线程执行后返回的结果,还又判断线程任务状态的方法,如:isDone()...
-
-
三种方式区别
-
继承Thread类
-
代码简单
-
因单继承原因,继承此类不能继承其他类
-
线程任务和线程对象绑一起,耦合度增高
-
原理
- start-->start0--->run 由于子类重写了run方法,因此创建子类对象调用run方法,运行时子类的内容(方法重写)
-
-
实现Runnable
-
线程任务和线程对象是分离的,降低耦合度
-
实际开发建议使用此方式
-
原理
- start-->start0-->run(Thread类中的run),方法中:target是Runnable类型,创建Thread对象时,给target变量赋值了,是Runnable的实现类对象,因此方法中的判断是true,多态形式调用方法,编译看父类运行看子类。
-
-
实现Callab好处
-
任务有返回结果,可以抛出异常
-
基本与Runnable类似
-
原理
- start-->start0-->run(Thread类中的run),方法中:target是Runnable类型,创建Thread对象时,给target变量赋值了,是Runnable的实现类对象, 是FutureTask,因此方法中的判断是true,多态形式调用方法,编译看父类运行看子类,执行的是FutureTask中的run方法。在该方法中,
-
-
常用方法
-
String getName()
-
获取线程名
-
线程默认格式
- Thread-编号,编号是从0开始
-
主线程默认的名称
- main
-
-
Thread static currentThread()
- 返回当前正在执行的线程对象的引用
-
void setName(String name)
-
将线程名称设置为name
-
线程启动前或启动后去设置都可以
-
构造方法也可给线程设置名称
- new Thread(String name)
- new Thread(Runnable run,String name)
-
-
static sleep(long millis)
- 当前线程休眠millis时间
- 主动放弃执行资格,cpu不用调度
- 时间到,线程醒过来,恢复执行资格,等待cpu调度
- 1000毫秒=1秒
-
int getPriority()
- 获取线程的优先级
- 每个线程在创建时都有一个优先级,默认是5,一共10个级别,1最小,10最大。
- 优先级大的线程,被执行的概率更高
-
void setPriority(int)
-
设置线程优先级
-
常量
-
MAX_PRIORITY
- 线程可以具有的最高优先级
-
MIN_PRIORITY
- 线程可以具有的最低优先级
-
NORM_PRIORITY
- 分配给线程的默认优先级
-
-
-
isDaemon()
- 判断线程是否是守护线程
- 守护线程也叫后台线程,为前台线程(非守护线程)提供服务
- 守护线程是依赖于前台线程而存在的,前台线程结束守护线程也会随之结束
- 默认线程是非守护线程
-
setDaemon(true)
- 设置线程为守护线程,在线程前去设置
-
interrupt()
- 中断线程执行,想要中断哪个线程,必须在其另一个线程去调用
-
static void yiele()
- 让步的意思。某个线程指向到该方法,意思是把cpu的执行权让出来,让其他线程或自己优先执行。注意:调用后有可能该线程会得到cpu的执行权也能得不到。
线程安全
线程安全问题产生
- 多线程执行时,共同操作同一个资源数据, 其中一个线程对共享资源数据的一次计算还没有执行完还没有完成,另一个线程参与进来执行,从而导致错误数据的产生
- t1刚执行完判断和打印,没有执行减减计算,t2获取cpu执行权执行if和打印...
线程安全问题解决
-
1、同步代码块
-
synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
-
格式
- synchronized(同步锁){
需要同步操作的代码
}
- synchronized(同步锁){
-
同步锁
- 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
-
- 锁对象 可以是任意类型。
-
- 多个线程对象 要使用同一把锁。
- 3、至少两个或两个以上线程
-
执行流程
- lock是一个对象,这个对象有两种状态:开和关,默认状态是开。
- 某个线程获取到执行权,执行到synchronized该语句,会先判断lock的状态,如果是关,则不能进入同步块中;如果是开,立即获取lock对象(线程持有该锁对象),将lock的状态由开改为关,线程执行到sleep后不会释放持有的锁。持有锁的线程一旦出了同步块,会立即释放持有的锁,将锁的状态由关改为开。
-
好处
- 解决了安全问题
-
弊端
- 线程执行需要判断锁,只有持有了锁,才能执行,因此执行效率降低
-
-
2、同步方法
-
使用关键字synchronized修饰的方法
-
特点
- 有锁对象,对象固定,不需要手动指定
-
方法分为
-
非静态方法
- 锁对象是this
-
静态方法
- 锁对象是静态方法所属的类的字节码对象
- 类名.class
-
-
注
- run方法也可以使用同步关键字修饰,但修饰后为单线程
-
-
3、显示锁
-
jdk5.0中的接口,叫做Lock,可以显示加锁和显示释放锁,java.util.concurrent.locks
-
方法分别是
- lock()添加锁
- unLock()释放锁
-
使用其子类
- ReentrantLock
-
死锁
多线程并发执行时,各自持有资源,都想要对方的资源,但是持有资源的线程又不释放资源,
导致程序不能向下执行,就会出现所谓的”卡”的现象,叫做死锁。
注
- 实际方法中避免写出死锁
模拟实现死锁
- 同步嵌套
工具
- 可以检测是否有死锁
- 打开命令行窗口,
cmd---->tasklist
找java/javaw pid编号 - jstack+pid编号
线程生命周期
生命周期
- 是一个过程,从创建到终止的过程,在过程中可以有不同的阶段
线程生命周期
-
线程从创建到消亡的过程,在这个过程中,也会有不同阶段,这些阶段可以进行转换
- 别名:线程的状态图、线程的声明状态图
新建状态
- 线程对象被创建,执行了new Thread()后
就绪状态(可运行)
- 调用start方法后的状态,有运行资格,等待cpu
运行状态
- 执行run方法的状态
消亡状态
- run方法结束,出现异常、调用stop/destory方法
阻塞状态
- 主动放弃执行资格,cpu不在调度,sleep方法
线程各个状态都是理论上的状态,实际上线程某一个时刻只能处于一种状态,这个状态可以通过方法getState获取到,方法的返回值是Thread.State,State是一个枚举类
wait和sleep方法的区别
- sleep方法不会释放锁对象,时间一到,回到准备状态
枚举
引用类型
- 数组 类 接口 枚举
枚举类型
- 也是一个类类型,是一个特殊的类,jdk5.0
格式
- 修饰符 class 类名{}
- 修饰符 interface 接口名{}
- 修饰符 enum 枚举类名{
1.枚举值,该类对象
2.成员变量,要在枚举值的下边
3.构造方法,要私有化,可以重载,调用重载需在枚举值后边传参
4.成员方法
5.抽象方法,一定要重写(每个枚举值都要进行重写,重写格式有点类似于匿名内部类)
}
- enum Level{
//枚举值
A(100){
@Override
public void method() {
System.out.println("method");
}
},B{
@Override
public void method() {
}
};
//,C,D,E;
//成员变量
int score;
//构造方法---私有的
private Level(){
System.out.println("执行了~~~");
}
private Level(int score){
System.out.println("执行了----"+score);
}
//方法
public void show(){
System.out.println("show");
}
//抽象方法
public abstract void method();
}
switch语句
- 表达式结果是byte short int String char 枚举
注意
- 枚举值名按照常量名的命名规范编写,通常都是大写字母或单词表示。
常用方法
-
toString()
- 打印枚举值名
-
ordinal()
- 返回序号
-
name()
- 返回枚举值名
-
compareTo()
- 序号的差值比较
-
values()
- 获取所有的枚举值
线程池
池
- 容器。
线程池
- 存储线程的
原因
- 创建线程和销毁线程都需要调用底层资源,这个过程是很耗费资源的,尤其是线程任务比较多但是有比较小的情况下,性能损耗比较严重。
解决方式
- 提前准备好线程对象,任务过来从线程池出出来一个线程来执行任务,任务结束也不销毁该线程,让该线程回到线程池,继续等待下一个任务。
在java中,有现有的线程池供我们使用,不需要自己创建(可以自己自定义线程)。
java.util.concurrrent包。
Executors类,创建线程池
-
线程池默认线程个数是0,最大线程个数是:int的最大值
- ExecutorService es = Executors.newCachedThreadPool();
-
线程池默认线程个数是0,最大线程个数是:指定的个数,2
- ExecutorService es = Executors.newFixedThreadPool(2);
-
向下转型
- ThreadPoolExecutor tpe = (ThreadPoolExecutor)es;
-
获取当前线程池中的线程个数
- tpe.getPoolSize()
- Integer.MAX_VALUE
-
提交任务,让线程中的线程执行任务
- es.submit()
- submit参数是Runnable类型或Callable类型
-
关闭线程池,会将已提交的任务执行完,不在接受新任务
- es.shutdown()
-
关闭线程池,试图停止正在执行的任务,将已提交并出于等待的线程作不处理
- es.shutdownNow()
ThreadPoolExecutor对象,创建线程池
-
通过Executors创建的线程还是有些使用的灵活度问题,建议使用ThreadPoolExecutor对象。
-
参数
- corePoolSize - 池中所保存的线程数,包括空闲线程。
- maximumPoolSize - 池中允许的最大线程数。
- keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
- unit - keepAliveTime 参数的时间单位。
- workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
- threadFactory - 执行程序创建新线程时使用的工厂。
- handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
-
注
- 注意: corePoolSize <0 maximumPoolSize <= 0 corePoolSize > maximumPoolSize
keepAliveTime < 0 这样设置不可以,如果这样设置会出现非法参数异常。
workQueue、threaDFactory、handler 都不能为null,如果为null这出异常。
- 注意: corePoolSize <0 maximumPoolSize <= 0 corePoolSize > maximumPoolSize
-
TimeUnit
- 时间单位,是一个枚举类,直接类名.枚举值访问
-
workQueue
-
提交队列
- SynchronousQueue,默认工作模式,任务执行阻塞式的,该任务执行完,才会执行下一个
-
无界队列
- LinkedBlockingQueue,maximumPoolSize,设置无效,任务处理依赖于corePoolSize
-
有界队列
- ArrayBlockingQueue ,最多能处理的线程数:maximumPoolSize+容量(手动指定的)
-
-
handler
-
任务超出范围时,采用的拒绝模式
-
static class ThreadPoolExecutor.AbortPolicy
- 用于被拒绝任务的处理程序,它将抛出 RejectedExecutionException.
-
static class ThreadPoolExecutor.CallerRunsPolicy
- 用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。
-
static class ThreadPoolExecutor.DiscardOldestPolicy
- 用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。
-
static class ThreadPoolExecutor.DiscardPolicy
-
用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
-
-
-
创建线程池对象
- //参数顺序:核心线程数 最大线程数 空闲线程存在时长 时间单位 工作队列 线程工程 拒绝策略
- ThreadPoolExecutor tpe = new ThreadPoolExecutor(1,3,20, TimeUnit.SECONDS,new ArrayBlockingQueue<>(1), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardPolicy());