「 代码性能优化 」作为一名Java程序员,你真的了解 synchronized 吗?(三)

简介: 文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。更多、更体系化的内容请持续关注博主,您的 关注、点赞、收藏 都将是小编持续创作的动力!

前言

文接上篇,本文将继续介绍 Synchronized,感兴趣的小伙伴继续跟博主一起讨论下。

更多、更体系化的内容请持续关注博主,您的 关注、点赞、收藏 都将是小编持续创作的动力!

Synchronized关键字用法

前文中提到synchronized 的用法可以从两个维度上面分类,下面蒋总结讨论具体用法:

1. 根据修饰对象分类

1.1 修饰代码块

synchronized(this|object) {}

synchronized(类.class) {}

1.2 修饰方法

修饰非静态方法

修饰静态方法

2. 根据获取的锁分类

2.1 获取对象锁

synchronized(this|object) {}

修饰非静态方法

2.2 获取类锁

synchronized(类.class) {}

修饰静态方法

Synchronized修饰实例方法

示例:

publicclassSyncDemo {
privateintcount;
publicsynchronizedvoidadd() {
count++;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
SyncDemosyncDemo=newSyncDemo();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
syncDemo.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
syncDemo.add();
      }
    });
t1.start();
t2.start();
t1.join(); // 阻塞住线程等待线程 t1 执行完成t2.join(); // 阻塞住线程等待线程 t2 执行完成System.out.println(syncDemo.count);// 输出结果为 20000  }
}

 

在上面的代码当中的add方法只有一个简单的count++操作,因为这个方法是使用synchronized修饰的因此每一个时刻只能有一个线程执行add方法,因此上面打印的结果是20000。如果add方法没有使用synchronized修饰的话,那么线程t1和线程t2就可以同时执行add方法,这可能会导致最终count的结果小于20000,因为count++操作不具备原子性。

Synchronized修饰静态方法

示例:

