Java 并发性和多线程2

四、如何创建并运行 java 线程

Java 线程类也是一个 object 类,它的实例都继承自 java.lang.Thread 或其子类。 可以用如下方式用 java 中创建一个线程:

Tread thread = new Thread();

执行该线程可以调用该线程的 start()方法:

thread.start();

在上面的例子中,我们并没有为线程编写运行代码,因此调用该方法后线程就终止了。

编写线程运行时执行的代码有两种方式:一种是创建 Thread 子类的一个实例并重写 run 方法,第二种是创建类的时候实现 Runnable 接口。接下来我们会具体讲解这两种方法:

## 创建 Thread 的子类

创建 Thread 子类的一个实例并重写 run 方法,run 方法会在调用 start()方法之后被执行。例子如下:

public class MyThread extends Thread {public void run(){System.out.println("MyThread running");}
}

可以用如下方式创建并运行上述 Thread 子类:

MyThread myThread = new MyThread();
myTread.start();

一旦线程启动后 start 方法就会立即返回,而不会等待到 run 方法执行完毕才返回。就好像 run 方法是在另外一个 cpu 上执行一样。当 run 方法执行后,将会打印出字符串 MyThread running。

你也可以如下创建一个 Thread 的匿名子类:

Thread thread = new Thread(){public void run(){System.out.println("Thread Running");}
};
thread.start();

当新的线程的 run 方法执行以后,计算机将会打印出字符串”Thread Running”。

## 实现 Runnable 接口

第二种编写线程执行代码的方式是新建一个实现了 java.lang.Runnable 接口的类的实例,实例中的方法可以被线程调用。下面给出例子:

public class MyRunnable implements Runnable {public void run(){System.out.println("MyRunnable running");}
}

为了使线程能够执行 run()方法,需要在 Thread 类的构造函数中传入 MyRunnable 的实例对象。示例如下:

Thread thread = new Thread(new MyRunnable());
thread.start();

当线程运行时,它将会调用实现了 Runnable 接口的 run 方法。上例中将会打印出”MyRunnable running”。

同样,也可以创建一个实现了 Runnable 接口的匿名类,如下所示:

Runnable myRunnable = new Runnable(){public void run(){System.out.println("Runnable running");}
}
Thread thread = new Thread(myRunnable);
thread.start();

## 创建子类还是实现 Runnable 接口?

对于这两种方式哪种好并没有一个确定的答案,它们都能满足要求。就我个人意见,我更倾向于实现 Runnable 接口这种方法。因为线程池可以有效的管理实现了 Runnable 接口的线程,如果线程池满了,新的线程就会排队等候执行,直到线程池空闲出来为止。而如果线程是通过实现 Thread 子类实现的,这将会复杂一些。

有时我们要同时融合实现 Runnable 接口和 Thread 子类两种方式。例如,实现了 Thread 子类的实例可以执行多个实现了 Runnable 接口的线程。一个典型的应用就是线程池。

## 常见错误:调用 run()方法而非 start()方法

创建并运行一个线程所犯的常见错误是调用线程的 run()方法而非 start()方法,如下所示:

Thread newThread = new Thread(MyRunnable());
newThread.run();  //should be start();

起初你并不会感觉到有什么不妥,因为 run()方法的确如你所愿的被调用了。但是,事实上,run()方法并非是由刚创建的新线程所执行的,而是被创建新线程的当前线程所执行了。也就是被执行上面两行代码的线程所执行的。想要让创建的新线程执行 run()方法,必须调用新线程的 start 方法。

## 线程名

当创建一个线程的时候,可以给线程起一个名字。它有助于我们区分不同的线程。例如:如果有多个线程写入 System.out,我们就能够通过线程名容易的找出是哪个线程正在输出。例子如下:

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable, "New Thread");
thread.start();
System.out.println(thread.getName());

需要注意的是,因为 MyRunnable 并非 Thread 的子类,所以 MyRunnable 类并没有 getName()方法。可以通过以下方式得到当前线程的引用:

Thread.currentThread();

因此,通过如下代码可以得到当前线程的名字:

String threadName = Thread.currentThread().getName();

## 线程代码举例

这里是一个小小的例子。首先输出执行main()方法线程名字。这个线程 JVM 分配的。然后开启 10 个线程,命名为 1~10。每个线程输出自己的名字后就退出。

public class ThreadExample {public static void main(String[] args){System.out.println(Thread.currentThread().getName());for(int i=0; i<10; i++){new Thread("" + i){public void run(){System.out.println("Thread: " + getName() + "running");}}.start();}}
}

