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. Aop踩坑!记一次模板类调用注入属性为空的问题

    问题起因 在做一个需求的时候,发现原来的代码逻辑都是基于模板+泛型的设计模式,模板用于规整逻辑处理流程,泛型用来转换参数和选取实现类.听上去是不是很nice! 类目录结构 AbstractTestAo ...

  2. 关于IDEA中添加静态资源(html,jpg等)后找不到资源(404 not found),以及WEB-INF目录介绍

    关于静态资源的加载 在IDEA中的java web application(或者maven项目)添加新的静态资源时(如html,jpg,gif等),常常会遇到静态资源无法加载的情况.这样的情况我们一般 ...

  3. MKL与VS2019配置方法

    VS2019配置oneAPI并调用MKL库 oneAPI oneAPI是一个跨架构的编程工具,旨在简化跨GPU.CPU.FPGA和AI加速器之间的编程,可以与英特尔自身设备,或其他厂商的芯片配合使用, ...

  4. JavaScript の querySelector 使用说明

    本文记录,JavaScript 中 querySelector 的使用方法.小白贡献,语失莫怪. // 两种 query 的 method (方法) document.querySelector(se ...

  5. Intellij IDEA 高效使用教程 (插件,实用技巧) 最好用的idea插件大全

    安装好Intellij idea之后,进行如下的初始化操作,工作效率提升十倍. 一. 安装插件 1. Codota 代码智能提示插件 只要打出首字母就能联想出一整条语句,这也太智能了,还显示了每条语句 ...

  6. XCTF练习题---MISC---功夫再高也怕菜刀

    XCTF练习题---MISC---功夫再高也怕菜刀 flag:flag{3OpWdJ-JP6FzK-koCMAK-VkfWBq-75Un2z} 解题步骤: 1.观察题目,下载附件 2.下载到电脑后发现 ...

  7. XCTF练习题---CRYPTO---告诉你个秘密

    XCTF练习题---CRYPTO---告诉你个秘密 flag:TONGYUAN 步骤解读: 1.观察题目,下载附件 2.打开附件,内容好像有点像十六进制,先进行一下十六进制转换,得到一串字符 网址:h ...

  8. 不懂 Zookeeper?来看看这篇文章

    开源Linux 长按二维码加关注~ 高并发分布式开发技术体系已然非常的庞大,从国内互联网企业使用情况,可发现RPC.Dubbo.ZK是最基础的技能要求.关于Zookeeper你是不是还停留在Dubbo ...

  9. kNN-预测

    现在进行第五步,对数据进行预测 那么要做的的是从数据集里面拿出一部分作为要预测的,剩下的去比较,书上使用的是10% # 对之前做好的kNN算法进行预测 # 首先获取之前构造好的kNN分类器.数据.规则 ...

  10. Dapr学习(2)之Rancher2.63(k8s&k3s)环境安装Dapr

    前言:前面写过一篇关于dapr入门安装的文章,self-host模式,使用docker安装的本地调试环境,并进行了测试:本篇介绍k8s方式安装dapr,此文主要基于的环境是k3s,通过rancher2 ...