Java 中的线程

创建线程的三种方式

方式一:继承Thread类

实现步骤:

  • 继承Thread类并重写run()方法;

  • 创建线程并启动。

代码实现:

public class MyThread extends Thread {@Overridepublic 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.start();my2.start();}
}

 输出结果如下:

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

    因为run()中封装被线程执行的代码。

方式二:实现Runnable接口

Thread构造方法

方法名说明
Thread(Runnable target)创建一个线程
Thread(Runnable target, String name)创建一个线程,线程名字为name

实现步骤:

  • 实现Runnable接口并重写run()方法,然后创建实现类的对象,表示线程要执行的任务;

  • 把Runnable接口实现类的对象作为构造方法的参数创建一个线程并启动;

代码实现:

public class RunDemo1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread() + "" + i);}}
}

创建线程对象:

public class TestDemo2 {public static void main(String[] args) {RunDemo1 r = new RunDemo1();// 其中r表示线程要执行的任务Thread t1 = new Thread(r);Thread t2 = new Thread(r);t1.start();t2.start();}
}
方式三:实现Callable接口

由于前两种方式的run方法都是没有返回值的,第三种可以获取返回值。

方法介绍:

方法名说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)创建一个 FutureTask,一旦运行就执行给定的 Callable
V get()用来获取执行结果

实现步骤: 实现 Callable 接口,重写 call 方法,这种方式可以通过 FutureTask 获取任务执行的返回值。

代码实现:

public class MyCallable implements Callable {@Overridepublic Object call() throws Exception {return "nihao";}
}

测试一下: 

public class TestDemo3 {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable cl = new MyCallable();FutureTask<String> ft = new FutureTask<String>(cl);Thread t = new Thread(ft);t.start();System.out.println(ft.get());}
}

输出结果:

线程中常用的方法

设置和获取线程名称的方法
方法名说明
void setName(String name)将线程的名字设置为参数name
String getName()返回此线程的名字
static Thread currentThread()返回执行到这行代码的线程
休眠线程方法
方法名说明
static void sleep(long millis)使当前正在执行的线程暂停指定的毫秒数

 需要注意的是,sleep 的时候要对异常进行处理。

try {//sleep会发生异常要显示处理Thread.sleep(20);//暂停20毫秒
} catch (InterruptedException e) {e.printStackTrace();
}
线程优先级相关方法
方法名说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)更改线程的优先级,线程默认优先级是5

如果获取main线程的优先级呢?

首先使用Thread.currentThread()获取到main线程对象;

然后再使用getPriority()获取优先级。

Thread类中与优先级有关的代码,可以看到线程优先级的范围是1-10。

    // The minimum priority that a thread can have.public static final int MIN_PRIORITY = 1;// The default priority that is assigned to a thread.public static final int NORM_PRIORITY = 5;// The maximum priority that a thread can have.public static final int MAX_PRIORITY = 10;

数字越大表示优先级越高,cpu执行这个线程的概率越高,反之越低。

优先级只是概率问题,并不是绝对的。

下面举一个例子:

public class TestDemo1 {public static void main(String[] args) {ThreadDemo1 t1 = new ThreadDemo1("线程1:");ThreadDemo1 t2 = new ThreadDemo1("线程2:");t1.setPriority(1);t2.setPriority(10);t1.start();t2.start();}
}

程序的执行结果中是有可能优先级低的线程1先结束执行的。这里就不提供图片了。

守护线程方法
方法名说明
void setDaemon(boolean on)将此线程标记为守护线程,当非守护线程结束后守护线程也慢慢结束

 ThreadDemo1中的run方法:

    @Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(this.getName() + ":" + i);}}

  ThreadDemo2中的run方法:

    @Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(this.getName() + ":" + i);}}

 创建两个线程,然后将线程t2设置为守护线程,代码如下:

public class TestDemo4 {public static void main(String[] args) {ThreadDemo1 t1 = new ThreadDemo1();ThreadDemo2 t2 = new ThreadDemo2();// 将线程t2设置为守护线程t2.setDaemon(true);t1.start();t2.start();}
}

可以看到在非守护线程t1结束之后,守护线程t2也慢慢结束了。 

 