需要注意的是,尽管启动线程的顺序是有序的,但是执行的顺序并非是有序的。也就是说,1 号线程并不一定是第一个将自己名字输出到控制台的线程。这是因为线程是并行执行而非顺序的。Jvm 和操作系统一起决定了线程的执行顺序,他和线程的启动顺序并非一定是一致的。

五、竞态条件与临界区

在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。如,同一内存区(变量,数组,或对象)、系统(数据库,web services 等)或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。

多线程同时执行下面的代码可能会出错:

public class Counter {protected long count = 0;public void add(long value){this.count = this.count + value;   }
}

想象下线程 A 和 B 同时执行同一个 Counter 对象的 add()方法,我们无法知道操作系统何时会在两个线程之间切换。JVM 并不是将这段代码视为单条指令来执行的,而是按照下面的顺序:

  从内存获取 this.count 的值放到寄存器
  将寄存器中的值增加 value
  将寄存器中的值写回内存

观察线程 A 和 B 交错执行会发生什么:

  this.count = 0;
  A: 读取 this.count 到一个寄存器 (0)
  B: 读取 this.count 到一个寄存器 (0)
  B: 将寄存器的值加 2
  B: 回写寄存器值(2)到内存. this.count 现在等于 2
  A: 将寄存器的值加 3
  A: 回写寄存器值(3)到内存. this.count 现在等于 3

两个线程分别加了 2 和 3 到 count 变量上,两个线程执行结束后 count 变量的值应该等于 5。然而由于两个线程是交叉执行的,两个线程从内存中读出的初始值都是 0。然后各自加了 2 和 3,并分别写回内存。最终的值并不是期望的 5,而是最后写回内存的那个线程的值,上面例子中最后写回内存的是线程 A,但实际中也可能是线程 B。如果没有采用合适的同步机制,线程间的交叉执行情况就无法预料。

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中 add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

六、线程安全与共享资源

允许被多个线程同时执行的代码称作线程安全的代码。线程安全的代码不包含竞态条件。当多个线程同时更新共享资源时会引发竞态条件。因此,了解 Java 线程执行时共享了什么资源很重要。

## 局部变量 

局部变量存储在线程自己的栈中。也就是说,局部变量永远也不会被多个线程共享。所以,基础类型的局部变量是线程安全的。下面是基础类型的局部变量的一个例子:

public void someMethod(){long threadSafeInt = 0;threadSafeInt++;
}

## 局部的对象引用 

对象的局部引用和基础类型的局部变量不太一样。尽管引用本身没有被共享,但引用所指的对象并没有存储在线程的栈内。所有的对象都存在共享堆中。如果在某个方法中创建的对象不会逃逸出(译者注:即该对象不会被其它方法获得,也不会被非局部变量引用到)该方法,那么它就是线程安全的。实际上,哪怕将这个对象作为参数传给其它方法,只要别的线程获取不到这个对象,那它仍是线程安全的。下面是一个线程安全的局部引用样例:

public void someMethod(){LocalObject localObject = new LocalObject();localObject.callMethod();method2(localObject);
}public void method2(LocalObject localObject){localObject.setValue("value");
}

样例中 LocalObject 对象没有被方法返回,也没有被传递给 someMethod()方法外的对象。每个执行 someMethod()的线程都会创建自己的 LocalObject 对象,并赋值给 localObject 引用。因此,这里的 LocalObject 是线程安全的。事实上,整个 someMethod()都是线程安全的。即使将 LocalObject 作为参数传给同一个类的其它方法或其它类的方法时,它仍然是线程安全的。当然,如果 LocalObject 通过某些方法被传给了别的线程,那它就不再是线程安全的了。

## 对象成员 

对象成员存储在堆上。如果两个线程同时更新同一个对象的同一个成员,那这个代码就不是线程安全的。下面是一个样例:

public class NotThreadSafe{StringBuilder builder = new StringBuilder();public add(String text){this.builder.append(text);}    
}

如果两个线程同时调用同一个 NotThreadSafe 实例上的 add()方法,就会有竞态条件问题。例如:

NotThreadSafe sharedInstance = new NotThreadSafe();new Thread(new MyRunnable(sharedInstance)).start();
new Thread(new MyRunnable(sharedInstance)).start();public class MyRunnable implements Runnable{NotThreadSafe instance = null;public MyRunnable(NotThreadSafe instance){this.instance = instance;}public void run(){this.instance.add("some text");}
}

