「 代码性能优化 」作为一名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各种使用方法

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

相关文章
|
7天前
|
安全 Java API
Java 17新特性让你的代码起飞!
【10月更文挑战第4天】自Java 8发布以来,Java语言经历了多次重大更新,每一次都引入了令人兴奋的新特性,极大地提升了开发效率和代码质量。本文将带你从Java 8一路走到Java 17,探索那些能让你的代码起飞的关键特性。
32 1
|
6天前
|
Java 程序员 API
Java中的Lambda表达式:简化代码的秘密武器
【10月更文挑战第11天】 在Java编程中,Lambda表达式是一种简洁而强大的工具,它允许我们将函数作为参数传递给其他方法。本文将介绍Lambda表达式的基本概念、使用方法以及在实际项目中的应用案例,帮助你更好地理解和利用这一特性来简化代码。
21 8
|
4天前
|
Java 开发者
在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。
【10月更文挑战第13天】在Java编程中,正确的命名规范不仅能提升代码的可读性和可维护性,还能有效避免命名冲突。本文将带你深入了解Java命名规则,包括标识符的基本规则、变量和方法的命名方式、常量的命名习惯以及如何避免关键字冲突,通过实例解析,助你写出更规范、优雅的代码。
25 3
|
4天前
|
Java 程序员
在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。
【10月更文挑战第13天】在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。本文介绍了Java关键字的基本概念及其重要性,并通过定义类和对象、控制流程、访问修饰符等示例,展示了关键字的实际应用。掌握这些关键字,是成为优秀Java程序员的基础。
11 3
|
7天前
|
Java 编译器 API
从Java 8到Java 17,这些新特性让你的代码起飞!
【10月更文挑战第10天】在软件开发领域,Java作为一种历史悠久且广泛使用的编程语言,不断进化以适应新的需求和挑战。从Java 8到Java 17,每一次版本更新都带来了诸多新特性和改进,极大地提升了开发效率和代码质量。今天,我们就来一起探讨这些新特性,看看它们是如何让我们的代码“起飞”的。
69 0
|
3月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
32 0
|
3月前
|
安全 Java 开发者
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
55 0
|
5月前
|
安全 Java 编译器
Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字(一)
线程安全问题是多线程编程中最典型的一类问题之一。如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的。 通俗来说,线程不安全指的就是某一代码在多线程环境下执行会出现bug,而在单线程环境下执行就不会。线程安全问题本质上是由于线程之间的调度顺序的不确定性,正是这样的不确定性,给我们的代码带来了很多“变数”。 本文将对Java多线程编程中,线程安全问题展开详细的讲解。
94 0
|
5月前
|
安全 Java 调度
Java多线程- synchronized关键字总结
Java多线程- synchronized关键字总结
46 0
|
安全 Java 数据安全/隐私保护
Java基础进阶多线程-线程安全和synchronized关键字
Java基础进阶多线程-线程安全和synchronized关键字
Java基础进阶多线程-线程安全和synchronized关键字