10.20-多线程
进程:直译就是正在进行当中的程序,在操作系统中同时运行的每一个程序都是进程
线程:就是进程中的一个负责程序执行的控制单元(执行路径),一个进程中可以有多个执行路径,称之为多线程。
对于一个进程(程序)而言,在程序内部也会同时运行多个任务,那么每一个任务都是由一个线程来控制执行的,一个进程中至少要有一个线程,开启多线程是为了在同一个程序中同时运行多部分代码,每一个线程都由自己运行的内容,这个内容就是线程要执行的任务
线程的运行都是并发执行的;所谓的并发从宏观上看所有的线程都是同时运行的,但从微观上看所有的线程都是走走停停
线程调度
将CPU的时间划分为若干个时间片段,尽可能均匀的分配给每一个线程,获取CPU时间片段的线程将得以被CPU执行
创建线程的两种方式
继承Thread类(线程类),该类的每一个实例表示可以并发的线程
实现Runnable接口(用于发布线程任务的接口)
例子-继承Thread类创建
package day10_20;
public class ThreadDemo01 {
/**
* 创建线程的第一种方式:继承Thread类
* 弊端:线程和线程要干的事情(任务)耦合到了一起
* 使用多线程时,不应当考虑执行先后顺序问题
* 执行没有先后顺序的,叫异步运行(并发运行)
* 执行有先后顺序的,叫同步运行
* @param args
*/
public static void main(String[] args) {
//创建线程对象
Thread t1 = new Person1();
Thread t2 = new Person2();
/*
* 启动线程调用的是start()方法,而并不是run()方法
*/
t1.start();
t2.start();
/*
* t1与t2的执行没有任何的先后顺序,
* 线程调度在给线程分配时间片段后,
* 并没有将某个线程一次性执行完毕。
* 并且分配的时间片段长短也不是一定均匀
*/
}
}
/*
* 该类继承Thread线程类,Thread类的子类就可以作为线程类来使用
* 该类的每一个实例都是一个可以并发的线程
*/
class Person1 extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++) {
System.out.println("Who are you");
}
}
}
class Person2 extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++) {
System.out.println("开门,社区查水表");
}
}
}例子-实现Runnable接口创建
使用线程的注意事项
对于线程调度而言,分配的时间片段长短,具体分配给哪一个时间片段,对于程序而言都是不可控的。
线程的阻塞
线程在执行某段逻辑时,可能会发生阻塞现象
sleep阻塞:该阻塞可以指定阻塞时间,并在线程阻塞了该时间后自动返回Runnable等待状态,并不是直接进入 Running运行状态,Thread类提供了一个静态方法sleep(),该方法就是用于睡眠阻塞的
例子-sleep阻塞线程
线程的停止
run方法正常执行完毕
run方法在执行过程中抛出了中断异常
InterruptedException
例子-主动中断
进程的停止
当一个进程中所有的前台线停止后,该进程就结束前台线程和后台线程
后台线程的特点:用法上与前台线程无异,只是当一个进程所有的前台线程都结束后,
无论线程是否还在运行当中都会强制结束,从而使进程结束
例子-线程守护
优先级
线程的优先级为1-10
优先级越高,分配时间片段的机会也就更多,获得CPU执行的时间也就更多
例子-优先级设置
线程安全
多线程并发访问同一个资源,会产生线程安全问题
解决办法:把异步操作变为同步操作
多线程并发读写同一临界资源会发生"线程安全并发问题",如果保证多线程同步访问临界资源,就可以解决线程安全问题
常见的临界资源
多线程共享的实例变量
静态的公共变量
异步:执行没有先后顺序
同步:执行由先后顺序
同步锁:synchronized关键字
synchronized可以修饰方法,当一个方法被该关键字修饰之后,这个方法就是一个同步方法,在同一个时间只能有一个线程访问该方法
synchronized块
当一个方法被synchronized修饰后,该方法就变为了同步方法,虽然保证了代码的执行安全,但是效率比较低,我们实际上只需要将方法中需要同步的代码片段加锁即可,这样可以缩小同步范围,从而提高代码的运行效率
语法
同步监视器:就是一个对象,任何对象都可以,但是要保证一点,多线程看到的应该是"同一个"对象,通常情况下使用this就可以
线程不安全
StringBuilder
ArrayList
HashMap
HashSet
.....
线程安全
StringBuffer
Vector
HashTable
......
对于集合(Collection)和Map而言,Collections集合工具类中提供了可以将给定集合转换为线程安全集合的方法
线程的协同工作
线程池
Java1.5以后提供了并发包concurrent
Executors是工厂,包含工厂用于创建Executor接口的实例
总结
创建线程的方式
继承Thread类
创建一个类继承Thread类,覆盖run()方法,提供并发运行的过程
创建这个类(子类)实例
使用start()方法启动线程
实现Runnable接口
创建一个类实现Runnable接口,覆盖run()方法,提供并发运行的过程
创建这个类(实现类/任务类)的实例,用这个实例作为Thread类的构造器参数,创建Thread类的实例
使用start()方法启动线程
匿名内部类
线程的状态
new新建状态,还未启动Runnable等待状态,可以运行的状态(就绪)Running运行状态,该线程已经获取了CPU假设线程获取了CPU,则进入
Running状态,开始执行线程体(run()方法)如果系统只有一个CPU(单核心),那么在任意时间点只有一个线程处于
Running状态;如果是双核心,那么任意时间点有两条线程处于Running状态;但是当线程数大于处理器数量时,依然会是多条线程在同一CPU中轮换执行当一条线程开始运行的时候,如果它不是一瞬间完成,线程在执行的过程中会被中断,目的是为了让其他线程获得被执行的机会,像这样的线程调度策略取决于底层平台;对于抢占式策略的平台来说,系统会给每一个可执行的线程一小段时间来处理任务;当时间用完时,系统会剥夺线程所占用的资源(CPU),让其他线程获得运行的机会
Block阻塞状态(挂起状态)以下情况会发生阻塞状态
线程使用了
sleep()方法,主动放弃了CPU资源线程中调用了阻塞式IO方法(比如控制台输入),在该方法返回前,该线程被阻塞
join/Object类中的
wait()
当正在执行的线程被阻塞时,其他线程就获得了机会。阻塞结束时,该线程将进入
Runnable等待状态,而并非直接进入Running运行状态
Dead 死亡状态 当
run()方法执行结束,该线程就进入死亡状态不要试图对于一个已经死亡的线程再次调用
start()方法,线程死亡后不能再次作为线程来执行,会抛出异常
程序、进程、线程三者之间的关系
程序是由一个或多个进程组成的,进程又是由一个或多个线程组成的
线程的状态管理
Thread.sleep(long times)使当前线程从
Running放弃处理器,进入Block状态休眠times毫秒,再返回Runnable状态,如果其他线程调用interrupt()方法中断了当前线程的Block就会发生中断异常(InterruptedException)
Thread.yield()主动的让出CPU,使当前线程进入
Runnable等待状态
线程常用的属性与方法
线程的优先级
setPriority(等级)默认等级为5
后台线程
setDaemon(true)
获取线程的名称
getName()
获取当前线程
Thread.currentThread()
join()阻塞sleep状态的打断与唤醒
sleep()进入休眠状态interrupt()中断休眠
最后更新于