注意两个 MyRunnable 共享了同一个 NotThreadSafe 对象。因此,当它们调用 add()方法时会造成竞态条件。

当然,如果这两个线程在不同的 NotThreadSafe 实例上调用 call()方法,就不会导致竞态条件。下面是稍微修改后的例子:

new Thread(new MyRunnable(new NotThreadSafe())).start();
new Thread(new MyRunnable(new NotThreadSafe())).start();

现在两个线程都有自己单独的 NotThreadSafe 对象,调用 add()方法时就会互不干扰,再也不会有竞态条件问题了。所以非线程安全的对象仍可以通过某种方式来消除竞态条件。

## 线程控制逃逸规则

线程控制逃逸规则可以帮助你判断代码中对某些资源的访问是否是线程安全的。

如果一个资源的创建,使用,销毁都在同一个线程内完成,
且永远不会脱离该线程的控制,则该资源的使用就是线程安全的。

资源可以是对象,数组,文件,数据库连接,套接字等等。Java 中你无需主动销毁对象,所以“销毁”指不再有引用指向对象。

即使对象本身线程安全,但如果该对象中包含其他资源(文件,数据库连接),整个应用也许就不再是线程安全的了。比如 2 个线程都创建了各自的数据库连接,每个连接自身是线程安全的,但它们所连接到的同一个数据库也许不是线程安全的。比如,2 个线程执行如下代码:

检查记录 X 是否存在,如果不存在,插入 X

如果两个线程同时执行,而且碰巧检查的是同一个记录,那么两个线程最终可能都插入了记录:

线程 1 检查记录 X 是否存在。检查结果:不存在
线程 2 检查记录 X 是否存在。检查结果:不存在
线程 1 插入记录 X
线程 2 插入记录 X

同样的问题也会发生在文件或其他共享资源上。因此,区分某个线程控制的对象是资源本身,还是仅仅到某个资源的引用很重要。

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

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

相关文章

国内镜像源配置方法(包括临时和永久方法)

国内镜像源&#xff1a; 阿里云 http://mirrors.aliyun.com/pypi/simple/中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/豆瓣 http://pypi.douban.com/simplePython官方 https://pypi.python.org/simple/v2ex http://pypi.v2ex.com/simple/中国科学院 http://pypi.mi…

【算法题】50. Pow(x, n)

题目 实现 pow(x, n) &#xff0c;即计算 x 的整数 n 次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2.10000, n 3 输出&#xff1a;9.…

【算法题】49. 字母异位词分组

