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,一经查实,立即删除!

相关文章

Linux Vim 由浅入深的教程

引言 原文链接 Vim是Linux系统中非常强大的文本编辑器&#xff0c;因其强大的功能和灵活的操作而受到广泛使用。尤其是在服务器管理和开发环境中&#xff0c;Vim几乎是必备工具。本教程将以CentOS 7为例&#xff0c;详细讲解Vim的安装、基本操作以及一些高级技巧&#xff0c;…

【git】git中的rebase命令解析

在 Git 中&#xff0c; rebase 是一种强大的命令&#xff0c;用于将一个分支的更改重新应用到另一个分支的顶部。它将一个分支的历史记录重新整合到另一个分支上&#xff0c;使得历史记录看起来更干净和线性。这通常用于将一个功能分支的更改合并到主分支&#xff08;如 mast…

ArcGIS Pro SDK (九)几何 5 多边形

ArcGIS Pro SDK &#xff08;九&#xff09;几何 5 多边形 文章目录 ArcGIS Pro SDK &#xff08;九&#xff09;几何 5 多边形1 构造多边形 - 从映射点的枚举2 构造多边形 - 从包络3 获取多边形的点4 获取多边形的各个部分5 枚举多边形的各个部分6 获取多边形的线段7 构建圆环…

Linux应急响应

1.排查账号 进行linux应急响应&#xff0c;首先你得优先查看是否多出来了管理员 &#xff08;1&#xff09;查询特权用户特权用户(uid 为0) awk -F: $30{print $1} /etc/passwd&#xff08;2&#xff09;查询进行远程链接的账号信息 awk /\$1|\$6/{print $1} /etc/shadow …

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…

ubuntu如何安装Redis

文章目录 Ubuntu 版本安装配置密码 Ubuntu 版本 22.04 安装 在 Ubuntu 中安装 Redis 可以按照以下步骤进行&#xff1a; 更新软件包列表 sudo apt update安装 Redis sudo apt install redis-server安装完成后&#xff0c;Redis 服务会自动启动。您可以使用以下命令检查 R…

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;长…

【Flutter 面试题】 使用成熟状态管理库的弊端有哪些?

【Flutter 面试题】 使用成熟状态管理库的弊端有哪些? 文章目录 写在前面口述回答补充说明写在前面 🙋 关于我 ,小雨青年 👉 CSDN博客专家,GitChat专栏作者,阿里云社区专家博主,51CTO专家博主。2023博客之星TOP153。 👏🏻 正在学 Flutter 的同学,你好! 😊 …

数据结构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…

pytorch lightning报错all tensors to be on the same device

RuntimeError: Expected all tensors to be on the same device, but found at least two devices, cuda:0 and cpu! 修改指定为gpu trainer pl.Trainer(max_epochstrain_params.iterations, loggertb_logger,acceleratorgpu, devices1)