publicclassSyncDemo {
privatestaticintcount;
publicstaticsynchronizedvoidadd() {
count++; 
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
SyncDemo.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
SyncDemo.add();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(SyncDemo.count); // 输出结果为 20000  }
}

上面的代码最终输出的结果也是20000,但是与前一个程序不同的是。这里的add方法用static修饰的,在这种情况下真正的只能有一个线程进入到add代码块,因为用static修饰的话是所有对象公共的,因此和前面的那种情况不同,不存在两个不同的线程同一时刻执行add方法。

Sychronized修饰多个方法

示例:

publicclassAddMinus {
publicstaticintans;
publicstaticsynchronizedvoidadd() {
ans++;
  }
publicstaticsynchronizedvoidminus() {
ans--;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
AddMinus.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
AddMinus.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(AddMinus.ans); // 输出结果为 0  }
}

在上面的代码当中我们用synchronized修饰了两个方法,addminus。这意味着在同一个时刻这两个函数只能够有一个被一个线程执行,也正是因为addminus函数在同一个时刻只能有一个函数被一个线程执行,这才会导致ans最终输出的结果等于0。

对于一个实例对象来说:

publicclassAddMinus {
publicintans;
publicsynchronizedvoidadd() {
ans++;
  }
publicsynchronizedvoidminus() {
ans--;
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
AddMinusaddMinus=newAddMinus();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
addMinus.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
addMinus.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(addMinus.ans);
  }
}

上面的代码没有使用static关键字,因此我们需要new出一个实例对象才能够调用addminus方法,但是同样对于AddMinus的实例对象来说同一个时刻只能有一个线程在执行add或者minus方法,因此上面代码的输出同样是0。

Synchronized修饰实例方法代码块

示例:

publicclassCodeBlock {
privateintcount;
publicvoidadd() {
System.out.println("进入了 add 方法");
synchronized (this) {
count++;
    }
  }
publicvoidminus() {
System.out.println("进入了 minus 方法");
synchronized (this) {
count--;
    }
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
CodeBlockcodeBlock=newCodeBlock();
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
codeBlock.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
codeBlock.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(codeBlock.count); // 输出结果为 0  }
}

上面的代码当中addminus方法没有使用synchronized进行修饰,因此一个时刻可以有多个线程执行这个两个方法。在上面的synchronized代码块当中我们使用了this对象作为锁对象,只有拿到这个锁对象的线程才能够进入代码块执行,而在同一个时刻只能有一个线程能够获得锁对象。也就是说add函数和minus函数用synchronized修饰的两个代码块同一个时刻只能有一个代码块的代码能够被一个线程执行,因此上面的结果同样是0。

Synchronized修饰静态代码块

示例:

publicclassCodeBlock {
privatestaticintcount;
publicstaticvoidadd() {
System.out.println("进入了 add 方法");
synchronized (CodeBlock.class) {
count++;
    }
  }
publicstaticvoidminus() {
System.out.println("进入了 minus 方法");
synchronized (CodeBlock.class) {
count--;
    }
  }
publicstaticvoidmain(String[] args) throwsInterruptedException {
Threadt1=newThread(() -> {
for (inti=0; i<10000; i++) {
CodeBlock.add();
      }
    });
Threadt2=newThread(() -> {
for (inti=0; i<10000; i++) {
CodeBlock.minus();
      }
    });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(CodeBlock.count);
  }
}

上面代码的锁对象是CodeBlock.class,这个时候他不再是锁住一个对象了,而是一个类了,这个时候的并发度就变小了,当锁对象是CodeBlock.class的时候,实例对象之间时不能够并发的,因为这个时候的锁对象是一个类。

总结

Synchronized修饰实例方法:不同的对象之间是可以并发的;
Synchronized修饰静态实例方法:不同的对象是不能并发的,但是不同的类之间可以进行并发;
Sychronized修饰多个方法:多个方法在同一时刻只能有一个方法被执行,而且只能有一个线程能够执行;
Synchronized修饰实例方法代码块:同一个时刻只能有一个线程执行代码块;
Synchronized修饰静态代码块:同一个时刻只能有一个线程执行这个代码块,而且不同的对象之间不能够进行并发;

参考&致谢

深入Synchronized各种使用方法

莫笑少年江湖梦,谁不少年梦江湖.感谢前人的经验、分享和付出,让我们可以有机会站在巨人的肩膀上眺望星辰大海!

相关文章
|
1月前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
333 4
|
1月前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
234 115
|
1月前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
171 98
|
1月前
|
Java 编译器 API
java最新版和java8的区别,用代码展示
java最新版和java8的区别,用代码展示
230 43
|
1月前
|
安全 Java 容器
告别空指针噩梦:Optional让Java代码更优雅
告别空指针噩梦:Optional让Java代码更优雅
352 94
|
算法 安全 Java
Java 性能优化:35个小细节,让你提升Java代码运行的效率
  代码优化,一个很重要的课题。可能有些人觉得没用,一些细小的地方有什么好修改的,改与不改对于代码的运行效率有什么影响呢?这个问题我是这么考虑的,就像大海里面的鲸鱼一样,它吃一条小虾米有用吗?没用,但是,吃的小虾米一多之后,鲸鱼就被喂饱了。   代码优化也是一样,如果项目着眼于尽快无BUG上线,那么此时可以抓大放小,代码的细节可以不精打细磨;但是如果有足够的时间开发、维护代码,这时候就必须考虑每个可以优化的细节了,一个一个细小的优化点累积起来,对于代码的运行效率绝对是有提升的。
349 0
|
机器学习/深度学习 算法 Java
11月27日云栖精选夜读 | Java性能优化的50个细节
在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身。养成良好的编码习惯非常重要,能够显著地提升程序性能。 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 第一,控制资源的使用,通过线程同步来控制资源的并发访问; 第二,控制实例的产生,以达到节约资源的目的; 第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
3096 0
|
Java 程序员 Android开发
10月31日云栖精选夜读 | Java性能优化的50个细节(珍藏版)
在JAVA程序中,性能问题的大部分原因并不在于JAVA语言,而是程序本身。养成良好的编码习惯非常重要,能够显著地提升程序性能。 1. 尽量在合适的场合使用单例 使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面: 第一,控制资源的使用,通过线程同步来控制资源的并发访问; 第二,控制实例的产生,以达到节约资源的目的; 第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。
3103 0
下一篇
oss云网关配置