Synchronized 的 8 种使用场景!

blog.csdn.net/x541211190/article/details/106272922

简介

本文将介绍8种同步方法的访问场景,我们来看看这8种情况下,多线程访问同步方法是否还是线程安全的。这些场景是多线程编程中经常遇到的,而且也是面试时高频被问到的问题,所以不管是理论还是实践,这些都是多线程场景必须要掌握的场景。

八种使用场景:

接下来,我们来通过代码实现,分别判断以下场景是不是线程安全的,以及原因是什么。

  1. 两个线程同时访问同一个对象的同步方法

  2. 两个线程同时访问两个对象的同步方法

  3. 两个线程同时访问(一个或两个)对象的静态同步方法

  4. 两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法

  5. 两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法

  6. 两个线程同时访问同一个对象的不同的同步方法

  7. 两个线程分别同时访问静态synchronized和非静态synchronized方法

  8. 同步方法抛出异常后,JVM会自动释放锁的情况

场景一:两个线程同时访问同一个对象的同步方法

分析:这种情况是经典的对象锁中的方法锁,两个线程争夺同一个对象锁,所以会相互等待,是线程安全的。

「两个线程同时访问同一个对象的同步方法,是线程安全的。」

场景二:两个线程同时访问两个对象的同步方法

这种场景就是对象锁失效的场景,原因出在访问的是两个对象的同步方法,那么这两个线程分别持有的两个线程的锁,所以是互相不会受限的。加锁的目的是为了让多个线程竞争同一把锁,而这种情况多个线程之间不再竞争同一把锁,而是分别持有一把锁,所以我们的结论是:

「两个线程同时访问两个对象的同步方法,是线程不安全的。」

代码验证:

public class Condition2 implements Runnable {  // 创建两个不同的对象  static Condition2 instance1 = new Condition2();  static Condition2 instance2 = new Condition2();  @Override  public void run() {  method();  }  private synchronized void method() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");  }  public static void main(String[] args) {  Thread thread1 = new Thread(instance1);  Thread thread2 = new Thread(instance2);  thread1.start();  thread2.start();  while (thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  
}  

运行结果:

两个线程是并行执行的,所以线程不安全。

线程名:Thread-0,运行开始  
线程名:Thread-1,运行开始  
线程:Thread-0,运行结束  
线程:Thread-1,运行结束  
测试结束  

代码分析:

「问题在此:」

两个线程(thread1、thread2),访问两个对象(instance1、instance2)的同步方法(method()),两个线程都有各自的锁,不能形成两个线程竞争一把锁的局势,所以这时,synchronized修饰的方法method()和不用synchronized修饰的效果一样(不信去把synchronized关键字去掉,运行结果一样),所以此时的method()只是个普通方法。

「如何解决这个问题:」

若要使锁生效,只需将method()方法用static修饰,这样就形成了类锁,多个实例(instance1、instance2)共同竞争一把类锁,就可以使两个线程串行执行了。这也就是下一个场景要讲的内容。

场景三:两个线程同时访问(一个或两个)对象的静态同步方法

这个场景解决的是场景二中出现的线程不安全问题,即用类锁实现:

「两个线程同时访问(一个或两个)对象的静态同步方法,是线程安全的。」

场景四:两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法

这个场景是两个线程其中一个访问同步方法,另一个访问非同步方法,此时程序会不会串行执行呢,也就是说是不是线程安全的呢?
我们可以确定是线程不安全的,如果方法不加synchronized都是安全的,那就不需要同步方法了。验证下我们的结论:

「两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法,是线程不安全的。」