 礼让线程方法

Java 中的优先级不是特别的可靠,Java 程序中对线程所设置的优先级只是给操作系统一个建议,操作系统不一定会采纳。而真正的调用顺序,是由操作系统的线程调度算法来决定的

插入线程方法

比如将线程t1插入到当前正在运行的main线程之前: 

public class TestDemo5 {public static void main(String[] args) throws InterruptedException {ThreadDemo1 t1 = new ThreadDemo1();t1.start();t1.join();for (int i = 0; i < 40; i++) {System.out.println("main线程:" + i);}}
}

 运行结果:

Java 线程的 6 个状态:

// Thread.State 源码
public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}

 线程的3个状态:就绪,运行,阻塞。图示如下:

cpu执行哪个线程是由操作系统进行调度的,有一些常见的线程调度算法,比如先来先服务,最短作业优先,最短剩余时间优先,轮转法等等。 

synchronized

synchronized关键字用来为这段代码加一个锁,如果只是一部分代码,则可用同步代码块的方式加锁,锁需要手动提供,而如果是整个方法的代码,则可使用同步方法加锁,这种方式的锁是JVM自动提供的。

同步代码块

多线程并发执行可能会出现数据安全问题,比如下面的三个窗口卖100张票的例子。

刚开始代码是这样写的:

public class MyThread1 extends Thread{static int ticket = 0;@Overridepublic void run() {while (true) {if (ticket > 99) {break;} else {ticket++;System.out.println(getName() + "卖了第" + ticket + "张票");}}}
}

有两个问题:①相同的票;②超过范围的票。

下面是正确的代码,加锁的目的是为了使操作具有原子性,当锁没有释放时,别的线程是不能执行这段代码的,一般锁对象用static修饰保证唯一性。

public class MyThread1 extends Thread {static int ticket = 0;final static Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj) {if (ticket > 99) {break;} else {ticket++;System.out.println(getName() + "卖了第" + ticket + "张票");}}}}
}

如果输出结果的顺序是混乱的,比如下面这种情况,是为什么呢?

因为锁没有起作用,比如定义创建一个锁的代码,因为在创建每一个新的线程的时候都会创建一个obj,锁不唯一。

final Object obj = new Object();
同步方法

用synchronized关键字修饰的方法。synchronized关键字写到访问修饰符的后面。

JVM为同步方法自动提供了一个锁:

①非静态方法的锁是调用同步方法的对象;

②静态方法的锁是字节码文件。

锁默认是打开的,当有一个线程进去之后,锁关闭,当代码执行完毕之后锁才打开。

线程在哪里?

执行这行代码的线程。

尝试将上面例子中同步代码块的代码抽取为一个同步方法,代码如下,但是不对,因为同步方法中的锁是调用此方法的对象,此时有三个锁,分别是3个线程,锁不唯一。

public class MyThread1 extends Thread {static int ticket = 0;final static Object obj = new Object();@Overridepublic void run() {while (true) {if (saleTicket()) break;}}private synchronized boolean saleTicket() {if (ticket > 99) {return true;} else {ticket++;System.out.println(getName() + "卖了第" + ticket + "张票");}return false;}
}

 所以同步方法一般使用Runnable接口,因为这种情况下锁唯一。

public class MyRunnable implements Runnable{static int ticket = 0;final static Object obj = new Object();@Overridepublic void run() {while (true) {if (saleTicket()) break;}}private synchronized boolean saleTicket() {if (ticket > 99) {return true;} else {ticket++;System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "张票");}return false;}
}

Lock锁

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口,不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

  • ReentrantLock构造方法

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

    方法名说明
    void lock()获得锁
    void unlock()释放锁

代码如下,有两个细节:

①锁用static修饰,唯一性。

②当某个线程进入锁时,由于ticket < 100为false,执行else里的break结束while循环,但是并没有释放锁,所以程序无法结束,可以将lock.unlock();放到finally子句中确保锁一定会释放。 

有一个疑问:用synchronized的锁是什么释放的?

在大括号结束时释放的。

