网站首页 > 精选教程 正文
Java 17 多线程的同步和异步
先简单的说说这两个名词的概念。
同步 Synchronize
同步是指在程序的运行中,要等待着处理结束后才能继续执行。比方说主函数中包含 a() 、 b()、 c() 三个方法。
a 调用了 b,b 又调用了 c。同步的概念就是 a 方法执行完之后,再执行 b 方法, b 方法执行完之后再执行 c 方法。
每个方法, 分别睡眠 1 秒钟、2 秒钟和 3 秒钟。同步的情况下,执行时间也就是 6 秒钟。
演示代码如下:
import java.util.concurrent.TimeUnit;
public class Synchronize01 {
public static void main(String[] args) throws InterruptedException {
long beginTime = System.currentTimeMillis();
new Synchronize01().a();
long endTime = System.currentTimeMillis();
System.out.println("执行的总时间为:" + (endTime - beginTime) + " 毫秒");
}
public void a() throws InterruptedException{
TimeUnit.SECONDS.sleep(2);
b();
System.out.println("执行 a() 方法");
}
public void b() throws InterruptedException{
TimeUnit.SECONDS.sleep(1);
c();
System.out.println("执行 b() 方法");
}
public void c() throws InterruptedException{
TimeUnit.SECONDS.sleep(3);
System.out.println("执行 c() 方法");
}
}
完整代码及效果:
因为 Java 的执行默认是同步的, 所以以下的调用方式和上面的结果也是一样的。
异步 Asynchronous
异步是指不需要等待着程序执行结束,还可以继续运行。也就是执行 a() 方法。可以继续执行 b() 方法, 不需要前面的两个方法执行完毕,还可以继续执行 c() 方法。
对于 Java 的异步想要实现, 就需要封装到一个线程中,让线程执行。
同样是上面的 a、b、c 方法。
代码如下:
Thread ta = new Thread() {
@Override
public void run() {
try {
a();
long endTime = System.currentTimeMillis();
System.out.println("a() 执行的总时间为:" + (endTime - beginTime) + " 毫秒");
} catch (Exception e) {
}
}
}.start();
完整代码如下:
查看效果:
用线程执行的结束时间用作结束时间, 这样使用最多的那个就是总用时。这样执行程序的时长取决于线程中执行时间最长的方法。
上面的main 方法中最后加入两句代码。
long endTime = System.currentTimeMillis();
System.out.println("main() 执行的总时间为:" + (endTime - beginTime) + " 毫秒");
重新执行。
你发现 main 方法在 1 毫秒内就执行完毕了。
上面是简单的异步和同步的概念。
线程同步的情况下不会出现程序中的歧义性。不会同时操作的问题。
异步方法在执行的时候, 可以发现其执行规律随机性。且无序的。
这个时候, 会遇到一个新概念,就是多个线程同时访问一个变量。导致并发的时候出现了数据和实际操作的数据不一致的情况。通常称为“脏读”。读取到的数据在读取的过程中被修改了。为什么会出现这种情况, 就是因为多线程情况下读取一个实例变量出现了线程安全问题。大家都在抢占资源,不管是否已经到别人手中了。无法保证多线程的实例变量或者方法不能保证唯一性以及原子性了。
举个存钱和取钱的例子。
首先创建两个线程,用来新增金额以及减少金额。
代码如下:
public void add(Asynchronous02 asynchronous02) {
new Thread(() -> {
asynchronous02.money = asynchronous02.money.add(new BigDecimal("100"));
System.out.println("已存入,余额:" + asynchronous02.money);
}).start();
}
public void minus(Asynchronous02 asynchronous02) {
new Thread(() -> {
asynchronous02.money = asynchronous02.money.add(new BigDecimal("100").negate());
System.out.println("已取出,余额:" + asynchronous02.money);
}).start();
}
定义了一遍变量:
private BigDecimal money = new BigDecimal("0");
完整代码如下:
查看运行效果:
为了方便测试出现脏读的情况,这里在 main 方法中的代码修改成如下内容:
try (Scanner scanner = new Scanner(System.in);) {
System.out.println("菜单");
System.out.println("1. 存入金额 100");
System.out.println("2. 取出金额 100");
System.out.println("3. 查询当前余额");
System.out.println("q. 退出");
Asynchronous02 asynchronous02 = new Asynchronous02();
String line;
while (scanner.hasNextLine() && (line = scanner.nextLine()) != null) {
System.out.println("=================");
switch (line) {
case "1":
for (int i = 0; i < 40; i++) {
asynchronous02.add(asynchronous02);
}
break;
case "2":
asynchronous02.minus(asynchronous02);
break;
case "3":
System.out.println("当前余额:" + asynchronous02.money);
break;
default:
System.out.println("退出系统成功");
System.exit(0);
}
}
} catch (Exception e) {
}
执行添加方法的时候, 直接发起40个线程进行调用。运行查看效果:
发现应该是余额 4000, 但是却只有 3900,这个肯定接受不了。这么辛苦挣的钱, 还吞了我 100 元。
需要说明的是,以上的演示纯粹为了演示而演示。
synchronized 关键字
这个时候 synchronized 关键字登场了。 同步语句代表着执行线程的时候,获取互斥锁,然后执行块内容,执行完成释放锁。如果执行线程已经加锁,其他的线程就没有办法再获取锁了。
synchronized 语句块
改造我们的程序,调整线程里面的 run 方法内容如下:
public void add(asynchronous03 asynchronous03) {
new Thread(() -> {
synchronized(this){
asynchronous03.money = asynchronous03.money.add(new BigDecimal("100"));
System.out.println(Thread.currentThread().getName() + "已存入,余额:" + asynchronous03.money);
}
}).start();
}
为了方便查看,加入了线程名称。
完整代码如下图所示:
这样,重新运行代码。 查看运行之后的效果:
可以看到结果,线程虽然是无序的,但是每次访问都会等待着上一个线程释放线程锁。所以增加的金额是有序的。这样就达到了我们的目的。保证数据的原子性,一致性。保证了业务的正确运行。
这里也可以把 synchronized 也可以放到方法上。 因为这里方法里面使用了线程,所以如果写到方法上,还是无法保证线程安全。所以无法使用该关键字写入到方法的使用方式, 只能使用 synchronized 代码块。
语句块如何保证其加锁状态的内。编写一个简单的代码。为了方便我们查看字节码的内容。
使用 javap -c -v Asynchronous04.class 查看字节码内容。
找到 add 方法。
可以看到 monitorenter 和 monitorexit 指令。在 Java 17 的虚拟机中是使用这个方式类来控制同步的进入与退出的。它被称为同步构造,或者叫做监视器。这只是语句块的方式,如果是方法, 则不是使用这个方式, 而是使用在运行时常量池中的 ACC_SYNCHRONIZED 标志来区分。
synchronized 方法修饰
修改上面的代码。
import java.math.BigDecimal;
public class Asynchronous05 {
private BigDecimal money = new BigDecimal("0");
public static void main(String[] args) throws InterruptedException {
Asynchronous05 asynchronous05 = new Asynchronous05();
asynchronous05.add();
}
public synchronized void add() {
money = money.add(new BigDecimal("100"));
System.out.println(Thread.currentThread().getName() + " 已存入,余额:" + money);
}
}
再次使用 javap -c -v Asynchronous05.class 查看字节码内容的变化。
找到字节码中的 add 方法。 查看字节码常量池的 flags 内容。
可以看到常量池中的代码
public synchronized void add();
descriptor: ()V
flags: (0x0021) ACC_PUBLIC, ACC_SYNCHRONIZED
从思想来说,想要实现同步,一定要给虚拟机说我要同步了,虚拟机说好,我给你当个门卫,你走的时候, 要给我说一声,我再放行下一个。你不出来,门卫就在那一直等你出来。
同步和异步的简单使用,以及异步中如何保证数据的唯一性。线程的知识还有很多很多。今天就先到这了。
感谢您的阅读。欢迎关注,收藏,点赞。更多内容持续更新中。
- 上一篇: java异步编程产生的原因
- 下一篇: Java 爬虫遇上数据异步加载,试试这两种办法
猜你喜欢
- 2024-11-27 多线程技术(同步异步,并发并行)守护线程(java垃圾回收机制)
- 2024-11-27 Java 爬虫遇上数据异步加载,试试这两种办法
- 2024-11-27 java异步编程产生的原因
- 2024-11-27 Java:了解Java中的异步套接字通道
- 2024-11-27 Java面试常见问题:阻塞与非阻塞,同步与异步
- 2024-11-27 parseq-让java异步并发编程更简单
- 2024-11-27 Java异步记录日志-2022新项目
- 2024-11-27 Java异步编程实战:基于JDK中的Future实现异步编程
- 2024-11-27 Java8异步编程就是拽的不行
- 2024-11-27 Java异步任务优化CompletionService
你 发表评论:
欢迎- 04-11Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- 04-11Java中你知道几种从字符串中找指定的字符的数量
- 04-11探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- 04-11Python字符串详解与示例(python字符串的常见操作)
- 04-11java正则-取出指定字符串之间的内容
- 04-11String s1 = new String("abc");这句话创建了几个字符串对象?
- 04-11java判断字符串中是否包含某个字符
- 04-11关于java开发中正确的发牌逻辑编写规范
- 最近发表
-
- Java面试“字符串三兄弟”String、StringBuilder、StringBuffer
- Java中你知道几种从字符串中找指定的字符的数量
- 探秘Java面试中问的最多的String、StringBuffer、StringBuilder
- Python字符串详解与示例(python字符串的常见操作)
- java正则-取出指定字符串之间的内容
- String s1 = new String("abc");这句话创建了几个字符串对象?
- java判断字符串中是否包含某个字符
- 关于java开发中正确的发牌逻辑编写规范
- windows、linux如何后台运行jar(并且显示进程名)
- 腾讯大佬私人收藏,GitHub上最受欢迎的100个JAVA库,值得学习
- 标签列表
-
- nginx反向代理 (57)
- nginx日志 (56)
- nginx限制ip访问 (62)
- mac安装nginx (55)
- java和mysql (59)
- java中final (62)
- win10安装java (72)
- java启动参数 (64)
- java链表反转 (64)
- 字符串反转java (72)
- java逻辑运算符 (59)
- java 请求url (65)
- java信号量 (57)
- java定义枚举 (59)
- java字符串压缩 (56)
- java中的反射 (59)
- java 三维数组 (55)
- java插入排序 (68)
- java线程的状态 (62)
- java异步调用 (55)
- java中的异常处理 (62)
- java锁机制 (54)
- java静态内部类 (55)
- java怎么添加图片 (60)
- java 权限框架 (55)
本文暂时没有评论,来添加一个吧(●'◡'●)