1内容

  • 进程、线程介绍

  • Java中 线程的实现方式

    • Thread 类

    • Runnable 接口

    • Callable 接口

  • 线程相关的方法

  • 线程安全问题 - 同步技术

线程等待唤醒机制

进程(Process)

  • 简单理解:进程就是正在运行的程序

多线程的意义:

随着处理器上的核心数量越来越多,现在大多数计算机都比以往更加擅长并行计算

而一个线程,在一个时刻,只能运行在一个处理器核心上

试想一下,一个单线程程序,在运行时只能使用一个处理器核心,那么再多的处理器核心加入也无法显著提升该程序的执行效率。

相反,如果该程序使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。

总结:使用多线程可以提高程序的执行效率

2、Java 中线程的实现方式

1.继承 Thread 类

方法名 说明
void run() 在线程开启后,此方法将被调用执行
void start() 使此线程开始执行,Java虚拟机会调用run方法()

实现步骤

  • 定义一个类 MyThread 继承 Thread 类

  • 在 MyThread 类中重写 run() 方法

  • 创建 MyThread 类的对象

  • 启动线程

public class MyThread extends Thread {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread(); // my1.run();
// my2.run(); // void start() 导致此线程开始执行; Java虚拟机调用此线程的run方法
my1.start();
my2.start();
}
}

两个小问题

  • 为什么要重写 run() 方法?

    因为 run() 是用来封装被线程执行的代码

run() 方法 和 start() 方法的区别?

run():封装线程执行的代码,直接调用,相当于普通方法的调用

  1. start():启动线程;然后由JVM调用此线程的run()方法

2.实现 Runable 接口

  • Thread构造方法

    方法名 说明
    Thread(Runnable target) 分配一个新的Thread对象
    Thread(Runnable target, String name) 分配一个新的Thread对象
  • 实现步骤
    • 定义一个类MyRunnable实现Runnable接口

    • 在MyRunnable类中重写run()方法

    • 创建MyRunnable类的对象

    • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数

    • 启动线程

public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
// 创建MyRunnable类的对象
MyRunnable my = new MyRunnable(); // 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
// Thread(Runnable target)
// Thread t1 = new Thread(my);
// Thread t2 = new Thread(my);
// Thread(Runnable target, String name)
Thread t1 = new Thread(my,"坦克");
Thread t2 = new Thread(my,"飞机"); // 启动线程
t1.start();
t2.start();
}
}

3.实现 Callable 接口

  • 方法介绍

    方法名 说明
    V call() 计算结果,如果无法计算结果,则抛出一个异常
    FutureTask(Callable<V> callable) 创建一个 FutureTask,一旦运行就执行给定的 Callable
    V get() 如有必要,等待计算完成,然后获取其结果
  • 实现步骤

    • 定义一个类 MyCallable 实现 Callable 接口

    • 在 MyCallable 类中重写 call() 方法

    • 创建 MyCallable 类的对象

    • 创建Future的实现类 FutureTask 对象,把 MyCallable 对象作为构造方法的参数

    • 创建Thread类的对象,把 FutureTask 对象作为构造方法的参数

    • 启动线程

    • 再调用get方法,就可以获取线程结束之后的结果。

public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("跟女孩表白" + i);
}
// 返回值就表示线程运行完毕之后的结果
return "答应";
}
}
public class Demo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable(); // Thread t1 = new Thread(mc); // 可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc); // 创建线程对象
Thread t1 = new Thread(ft); // String s = ft.get();
// 开启线程
t1.start(); String s = ft.get();
System.out.println(s);
}
}

三种实现方式的对比

  • 实现Runnable、Callable接口

    • 好处: 扩展性强,实现该接口的同时还可以继承其他的类

    • 缺点: 编程相对复杂,不能直接使用Thread类中的方法

  • 继承Thread类

    • 好处: 编程比较简单,可以直接使用Thread类中的方法

    • 缺点: 可以扩展性较差,不能再继承其他的类

    •  

3、线程中的相关方法

设置和获取线程名称

方法名 说明
void setName(String name) 将此线程的名称更改为等于参数name
String getName() 返回此线程的名称
Thread currentThread() 返回对当前正在执行的线程对象的引用
  • 代码演示

public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
} @Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread(); // void setName(String name):将此线程的名称更改为等于参数 name
my1.setName("高铁");
my2.setName("飞机"); // Thread(String name)
MyThread my1 = new MyThread("高铁");
MyThread my2 = new MyThread("飞机"); my1.start();
my2.start(); // static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
}
}

线程休眠

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • 代码演示

 1 public class MyRunnable implements Runnable {
2 @Override
3 public void run() {
4 for (int i = 0; i < 100; i++) {
5 try {
6 Thread.sleep(100);
7 } catch (InterruptedException e) {
8 e.printStackTrace();
9 }
10
11 System.out.println(Thread.currentThread().getName() + "---" + i);
12 }
13 }
14 }
15 public class Demo {
16 public static void main(String[] args) throws InterruptedException {
17 /*
18 System.out.println("睡觉前");
19 Thread.sleep(3000);
20 System.out.println("睡醒了");
21 */
22
23 MyRunnable mr = new MyRunnable();
24
25 Thread t1 = new Thread(mr);
26 Thread t2 = new Thread(mr);
27
28 t1.start();
29 t2.start();
30 }
31 }