题目 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs ["eat", "tea", "tan", "ate", "nat&q…

git 提交符号

emojiemoji代码commit说明&#x1f3a8; (调色板):art:改进代码结构/代码格式⚡️ (闪电):zap:提升性能&#x1f40e; (赛马):racehorse:提升性能&#x1f525; (火焰):fire:移除代码或文件&#x1f41b; (bug):bug:修复 bug&#x1f691; (急救车):ambulance:重要补丁✨ (火花…

PyTorch GPU利用率为0%(很低)

一、确保Pytorch与TorchVision是CUDA(GPU)版本 参考链接&#xff1a;【Xiang哥避坑指南】YOLOV5只在CPU跑不在GPU跑的问题。 在Python终端下操作&#xff1a; 1、Pytorch import torch print(torch.__version__) #上方的_是两个 杠杠2、TorchVision import torchvision prin…

flutter在windows环境搭建

下载flutter https://flutter.cn/docs/development/tools/sdk/releases 下载相应的版本 我放在C盘下&#xff1a; 环境变量 再加系统变量&#xff1a; PUB_HOSTED_URLhttps://pub.flutter-io.cn 如图 FLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn 完成

在Ubuntu系统中,要优化文件句柄数、线程和网络

在Ubuntu系统中&#xff0c;要优化文件句柄数、线程和网络&#xff0c;通常涉及到系统限制和内核参数的调整。下面是一些常见的优化步骤&#xff0c;它们可以通过重启来应用&#xff1a; 优化文件句柄数&#xff08;File Descriptors&#xff09;: 编辑 /etc/security/limits.c…

使用nginx+HTML2canvas将任意html网页转为png图片自定义张数

文章目录 概述网页的转换html2canvas的使用导入导入HTML2canvas库函数定义 nginx部署编写控制截图网页代码iframe 网页控制代码 测试说明 概述 本文简述如何使用nginxhtml2canvas将任意网页html转为png图片 网页的转换 如果是本地网页&#xff0c;直接进行nginx反向代理就行…

【springboot】sprinMVC练习

Spring MVC练习 1. 加法计算器练习 前端页面&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><t…

Go语言中的同步原语:ErrGroup、Semaphore和SingleFlight

1. 并发基础 并发是同时发生多个计算或事件的能力。并发通常通过同时执行多个任务或进程来实现&#xff0c;这些任务或进程共享相同的资源&#xff08;例如内存或处理器&#xff09;。并发使用的基本机制被称为锁。在Go语言中&#xff0c;锁是一个类型变量&#xff0c;它包含一…

ubuntu nginx安装部署

上传nginx-1.18.0.tar.gz mv nginx-1.18.0.tar.gz /usr/local/ #解压 tar -zxvf nginx-1.18.0.tar.gz #安装 cd nginx-1.18.0 #安装依赖包apt-get install build-essential zlib1g-dev libpcre3 libpcre3-dev libssl-dev libxslt1-dev libxml2-dev libgeoip-dev openssl libgd…

【开源】基于JAVA+Vue+SpringBoot的医院门诊预约挂号系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2 科室医生档案模块2.1.3 预约挂号模块2.1.4 医院时政模块 2.2 可行性分析2.2.1 可靠性2.2.2 易用性2.2.3 维护性 三、数据库设计3.1 用户表3.2 科室档案表3.3 医生档案表3.4 医生放号…

【目标检测】YOLOv5算法实现(七):模型训练

本系列文章记录本人硕士阶段YOLO系列目标检测算法自学及其代码实现的过程。其中算法具体实现借鉴于ultralytics YOLO源码Github&#xff0c;删减了源码中部分内容&#xff0c;满足个人科研需求。   本系列文章主要以YOLOv5为例完成算法的实现&#xff0c;后续修改、增加相关模…

2023一带一路暨金砖国家技能发展与技术创新大赛“网络安全”赛项省选拔赛样题卷①

2023金砖国家职业技能竞赛"网络安全" 赛项省赛选拔赛样题 2023金砖国家职业技能竞赛 省赛选拔赛样题第一阶段&#xff1a;职业素养与理论技能项目1. 职业素养项目2. 网络安全项目3. 安全运营 第二阶段&#xff1a;安全运营项目1. 操作系统安全配置与加固任务一Linux …

机器学习和深度学习检测网络安全课题资料:XSS、DNS和DGA、恶意URL、webshell

开源算法 XSS 机器学习识别XSS实践使用深度学习检测XSS使用深度学习检测XSS(续)DNS&DGA检测 使用CNN检测DNS隧道 探秘-基于机器学习的DNS隐蔽隧道检测方法与实现 DNS Tunnel隧道隐蔽

Go语言中的Channel

1. 简介 Channel是Go语言中一种重要的并发原语&#xff0c;它允许goroutine之间安全地交换数据。Channel是一个类型化的队列&#xff0c;它可以存储一个特定类型的值。goroutine可以通过发送和接收操作来向channel中写入和读取数据。 2. Channel的类型 Channel的类型由其元素…

第一次面试复盘

这个秋招到目前为止第一次拿到了面试机会&#xff0c;虽然是小公司&#xff0c;但是人家是有官网的&#xff01;&#xff01;&#xff01;很爱&#xff01;先赶紧复盘一下&#xff0c;因为还有很多笔试没有复盘。 你们的数学建模解决了什么问题&#xff1f;你觉得你们为什么能拿…

CompletableFuture、ListenableFuture高级用列

CompletableFuture 链式 public static void main(String[] args) throws Exception {CompletableFuture<Integer> thenCompose T1().thenCompose(Compress::T2).thenCompose(Compress::T3);Integer result thenCompose.get();System.out.println(result);}// 假设这些…

【Oracle】Oracle编程PLSQL

Oracle编程 一、PL/SQL 1、PL/SQL概述 PL/SQL&#xff08;Procedure Language/SQL&#xff09;是 Oracle 对 sql 语言的过程化扩展&#xff0c;使 SQL 语言具有过程处理能力。 基本语法结构 [declare -- 声明变量 ]begin-- 代码逻辑 [exception-- 异常处理 ]end;2、变量 …

centos7下升级openssh9.4p1及openssl1.1.1v版本

背景&#xff1a;客户服务器扫描出一些漏洞&#xff0c;发现和版本有关&#xff0c;漏洞最高的版本是9.3p2&#xff0c;所以我们安装一个openssh9.4p1版本及openssl1.1.1v版本 虽然我们进行了镜像备份&#xff0c;为了安全先安装telnet以防止升级失败无法通过ssh连接服务器 一…