目录
目标
- 理解线程的基本概念
- 掌握线程的创建和启动
- 掌握线程调度的常用方法
- 掌握线程同步的方式
- 理解线程安全的类型
一、基本概念
进程
程序执行的基本单位,是程序的一次动态执行过程,它是从代码加载、执行中到执行完毕的一个完整过程
- 进程的产生、发展到最终消亡的过程
- OS同时管理一个计算机系统的多个进程,轮流使用CPU或共享OS其他资源,CPU执行速度快像“同 时”运行
- 进程分系统进程(OS内部建立的)和用户进程(用户程序建立)
- 进程是系统运行程序的最小单元。
- 各进程间是独立的,每个进程内部数据和状态也是独立的
- 线程:组成进程的基本单位,是程序指定运算的最小单位,是在进程基础上的进一步划分,一个线程可 以完成一个独立的顺序控制流程。
- 同一个进程内的多个线程共享同一块内存空间(包括代码空间、数据空间)和一块系统资源
- 线程切换负担小于进程切换
例如:可以使用QQ聊天的同时听音乐,即有多个独立运行的任务,每个任务对应一个进程,每个进程也可产生多个线程。
在操作系统中可以有多个进程,这些进程包括系统进程(由操作系统内部建立的进程)和用户进程(由用户程序建立的进程)。可以从Windows任务管理器中查看已启动的进程
线程
线程是进程中执行运算的最小单位,可完成一个独立的顺序控制流程。每个进程中,必须至少建立一个线程(这个线程为主线程)来作为这个程序运行的入口点。如果在一个进程中同时运行了多个线程,用来完成我不同的工作,则称之为“多线程”。
例如:在学校里有语文老师、数学老师、英语老师都布置了作业并且都希望你当天写完,但是在同一时间,你只能专心的做一门作业。这就好像程序开启了多个线程,可以处理多个不同的任务,但是在CPU在同一时刻,只能执行该进程的一个线程。
进程于线程的关系
多线程并运行
多线程好处
- 充分 利 用 C P U 的 资 源
- 简 化 编 程 模 型
- 良 好 的 用 户 体 验
进程基本状态
就 绪 ( R e a d y )
执 行 状 态
阻 塞 ( Blo c k ) 状 态
新 ( N e w ) 状 态
终 止 ( Te r min a t e d ) 状 态
二、多线程编程
1.java.lang.Thread类
1.1常用方法
方法 | 描述 | 类型 |
Thread() | 创建对象 | 构造方法 |
Thread(Runnable target) | 创建对象,target为run()方法被调用的对象 | 构造方法 |
Thread(Runnable target , String name) | S创建对象,target为run()方法被调用的对象,name为新线程的名称 | 构造方法 |
void run() | 执行任务操作的方法 | 实例方法 |
void start() | 使该线程开始执行,JVM将调用该线程的run()方法 | 实例方法 |
void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠(暂 停执行) | 静态 方法 |
Thread currentThread() | 返回当前线程对象的引用 | 静态 方法 |
说明:
Thread类的静态方法currentThread()返回当前线程对象的引用
主线程:Java程序启动时立即启动的线程,public static void main() 主线程的入口,每个进程至少一个 主线程
主线程重要性:
- 产生其他子线程的线程
- 因为执行各种关闭动作,故必须最后完成运行
代码如下(示例):
public static void main(String[] args) {
Thread t = Thread.currentThread();
System.out.println("当前线程"+t.getName());//当前线程main
t.setName("瓦达吉瓦");
System.out.println("当前setName线程"+t.getName());//当前setName线程瓦达吉瓦
}
}
1.2继承Thread类创建线程类
条件:
- 此类必须继承Thread类
- 将线程执行的代码写在run()方法中
代码如下(示例):
public class MyThread extends Thread {
@Override
public void run() {//重写run()方法,定义线程执行代码
for (int i=0;i<10;i++) {
//获取当前线程名并打印
//System.out.println(this.getName());
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(500);//线程休眠500毫秒 打印时会500毫秒打印一次
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//-------------------------------------------------------
public static void main(String[] args) {
MyThread mt1=new MyThread();
mt1.setName("My Thread -----> 1");
mt1.start();//<<<<< 注意:使用start()方法启动线程
MyThread mt2=new MyThread();
mt2.setName("My Thread -----> 2");
mt2.start();
}
2.实现Runnable接口创建线程类
2.1说明:
- java中不支持多继承,故继承Thread类后不能在继承其他类
-
通过实现Runnable接口,可以继承其他类特性,更灵活
-
属于java.lang包中,仅提供一个抽象方法 run()的声明
-
Runnable接口离不开Thread类,需借助Thread类中start()方法启动线程及其他操作
代码如下(示例):
public class MyRunnable implements Runnable{//实现接口
@Override
public void run() {
for (int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000);//一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//---------------------------------------------------------------------
public static void main(String[] args) {
MyRunnable mr1=new MyRunnable();//创建接口对象
Thread t1=new Thread(mr1);//通过Thread构造实现的接口对象
t1.setName("My Ruannable -----> 1");
t1.start();//调用Thread对象的start()方法
//运行结果 一秒打印一次My Ruannable -----> 1 一共10次
}
常用结构:
class MyThreadRunnable extends BaseClass implements Runnable{
@Override
public void run(){
//线程代码
}
}
3.线程的状态转换
3.1线程的五种状态
.1.1新建状态(New)
一个Thread或其子类对象被声明并创建,但未启动 对象已分配内存空间和资源,并初始化。但未被调度
3.1.2就绪状态(Runnable)
可运行状态 新建状态的线程被启动(调用start()方法) 进入线程队列等待CPU时间片,一旦运行就脱离主线程,进入自己的生命周期
3.1.3运行状态(Running)
就绪状态的线程被调度并获得CPU资源 运行状态的线程四情形 线程执行完毕 优先级更高的线程抢占CPU
线程休眠(sleep()) 因等待某资源处于阻塞资源
3.1.4阻塞状态(Blocked)
因某些情况运行的线程让出CPU资源 阻塞时没有获得相应资源不能转入就绪状态
3.1.5死亡状态(Dead)
一个线程的run()方法运行完毕 线程死亡的原因 正常运行的线程完成他的所有工作,执行到最后一条语句并退出 当前进程停止运行时,该进程中的所有线程将强制中止 死亡状态并没有该线程的引用时,JVM会从内存中删除该线程类对象
3.2线程调度相关方法
3.2.1常用方法
方法 | 说 明 |
int getPriority() | 返回线程的优先级 |
void setPrority(int newPriority) | 更改线程的优先级 |
boolean isAlive() | 测试线程是否处于活动状态 |
void join() | 进程中的其它线程必须等待该线程终止后才能执行 |
void interrupt() | 中断线程 |
void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
3..2.2 线程优先级
- setPriority(int newPriority)设置优先级,取值范围:1~10 Thread类静态常量优先值:
- MAX_PRIORITY:值为10,最高优先级
MIN_PRIORITY:值为1,最低优先级
NORM_PRIORITY:值为5,默认优先级
代码如下(示例):
public class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":");
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+" 进度:优先级
为:"+Thread.currentThread().getPriority());//显示线程名及优先级
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//-----------------------------
public static void main(String[] args) {
Thread t1=new Thread(new MyPriority());//声明线程
Thread t2=new Thread(new MyPriority());
Thread t3=new Thread(new MyPriority());
t1.setPriority(Thread.MAX_PRIORITY);//设置线程优先级
t2.setPriority(Thread.NORM_PRIORITY);
t3.setPriority(Thread.MIN_PRIORITY);
t1.start();//启动线程
t2.start();
t3.start();
//运行结果会按照优先级从大到小执行
}
3.3线程强制运行 join()
重载方法:
public final void join() //默认参数为0,线程会一直等待
public final void join(long mills)
public final void join(long mills, int nanos)
说明:
- millis:以毫秒为单位的等待时长
- nanos:要等待的附加纳秒时长
- 需处理InterruptedException异常
代码如下(示例):
public class MyJoinThread {
public static void main(String[] args) {
new Thread(()->{ //Lambda表达式
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
if (i==5){
System.out.println("i==5 tji 强制加入");
try{
Thread.currentThread().join(500);//强制执行子线程
// Thread.currentThread().join();
//默认为0!!!如果传递参数是0,则线程会一直等待
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
}
运行结果:
3.4线程的礼让yidld
方法:
public static native void yield();
代码如下(示例):
public class MyYieldThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
Thread.yield();
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
//--------------------------------------------------------------
public static void main(String[] args) {
//礼让
Thread t1=new Thread(new MyYieldThread());
Thread t2=new Thread(new MyYieldThread());
Thread t3=new Thread(new MyYieldThread());
t1.start();
t2.start();
t3.start();
}
注:sleep()与yield()比较
- 相同点:都是静态方法、放弃CPU使用权
- 区别:
礼让线程 异常处理 不考虑其他线程优先级 InterruptedException 让给同级或更高优先级线程 无
4.线程同步
4.1为什么需要线程同步:
- 线程都是独立且异步执行,如遇公用数据需要关注线程状态和行为。
- 将线程中需要一次性完成不允许中断的操作加上一把锁,以解决冲突
4.2实现方式:
关键字:synchronized
1.同步代码块:
synchronized(obj){
//需要同步的代码
}
2.同步方法:
访问修饰符 synchronized 返回类型 方法名(参数列表){//省略方法体}
或
synchronized 访问修饰符 返回类型 方法名(参数列表){//省略方法体}
public class TicketTest implements Runnable {
private Lock lock = new ReentrantLock(); //Lock锁
private int ticket = 100; //票数
private Object locks = new Object();
@Override
public void run() {
while (true) {
synchronized (locks) {
if (ticket > 0) {
try {
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticket + "张票");
ticket--;
System.out.println();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
TicketTest ticket = new TicketTest();
Thread t1= new Thread(ticket,"窗口一");
Thread t2=new Thread(ticket,"窗口二");
Thread t3=new Thread(ticket,"窗口三");
t1.start();
t2.start();
t3.start();
//3个窗口同步买票
}
}
4.3线程同步的特征:
- 不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时,因为要获得这个对象的锁 而相互牵制
- 多个并发线程访问同一资源的同步代码块或同步方法时
- 同一时刻只能有一个线程进入synchronized(this)同步代码块
- 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步 代码块同样被锁定 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非
- synchronized(this)同步代码 如果多个线程访问的不是同一共享资源,无需同步
- 使用同步代码块和同步方法完成线程同步,二者的实现结果没有区别
- 不同点: 同步方法便于阅读理解 同步代码块更精确地限制访问区域,会更高效
4.4 线程安全的类型
如果程序所在的进程中,有多个线程同时运行,每次运行结果和单线程时运行结果是一样的,且其他变 量的值也和预期相同,则当前程序是线程安全的
方法是否同步 | 效率比较 | 适合场景 | |
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
注:为达到安全性和效率的平衡,可以根据实际场景选择合适的类型
4.4.2 常见类型对比
类 | 继承关系 | 线程安全类型 | 键和值为null |
Hashtable | 实现了Map接口,继承Dictionary类 | 线程安全,效率较低 | 不允许 |
HashMap | 实现了Map接口,继AbstractMap类 | 类非线程安全,效率较 高 | 允许 |
StringBuffer && StringBuilder:前者线程安全,后者非线程安全