public class MyThread1 extends Thread {static int ticket = 0;final static Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {lock.lock();try {if (ticket > 99) {break;} else {ticket++;System.out.println(getName() + "卖了第" + ticket + "张票");}} finally {lock.unlock();}}}
}

 子父类有关异常的处理?

生产者消费者模式(等待唤醒机制)

由于线程之间是抢占式执⾏的,因此线程之间执⾏的先后顺序难以预知。但是实际开发中希望协调多个线程的执行顺序,实现线程间的协作。

在 Java 中可以用 wait、notify 和 notifyAll 来实现线程间的通信。

wait和notify两个方法被提取到顶级父类Object中。Object类的等待和唤醒方法:

方法名说明
void wait()让当前线程进⼊等待状态,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify()随机唤醒等待队列中的一个线程
void notifyAll()唤醒等待队列中的所有线程

线程在运行的时候,如果发现某些条件没有被满足,可以调用wait方法放弃已经获得的锁,并且暂停自己的执行,然后进入等待状态。

当该线程被其他线程唤醒并获得锁后,可以沿着之前暂停的地方继续向后执行,而不是再次从同步代码块开始的地方开始执行。

但是需要注意的一点是,在线程通信中涉及到条件判断时要用while而不是if。因为while语句来说,这样在线程被唤醒后,会再次判断条件是否正真满足。

信号量实现
public class Food {static boolean flag;public synchronized static void eat() {while (!flag){try {Food.class.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("小张正在吃汉堡");flag = !flag;Food.class.notifyAll();}public synchronized static void cook() {while (flag){try {Food.class.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("小刘正在做法式大餐");flag = !flag;Food.class.notifyAll();}
}
public class Consumer extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {Food.eat();}}
}
public class Producer extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {Food.cook();}}
}
阻塞队列实现

即生产者和消费者中间有一个缓冲区或者叫容器用来存放产品,可以存放多个。

(1)当CPU执行到生产者这个线程时:

①首先判断容器里产品是否已满;

②已满就调用wait方法使自己进入等待状态;

③没有则生产,使产品数量+1,并唤醒等待的消费者进行消费。

关于其中的notifyAll:由于没有产品可消费了,消费者处于等待状态,当生产者生产了产品后,唤醒消费者。

(2)当CPU执行到消费者这个线程时:

①首先判断容器里是否有产品;

②没有就调用wait方法使自己进入等待状态;

③有则消费,使产品数量-1,并唤醒等待的生产者进行生产。

实现如下:

产品描述Product类代码如下:

public class Product {int id;public Product(int id) {this.id = id;}
}

 用来装产品的容器类SynContainer代码如下:

public class SynContainer {Product[] products = new Product[10];// 既表示产品的个数,也表示生产的产品存入数组中的下标int count = 0;public synchronized void put(Product p) {// 判断容器是否满了// 1. 满了while (count == products.length) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}products[count] = p;count++;this.notifyAll();}public synchronized Product take() {// 首先判断容器里是否有产品// 1. 没有if (count == 0) {try {this.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}count--;this.notifyAll();return products[count];}
}

生产者Producer的代码如下:

public class Producer extends Thread{SynContainer synContainer;public Producer(SynContainer synContainer) {this.synContainer = synContainer;}@Overridepublic void run() {for (int i = 1; i < 100; i++) {synContainer.put(new Product(i));System.out.println("生产者生产了第" + i + "个产品");}}
}

消费者Consumer代码如下:

public class Consumer extends Thread{SynContainer synContainer;public Consumer(SynContainer synContainer) {this.synContainer = synContainer;}@Overridepublic void run() {for (int i = 1; i < 100; i++) {System.out.println("消费者消费了第" + synContainer.take().id + "个产品");}}
}

测试类代码如下:

public class Test {public static void main(String[] args) {SynContainer synContainer = new SynContainer();Producer producer = new Producer(synContainer);Consumer consumer = new Consumer(synContainer);producer.start();consumer.start();}
}

由于线程中的run方法并没有使用锁,因此输出的时候顺序会乱,如下:

还有一个细节,如果如何确保生产者和消费者使用的是同一个容器,即在Producer和Consumer类中只声明一个SynContainer变量,由各自的构造方法传递实际的容器类对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/47857.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DB-GPT:LLM应用的集大成者

整体架构 架构解读 可以看到&#xff0c;DB-GPT把架构抽象为7层&#xff0c;自下而上分别为&#xff1a; 运行环境&#xff1a;支持本地/云端&单机/分布式等部署方式。顺便一提&#xff0c;RAY是蚂蚁深度参与的一个开源项目&#xff0c;所以对RAY功能的支持应该非常完善。…

Vue自定义指令与Vue插槽学习

文章目录 自定义指令1.指令介绍2.自定义指令3.自定义指令语法4.指令中的配置项 自定义指令-指令的值1.使用效果2.语法 插槽-默认插槽1.作用2.用处4.插槽的基本语法 插槽-具名插槽1.作用2.具名插槽语法3.v-slot的简写 插槽总结1.插槽分类2.作用3.场景4.使用步骤 自定义指令 1.指…

实现Nginx的反向代理和负载均衡

一、反向代理和负载均衡简介 1.1、反向代理 反向代理(reverse proxy)指:以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给Internet上请求连接的客户端。此时代理服务器对外就表现为一个反向代理服务器。 反向代…

【Android Compose】ListView效果

【Android Compose】ListView效果 1、Column、Row 和 Box2、LazyColumn和LazyRow3、Compose 中的状态4、ListView效果5、android-compose-codelabs Jetpack Compose 使用入门 Jetpack Compose 教程 Jetpack Compose 1、Column、Row 和 Box Compose 中的三个基本标准布局元素是 …

算法day05 master公式估算递归时间复杂度 归并排序 小和问题 堆排序

2.认识O(NlogN)的排序_哔哩哔哩_bilibili master公式 有这样一个数组&#xff1a;【0&#xff0c;4&#xff0c;2&#xff0c;3&#xff0c;3&#xff0c;1&#xff0c;2】&#xff1b;假设实现了这样一个sort()排序方法&#xff0c; 将数组二分成左右两等分&#xff0c;使用so…

linux、windows、macos,命令终端清屏

文章目录 LinuxWindowsmacOS 在Linux、Windows和macOS的命令终端中&#xff0c;清屏的命令或方法各不相同。以下是针对这三种系统的清屏方法&#xff1a; Linux clear命令&#xff1a;这是最常用的清空终端屏幕的命令之一。在终端中输入clear命令后&#xff0c;屏幕上的所有内容…

【计算机网络】TCP/IP——流量控制与拥塞控制

学习日期&#xff1a;2024.7.22 内容摘要&#xff1a;TCP的流量控制与拥塞控制 流量控制 一般来说&#xff0c;我们总是希望数据传输的快一些&#xff0c;但是如果数据传输的太快&#xff0c;接收方可能就来不及接收&#xff0c;这就会导致数据的丢失&#xff0c;流量控制正是…

Vue中渲染函数

why? 在绝大多数情况下&#xff0c;Vue 推荐使用模板语法来创建应用。然而在某些使用场景下&#xff0c;我们真的需要用到 JavaScript 完全的编程能力。这时渲染函数就派上用场了。 例如&#xff1a;下方要在多个模型上方设置对话框&#xff0c;如果使用Vue模板语法相对较困难…

小技巧:如何在已知PDF密码情况下去掉PDF的密码保护

第一步&#xff0c;用Edge打开你的pdf&#xff0c;输入密码进去 第二步&#xff0c;点击打印 第三步&#xff0c;选择导出PDF&#xff0c;选择彩印 第四步&#xff0c;选择导出位置&#xff0c;导出成功后打开发现没有密码限制了&#xff01;

什么是长效住宅IP?

长效住宅IP的定义 长效住宅IP&#xff0c;简而言之&#xff0c;是指长期稳定、非动态更换的住宅网络IP地址。这类IP地址通常由互联网服务提供商&#xff08;ISP&#xff09;分配给居民家庭用户&#xff0c;用于上网、网络通信等日常网络活动。与传统的动态IP相比&#xff0c;长…

数据结构day5