public class Condition4 implements Runnable {  static Condition4 instance = new Condition4();  @Override  public void run() {  //两个线程访问同步方法和非同步方法  if (Thread.currentThread().getName().equals("Thread-0")) {  //线程0,执行同步方法method0()  method0();  }  if (Thread.currentThread().getName().equals("Thread-1")) {  //线程1,执行非同步方法method1()  www.xttblog.commethod1();  }  }  // 同步方法  private synchronized void method0() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法,运行结束");  }  // 普通方法  private void method1() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",普通方法,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",普通方法,运行结束");  }  public static void main(String[] args) {  Thread thread1 = new Thread(instance);  Thread thread2 = new Thread(instance);  thread1.start();  thread2.start();  while (thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  }  

运行结果:

两个线程是并行执行的,所以是线程不安全的。

线程名:Thread-0,同步方法,运行开始  
线程名:Thread-1,普通方法,运行开始  
线程:Thread-0,同步方法,运行结束  
线程:Thread-1,普通方法,运行结束  
测试结束  

结果分析

问题在于此:method1没有被synchronized修饰,所以不会受到锁的影响。即便是在同一个对象中,当然在多个实例中,更不会被锁影响了。结论:

「非同步方法不受其它由synchronized修饰的同步方法影响」

你可能想到一个类似场景:多个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法,这个场景会是线程安全的吗?

场景五:两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法

我们来实验下这个场景,用两个线程调用同步方法,在同步方法中调用普通方法;再用一个线程直接调用普通方法,看看是否是线程安全的?

public class Condition8 implements Runnable {  static Condition8 instance = new Condition8();  @Override  public void run() {  if (Thread.currentThread().getName().equals("Thread-0")) {  //直接调用普通方法  method2();  } else {  // 先调用同步方法,在同步方法内调用普通方法  method1();  }  }  // 同步方法  private static synchronized void method1() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法,运行开始");  try {  Thread.sleep(2000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法,运行结束,开始调用普通方法");  method2();  }  // 普通方法  private static void method2() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",普通方法,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",普通方法,运行结束");  }  public static void main(String[] args) {  // 此线程直接调用普通方法  Thread thread0 = new Thread(instance);  // 这两个线程直接调用同步方法  Thread thread1 = new Thread(instance);  Thread thread2 = new Thread(instance);  thread0.start();  thread1.start();  thread2.start();  while (thread0.isAlive() || thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  }  

运行结果:

线程名:Thread-0,普通方法,运行开始  
线程名:Thread-1,同步方法,运行开始  
线程:Thread-1,同步方法,运行结束,开始调用普通方法  
线程名:Thread-1,普通方法,运行开始  
线程:Thread-0,普通方法,运行结束  
线程:Thread-1,普通方法,运行结束  
线程名:Thread-2,同步方法,运行开始  
线程:Thread-2,同步方法,运行结束,开始调用普通方法  
线程名:Thread-2,普通方法,运行开始  
线程:Thread-2,普通方法,运行结束  
测试结束  

结果分析:

我们可以看出,普通方法被两个线程并行执行,不是线程安全的。这是为什么呢?

因为如果非同步方法,有任何其他线程直接调用,而不是仅在调用同步方法时,才调用非同步方法,此时会出现多个线程并行执行非同步方法的情况,线程就不安全了。

对于同步方法中调用非同步方法时,要想保证线程安全,就必须保证非同步方法的入口,仅出现在同步方法中。但这种控制方式不够优雅,若被不明情况的人直接调用非同步方法,就会导致原有的线程同步不再安全。所以不推荐大家在项目中这样使用,但我们要理解这种情况,并且我们要用语义明确的、让人一看就知道这是同步方法的方式,来处理线程安全的问题。

所以,最简单的方式,是在非同步方法上,也加上synchronized关键字,使其变成一个同步方法,这样就变成了《场景五:两个线程同时访问同一个对象的不同的同步方法》,这种场景下,大家就很清楚的看到,同一个对象中的两个同步方法,不管哪个线程调用,都是线程安全的了。

所以结论是:

「两个线程访问同一个对象中的同步方法,同步方法又调用一个非同步方法,仅在没有其他线程直接调用非同步方法的情况下,是线程安全的。若有其他线程直接调用非同步方法,则是线程不安全的。」

场景六:两个线程同时访问同一个对象的不同的同步方法

这个场景也是在探讨对象锁的作用范围,对象锁的作用范围是对象中的所有同步方法。所以,当访问同一个对象中的多个同步方法时,结论是:

「两个线程同时访问同一个对象的不同的同步方法时,是线程安全的。」

public class Condition5 implements Runnable {  static Condition5 instance = new Condition5();  @Override  public void run() {  if (Thread.currentThread().getName().equals("Thread-0")) {  //线程0,执行同步方法method0()  method0();  }  if (Thread.currentThread().getName().equals("Thread-1")) {  //线程1,执行同步方法method1()  www.xttblog.commethod1();  }  }  private synchronized void method0() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法0,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法0,运行结束");  }  private synchronized void method1() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",同步方法1,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",同步方法1,运行结束");  }  //运行结果:串行  public static void main(String[] args) {  Thread thread1 = new Thread(instance);  Thread thread2 = new Thread(instance);  thread1.start();  thread2.start();  while (thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  
}  

运行结果:

是线程安全的。

线程名:Thread-1,同步方法1,运行开始  
线程:Thread-1,同步方法1,运行结束  
线程名:Thread-0,同步方法0,运行开始  
线程:Thread-0,同步方法0,运行结束  
测试结束  

结果分析:

两个方法(method0()和method1())的synchronized修饰符,虽没有指定锁对象,但默认锁对象为this对象为锁对象,
所以对于同一个实例(instance),两个线程拿到的锁是同一把锁,此时同步方法会串行执行。这也是synchronized关键字的可重入性的一种体现。

场景七:两个线程分别同时访问静态synchronized和非静态synchronized方法

这种场景的本质也是在探讨两个线程获取的是不是同一把锁的问题。静态synchronized方法属于类锁,锁对象是(*.class)对象,非静态synchronized方法属于对象锁中的方法锁,锁对象是this对象。两个线程拿到的是不同的锁,自然不会相互影响。结论:

「两个线程分别同时访问静态synchronized和非静态synchronized方法,线程不安全。」

代码实现:

public class Condition6 implements Runnable {  static Condition6 instance = new Condition6();  @Override  public void run() {  if (Thread.currentThread().getName().equals("Thread-0")) {  //线程0,执行静态同步方法method0()  method0();  }  if (Thread.currentThread().getName().equals("Thread-1")) {  //线程1,执行非静态同步方法method1()  method1();  }  }  // 重点:用static synchronized 修饰的方法,属于类锁,锁对象为(*.class)对象。  private static synchronized void method0() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",静态同步方法0,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",静态同步方法0,运行结束");  }  // 重点:synchronized 修饰的方法,属于方法锁,锁对象为(this)对象。  private synchronized void method1() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",非静态同步方法1,运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",非静态同步方法1,运行结束");  }  //运行结果:并行  public static void main(String[] args) {  //问题原因: 线程1的锁是类锁(*.class)对象,线程2的锁是方法锁(this)对象,两个线程的锁不一样,自然不会互相影响,所以会并行执行。  Thread thread1 = new Thread(instance);  Thread thread2 = new Thread(instance);  thread1.start();  thread2.start();  while (thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  

运行结果:

线程名:Thread-0,静态同步方法0,运行开始  
线程名:Thread-1,非静态同步方法1,运行开始  
线程:Thread-1,非静态同步方法1,运行结束  
线程:Thread-0,静态同步方法0,运行结束  
测试结束  

场景八:同步方法抛出异常后,JVM会自动释放锁的情况

本场景探讨的是synchronized释放锁的场景:

「只有当同步方法执行完或执行时抛出异常这两种情况,才会释放锁。」

所以,在一个线程的同步方法中出现异常的时候,会释放锁,另一个线程得到锁,继续执行。而不会出现一个线程抛出异常后,另一个线程一直等待获取锁的情况。这是因为JVM在同步方法抛出异常的时候,会自动释放锁对象。

代码实现:

public class Condition7 implements Runnable {  private static Condition7 instance = new Condition7();  @Override  public void run() {  if (Thread.currentThread().getName().equals("Thread-0")) {  //线程0,执行抛异常方法method0()  method0();  }  if (Thread.currentThread().getName().equals("Thread-1")) {  //线程1,执行正常方法method1()  method1();  }  }  private synchronized void method0() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  //同步方法中,当抛出异常时,JVM会自动释放锁,不需要手动释放,其他线程即可获取到该锁  System.out.println("线程名:" + Thread.currentThread().getName() + ",抛出异常,释放锁");  throw new RuntimeException();  }  private synchronized void method1() {  System.out.println("线程名:" + Thread.currentThread().getName() + ",运行开始");  try {  Thread.sleep(4000);  } catch (InterruptedException e) {  e.printStackTrace();  }  System.out.println("线程:" + Thread.currentThread().getName() + ",运行结束");  }  public static void main(String[] args) {  Thread thread1 = new Thread(instance);  Thread thread2 = new Thread(instance);  thread1.start();  thread2.start();  while (thread1.isAlive() || thread2.isAlive()) {  }  System.out.println("测试结束");  }  }  

运行结果:

线程名:Thread-0,运行开始  
线程名:Thread-0,抛出异常,释放锁  
线程名:Thread-1,运行开始  
Exception in thread "Thread-0" java.lang.RuntimeException  at com.study.synchronize.conditions.Condition7.method0(Condition7.java:34)  at com.study.synchronize.conditions.Condition7.run(Condition7.java:17)  at java.lang.Thread.run(Thread.java:748)  
线程:Thread-1,运行结束  
测试结束  

结果分析:

可以看出线程还是串行执行的,说明是线程安全的。而且出现异常后,不会造成死锁现象,JVM会自动释放出现异常线程的锁对象,其他线程获取锁继续执行。

总结

本文总结了并用代码实现和验证了synchronized各种使用场景,以及各种场景发生的原因和结论。我们分析的理论基础都是synchronized关键字的锁对象究竟是谁?多个线程之间竞争的是否是同一把锁?根据这个条件来判断线程是否是安全的。所以,有了这些场景的分析锻炼后,我们在以后使用多线程编程时,也可以通过分析锁对象的方式,判断出线程是否是安全的,从而避免此类问题的出现。

本文涵盖了synchronized关键字的最重要的各种使用场景,也是面试官常常会问到的高频问题,是一篇值得大家仔细阅读和亲自动手实践的文章,喜欢本文请点赞和收藏。


往期推荐

线程池的7种创建方式,强烈推荐你用它...


求求你,别再用wait和notify了!


2020年终总结:新的“开始”


关注我,每天陪你进步一点点!

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

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

相关文章

Python的threadpool模块

2019独角兽企业重金招聘Python工程师标准>>> Python的threadpool模块 这是一个使用python实现的线程池库。 安装 pip install threadpool 文档 http://gashero.yeax.com/?p44 http://www.chrisarndt.de/projects/threadpool/ 测试 使用一个20个线程的线程池进行测试…

硬核Redis总结,看这篇就够了!

高清思维导图已同步Git:https://github.com/SoWhat1412/xmindfile总感觉哪里不对,但是又说不上来1、基本类型及底层实现1.1、String用途:适用于简单key-value存储、setnx key value实现分布式锁、计数器(原子性)、分布式全局唯一ID。底层&…

文件写入的6种方法,这种方法性能最好

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)在 Java 中操作文件的方法本质上只有两种:字符流和字节流,而字节流和字符流的实现类又有很多&#x…

JDK 16 即将发布,新特性速览!

你还能追上 Java 的更新速度吗?当开发者深陷 Java 8 版本之际,这边下一版本 Java 16 有了最新的消息,与 Java 15 一样,作为短期版本,Oracle 仅提供 6 个月的支持。根据发布计划,JDK 16 将在 12 月 10 日和 …

最牛逼的 Java 项目实战,没有之一!

想要成长为高级开发,掌握更多层面的技术,兼顾深度和广度是毋庸置疑的。你肯定认为,我要认真努力的学习技术,丰富自己的技术栈,然后就可以成为一个优秀的高级开发了。但当你真正去学习之后就会发现,技术栈异…

定时任务的实现原理,看完就能手撸一个!

一、摘要在很多业务的系统中,我们常常需要定时的执行一些任务,例如定时发短信、定时变更数据、定时发起促销活动等等。在上篇文章中,我们简单的介绍了定时任务的使用方式,不同的架构对应的解决方案也有所不同,总结起来…

Spring Boot集成Redis,这个坑把我害惨了!

最近项目中使用SpringBoot集成Redis,踩到了一个坑:从Redis中获取数据为null,但实际上Redis中是存在对应的数据的。是什么原因导致此坑的呢?本文就带大家从SpringBoot集成Redis、所踩的坑以及自动配置源码分析来学习一下SpringBoot…

数据分析告诉你为什么Apple Watch会大卖?

摘要: 不管是无敌创意还是无聊鸡肋,苹果手表还是来了。眼下它上市在即,将率先登陆9个国家或地区——包括中国。根据凌晨发布会上公布的内容,Apple Watch采用全新的压感触屏和蓝宝石镜面,能够记录健康数据、同步手机信息 ...不管是…

putc函数_C语言中的putc()函数与示例

putc函数C语言中的putc()函数 (putc() function in C) The putc() function is defined in the <stdio.h> header file. putc()函数在<stdio.h>头文件中定义。 Prototype: 原型&#xff1a; int putc(const char ch, FILE *filename);Parameters: const char ch,…

编程中的21个坑,你占几个?

前言最近看了某客时间的《Java业务开发常见错误100例》&#xff0c;再结合平时踩的一些代码坑&#xff0c;写写总结&#xff0c;希望对大家有帮助&#xff0c;感谢阅读~1. 六类典型空指针问题包装类型的空指针问题级联调用的空指针问题Equals方法左边的空指针问题ConcurrentHas…

Mybatis使用的9种设计模式,真是太有用了

crazyant.net/2022.html虽然我们都知道有26个设计模式&#xff0c;但是大多停留在概念层面&#xff0c;真实开发中很少遇到&#xff0c;Mybatis源码中使用了大量的设计模式&#xff0c;阅读源码并观察设计模式在其中的应用&#xff0c;能够更深入的理解设计模式。Mybatis至少遇…

Java 生成随机数的 5 种方式,你知道几种?

1. Math.random() 静态方法产生的随机数是 0 - 1 之间的一个 double&#xff0c;即 0 < random < 1。使用&#xff1a;for (int i 0; i < 10; i) {System.out.println(Math.random()); }结果&#xff1a;0.3598613895606426 0.2666778145365811 0.25090731064243355 …

MySQL为Null会导致5个问题,个个致命!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;正式开始之前&#xff0c;我们先来看下 MySQL 服务器的配置和版本号信息&#xff0c;如下图所示&#xff1a;“兵马未动粮草…

Spring Boot 解决跨域问题的 3 种方案!

作者 | telami来源 | telami.cn/2019/springboot-resolve-cors前后端分离大势所趋&#xff0c;跨域问题更是老生常谈&#xff0c;随便用标题去google或百度一下&#xff0c;能搜出一大片解决方案&#xff0c;那么为啥又要写一遍呢&#xff0c;不急往下看。问题背景&#xff1a;…

SpringBoot集成Google开源图片处理框架,贼好用!

1、序在实际开发中&#xff0c;难免会对图片进行一些处理&#xff0c;比如图片压缩之类的&#xff0c;而其中压缩可能就是最为常见的。最近&#xff0c;我就被要求实现这个功能&#xff0c;原因是客户那边嫌速度过慢。借此机会&#xff0c;今儿就给大家介绍一些一下我做这个功能…

推荐一款开源数据库设计工具,比PowerDesigner更好用!

最近有个新项目刚过完需求&#xff0c;正式进入数据库表结构设计阶段&#xff0c;公司规定统一用数据建模工具 PowerDesigner。但我并不是太爱用这个工具&#xff0c;因为它的功能实在是太多了&#xff0c;显得很臃肿繁琐&#xff0c;而平时设计表用的也就那么几个功能。这里找…

cocos2d-x lua 学习笔记(1) -- 环境搭建

Cocos2d-x 3.0以上版本的环境搭建和之前的Cocos2d-x 2.0 版差异较大的,同时从Cocos2d-x 3.0项目打包成apk安卓应用文件&#xff0c;搭建安卓环境的步骤有点繁琐&#xff0c;但搭建一次之后&#xff0c;以后就会非常快捷&#xff01;OK&#xff0c;现在就开始搭建环境吧&#xf…

Socket粘包问题的3种解决方案,最后一种最完美!

作者 | 王磊来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 语言中&#xff0c;传统的 Socket 编程分为两种实现方式&#xff0c;这两种实现方式也对应着两种不同的传输层协议…

【万里征程——Windows App开发】控件大集合1

添加控件的方式有多种&#xff0c;大家更喜欢哪一种呢&#xff1f; 1&#xff09;使用诸如 Blend for Visual Studio 或 Microsoft Visual Studio XAML 设计器的设计工具。 2&#xff09;在 Visual Studio XAML 编辑器中将控件添加到 XAML 标记中。 3&#xff09;在代码中添…

从String中移除空白字符的多种方式!?差别竟然这么大!

字符串&#xff0c;是Java中最常用的一个数据类型了。我们在日常开发时候会经常使用字符串做很多的操作。比如字符串的拼接、截断、替换等。这一篇文章&#xff0c;我们介绍一个比较常见又容易被忽略的一个操作&#xff0c;那就是移除字符串中的空格。其实&#xff0c;在Java中…