线程优先级

  • 线程调度

    • 两种调度方式

      • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

      • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

    • Java使用的是抢占式调度模型

    • 随机性

      假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

优先级相关方法

方法名 说明
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10

代码演示

public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return "线程执行完毕了";
}
}
public class Demo {
public static void main(String[] args) {
// 优先级: 1 - 10 默认值:5
MyCallable mc = new MyCallable(); FutureTask<String> ft = new FutureTask<>(mc); Thread t1 = new Thread(ft);
t1.setName("飞机");
t1.setPriority(10);
// System.out.println(t1.getPriority());//5
t1.start(); MyCallable mc2 = new MyCallable(); FutureTask<String> ft2 = new FutureTask<>(mc2); Thread t2 = new Thread(ft2);
t2.setName("坦克");
t2.setPriority(1);
// System.out.println(t2.getPriority());//5
t2.start();
}
}

4、线程同步

卖票案例

  • 案例需求

    某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

  • 实现步骤

    • 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

    • 在SellTicket类中重写run()方法实现卖票,代码步骤如下

    • 判断票数大于0,就卖票,并告知是哪个窗口卖的

    • 卖了票之后,总票数要减1

    • 票卖没了,线程停止

    • 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

    • 创建SellTicket类的对象

    • 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

    • 启动线程

  • 代码实现

 1 public class SellTicket implements Runnable {
2 private int tickets = 100;
3 // 在SellTicket类中重写run()方法实现卖票,代码步骤如下
4 @Override
5 public void run() {
6 while (true) {
7 if(ticket <= 0){
8 // 卖完了
9 break;
10 }else{
11 try {
12 Thread.sleep(100);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 ticket--;
17 System.out.println(Thread.currentThread().getName()
18 + "在卖票,还剩下" + ticket + "张票");
19 }
20 }
21 }
22 }
23 public class SellTicketDemo {
24 public static void main(String[] args) {
25 // 创建SellTicket类的对象
26 SellTicket st = new SellTicket();
27
28 // 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
29 Thread t1 = new Thread(st,"窗口1");
30 Thread t2 = new Thread(st,"窗口2");
31 Thread t3 = new Thread(st,"窗口3");
32
33 // 启动线程
34 t1.start();
35 t2.start();
36 t3.start();
37 }
38 }

Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

  • ReentrantLock构造方法

    方法名 说明
    ReentrantLock() 创建一个ReentrantLock的实例
  • 加锁解锁方法

    方法名 说明
    void lock() 获得锁
    void unlock() 释放锁
  • 代码演示

public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock(); @Override
public void run() {
while (true) {
//synchronized (obj){//多个线程必须使用同一把锁.
try {
lock.lock();
if (ticket <= 0) {
//卖完了
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName()
+ "在卖票,还剩下" + ticket + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
} public class Demo {
public static void main(String[] args) {
Ticket ticket = new Ticket(); Thread t1 = new Thread(ticket);
Thread t2 = new Thread(ticket);
Thread t3 = new Thread(ticket); t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三"); t1.start();
t2.start();
t3.start();
}
}

死锁

  • 概述

    线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行

  • 什么情况下会产生死锁

    1. 资源有限

    2. 同步嵌套

  • 代码演示

 1 public class Demo {
2 public static void main(String[] args) {
3 Object objA = new Object();
4 Object objB = new Object();
5
6 new Thread(()->{
7 while(true){
8 synchronized (objA){
9 // 线程一
10 System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上A锁");
11 synchronized (objB){
12 System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上B锁");
13 }
14 }
15 }
16 }).start();
17
18 new Thread(()->{
19 while(true){
20 synchronized (objB){
21 // 线程二
22 System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上B锁");
23 synchronized (objA){
24 System.out.println(Thread.currentThread().getName() + "----抢到了执行权, 上A锁");
25 }
26 }
27 }
28 }).start();
29 }
30 }

5、线程等待唤醒机制

生产者和消费者模式概述

  • 概述

    生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

    所谓生产者消费者问题,实际上主要是包含了两类线程:

    一类是生产者线程用于生产数据

    一类是消费者线程用于消费数据

    为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

    生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

    消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

  • Object类的等待和唤醒方法

    方法名 说明
    void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    void notify() 唤醒正在等待对象监视器的单个线程
    void notifyAll() 唤醒正在等待对象监视器的所有线程

生产者和消费者案例

public class Box {
public static boolean flag = false;
} public class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (Box.class) {
if (Box.flag == false) {
// 等待
try {
System.out.println("没有包子了, 消费者等待...");
Box.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("消费者开吃....");
Box.flag = false;
Box.class.notifyAll();
}
}
}
}
} public class Maker extends Thread{
@Override
public void run() {
while(true){
synchronized (Box.class) {
if(Box.flag == false){
// 没包子了, 生产者制作
System.out.println("生产制作包子....");
Box.flag = true;
Box.class.notifyAll();
}else {
// 有包子, 生产者等待
System.out.println("有包子, 生产者等待");
try {
Box.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
} public class Test {
public static void main(String[] args) {
Consumer c = new Consumer();
Maker m = new Maker(); c.start();
m.start();
}
}

如有问题,请邮件联系!!!!

day11 - 多线程的更多相关文章

  1. day11(多线程,唤醒机制,生产消费者模式,多线程的生命周期)

    A:进程: 进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. B:线程: 线程是进程中的一个执行单元,负责当前进程中程序的执 ...

  2. python_way ,day11 线程,怎么写一个多线程?,队列,生产者消费者模型,线程锁,缓存(memcache,redis)

    python11 1.多线程原理 2.怎么写一个多线程? 3.队列 4.生产者消费者模型 5.线程锁 6.缓存 memcache redis 多线程原理 def f1(arg) print(arg) ...

  3. Day11 多进程与多线程编程

    一.进程与线程 1.什么是进程(process)? An executing instance of a program is called a process. Each process provi ...

  4. day11学python 多线程+queue

    多线程+queue 两种定义线程方法 1调用threading.Thread(target=目标函数,args=(目标函数的传输内容))(简洁方便) 2创建一个类继承与(threading.Threa ...

  5. java 多线程 day11 lock

    import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock; /** * Create ...

  6. 多线程、多进程、协程、缓存(memcache、redis)

    本节内容: 线程: a:基本的使用: 创建线程: 1:方法 import threading def f1(x): print(x) if __name__=='__main__': t=thread ...

  7. Python中的多进程与多线程(一)

    一.背景 最近在Azkaban的测试工作中,需要在测试环境下模拟线上的调度场景进行稳定性测试.故而重操python旧业,通过python编写脚本来构造类似线上的调度场景.在脚本编写过程中,碰到这样一个 ...

  8. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

  9. 多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类)

    前言:刚学习了一段机器学习,最近需要重构一个java项目,又赶过来看java.大多是线程代码,没办法,那时候总觉得多线程是个很难的部分很少用到,所以一直没下决定去啃,那些年留下的坑,总是得自己跳进去填 ...

随机推荐

  1. Redis6通信协议升级至RESP3,一口气看完13种新数据类型

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 在前面的文章 Redis:我是如何与客户端进行通信的 中,我们介绍过RESP V2版本协议的规范,RESP的全程是Redis Serializa ...

  2. CentOS8更换yum源后出现同步仓库缓存失败的问题

    1.错误情况更新yum时报错: 按照网上教程,更换阿里源.清华源都还是无法使用.可参考: centos8更换国内源(阿里源)_大山的博客-CSDN博客_centos8更换阿里源icon-default ...

  3. 全场景AI推理引擎MindSpore Lite, 助力HMS Core视频编辑服务打造更智能的剪辑体验

    移动互联网的发展给人们的社交和娱乐方式带来了很大的改变,以vlog.短视频等为代表的新兴文化样态正受到越来越多人的青睐.同时,随着AI智能.美颜修图等功能在图像视频编辑App中的应用,促使视频编辑效率 ...

  4. [论文] FRCRN:利用频率递归提升特征表征的单通道语音增强

    本文介绍了ICASSP2022 DNS Challenge第二名阿里和新加坡南阳理工大学的技术方案,该方案针对卷积循环网络对频率特征的提取高度受限于卷积编解码器(Convolutional Encod ...

  5. ucore lab1 操作系统启动过程 学习笔记

    开头赞美THU给我们提供了这么棒的资源.难是真的难,好也是真的好.这种广查资料,反复推敲,反复思考从通电后第一条代码搞起来理顺一个操作系统源码的感觉是真的爽. 1. 操作系统镜像文件ucore.img ...

  6. input 相关

    1.label 标签 for 属性同 input 标签 id 属性联系之一

  7. 关闭BottomSheetDialogFragment从后台返回后的动画

    问题 显示BottomSheetDialogFragment后.将当前应用放于后台,切换到其他APP,然后再返回当前应用.此时会看到BottomSheetDialogFragment从下而上的动画再次 ...

  8. 读 Angular 代码风格指南

    读 Angular 代码风格指南 本文写于 2021 年 1 月 17 日 原文地址:Angular 文档 该文章拥有完整的代码风格指南--大到如何编排文件夹,小到如何进行变量命名都涉及.但是与 ng ...

  9. 《HelloGitHub》第 74 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. https://github.com/521xueweiha ...

  10. 【Unity Shader】syntax error: unexpected token 'struct' at line x 错误解决办法

    以下代码处出现了syntax error: unexpected token 'struct' at line 33的错误 struct a2v { float4 vertex_position : ...