一、思维导图 二、课后练习 1、使用循环链表完成约瑟夫环问题 2、使用栈&#xff0c;完成进制转换&#xff08;输入&#xff1a;一个整数&#xff0c;进制数&#xff0c;输出&#xff1a;该数的对应的进制数&#xff09; //头文件 #ifndef DEC_TO_BIN_H #define DEC_TO_BIN_H…

【WAF剖析】10种XSS某狗waf绕过姿势,以及思路分析

原文&#xff1a;【WAF 剖析】10 种 XSS 绕过姿势&#xff0c;以及思路分析 xss基础教程参考&#xff1a;https://mp.weixin.qq.com/s/RJcOZuscU07BEPgK89LSrQ sql注入waf绕过文章参考&#xff1a; https://mp.weixin.qq.com/s/Dhtc-8I2lBp95cqSwr0YQw 复现 网站安全狗最新…

Electron 渲染进程直接调用主进程的API库@electron/remote引用讲解

背景 remote是个老库&#xff0c;早期Electron版本中有个remote对象&#xff0c;这个对象可以横跨所有进程&#xff0c;随意通信&#xff0c;后来官方认为不安全&#xff0c;被干掉了&#xff0c;之后有人利用Electron的IPC通信&#xff0c;底层通过Promise的await能力&#x…

Air780EP- AT开发-阿里云应用指南

简介 使用AT方式连接阿里云分为一机一密和一型一密两种方式&#xff0c;其中一机一密又包括HTTP认证二次连接和MQTT直连两种方式 关联文档和使用工具&#xff1a; AT固件获取在线加/解密工具阿里云平台 准备工作 Air780EP_全IO开发板一套&#xff0c;包括天线SIM卡&#xff0…

在Windows安装、部署Tomcat的方法

本文介绍在Windows操作系统中&#xff0c;下载、配置Tomcat的方法。 Tomcat是一个开源的Servlet容器&#xff0c;由Apache软件基金会的Jakarta项目开发和维护&#xff1b;其提供了执行Servlet和Java Server Pages&#xff08;JSP&#xff09;所需的所有功能。其中&#xff0c;S…

机械学习—零基础学习日志(高数09——函数图形)

零基础为了学人工智能&#xff0c;真的开始复习高数 函数图像&#xff0c;开始新的学习&#xff01; 幂函数 利用函数的性质&#xff0c;以幂函数为例&#xff0c;因为单调性相同&#xff0c;利用图中的2和3公式&#xff0c;求最值问题&#xff0c;可以直接将式子进行简化。这…

自监督学习在言语障碍及老年语音识别中的应用

近几十年来针对正常言语的自动语音识别&#xff08;ASR&#xff09;技术取得了快速进展&#xff0c;但准确识别言语障碍&#xff08;dysarthric&#xff09;和老年言语仍然是一项极具挑战性的任务。言语障碍是一种由多种运动控制疾病引起的常见言语障碍类型&#xff0c;包括脑瘫…

175道Docker面试题(上)

目录 1、什么是docker&#xff1f; 2、Docker与普通虚拟机的对比&#xff1a; 3、Docker常用命令&#xff1a; 4、Docker镜像是什么&#xff1f; 5、Docker容器是什么&#xff1f; 6、Docker容器有几种状态&#xff1f; 7、Dockerfile中最常见的指令是什么&#xff1f; …

【人工智能】使用Python的dlib库实现人脸识别技术

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、传统人脸识别技术1. 基于几何特征的方法2. 基于模板匹配的方法3. 基于统计学习的方法 三、深度学习在脸识别中的应用1. 卷积神经网络&#xff08;CNN&#xff09;2. FaceNet和ArcFace 四、使用Python和dlib库实…

【STM32 HAL库】DMA+串口

DMA 直接存储器访问 DMA传输&#xff0c;将数据从一个地址空间复制到另一个地址空间。-----“数据搬运工”。 DMA传输无需CPU直接控制传输&#xff0c;也没有中断处理方式那样保留现场和恢复现场&#xff0c;它是通过硬件为RAM和IO设备开辟一条直接传输数据的通道&#xff0c…