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. JavaWeb和WebGIS学习笔记(六)——使用ArcGIS for Server发布地图服务

    系列链接: Java web与web gis学习笔记(一)--Tomcat环境搭建 Java web与web gis学习笔记(二)--百度地图API调用 JavaWeb和WebGIS学习笔记(三)-- ...

  2. C++ atomic 和 memory ordering 笔记

    如果不使用任何同步机制(例如 mutex 或 atomic),在多线程中读写同一个变量,那么,程序的结果是难以预料的.简单来说,编译器以及 CPU 的一些行为,会影响到程序的执行结果: 即使是简单的语 ...

  3. vscode 开发项目, Prettier ESLint的配置全攻略(基础篇)

    我们在做项目尤其是多人合作开发的时候经常会因为不同的开发规范和代码风格导致出现冲突, 为了能统一代码风格和规范我们需要使用到prettier和eslint,接下来就一vscode编辑器为例详细讲解下: ...

  4. Go Context 原理详解

    实现一个小目标 很开心的一件事,学习了一个月的后端拿到一个13k的offer,今年年底目标拿到一个30k的go方向offer. 好了回归正文,这篇文章是回答交流时一个老哥的问题,跟go的context ...

  5. unity---小地图制作

    脚本控制移动 public float moveSpeed =5f; public float roundSpeed=120f; void Update() { this.transform.Tran ...

  6. Spark在Local环境下的使用

    ①    将 spark-3.0.0-bin-hadoop3.2.tgz 文件上传到 Linux (cd /opt/module路径下)并解压缩 ②    修改spark-3.0.0-bin-hado ...

  7. 学习Java的第十六天——随机数

    学习内容:随机数 1.GetEvenNum()方法 实例代码: package 数字处理类; public class MathRondom {public static int GetEvenNum ...

  8. 网络编程之socket套接字

    目录 socket套接字简介 socket模块 通信循环 代码优化 连接循环 半连接池 黏包问题 解决黏包问题 黏包问题特殊情况(文件过大) socket套接字简介 由于操作OSI七层是所有C/S架构 ...

  9. 《Effective C++》阅读总结(四): 设计、声明与实现

    第四章: 设计与声明 18. 让接口更容易被正确使用,不易被误用 将你的class的public接口设计的符合class所扮演的角色,必要时不仅对传参类型限制,还对传参的值域进一步限制. 19. 设计 ...

  10. 从零开始学YC-Framework之鉴权

    一.YC-Framework鉴权是基于哪一个开源框架做的? YC-Framework鉴权主要基于Dromara开源社区组织下的Sa-Token. 1.什么是Sa-Token? Sa-Token是一个轻 ...