• 一文彻底理解synchronized(通俗易懂的synchronized)


    目录

    一、什么是synchronized

    二、synchronized的四种用法

    2.1、修饰一个代码块

    2.2、修饰一个方法

    2.3、修饰一个静态的方法

    2.4、修饰一个类

    三、使用案例分析

    3.1、修饰一个代码块

    3.2、修饰一个方法

    3.3、修饰一个静态的方法

    3.4、修饰一个类

    3.5 经典用法:

    总结


     

    一、什么是synchronized

    synchronized 是 Java 中的关键字,是一种同步锁主要应用于多线程环境下保证线程的安全性。

    二、synchronized的四种用法

    2.1、修饰一个代码块

             被修饰的代码块称为同步语句块,其作用的范围是大括号{} 括起来的代码,作用的对象是调用这个代码块的对象;

    2.2、修饰一个方法

            被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

            虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方 法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这 个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上 synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方 法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此, 子类的方法也就相当于同步了。

    2.3、修饰一个静态的方法

    其作用的范围是整个静态方法,作用的对象是这个类的 所有对象;

    2.4、修饰一个类

    其作用的范围是synchronized后面括号括起来的部分,作用对象是这个类的所有对象。

    三、使用案例分析

    3.1、修饰一个代码块

    1) 一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。

    1. class SyncThread implements Runnable {
    2. private static int count;
    3. public SyncThread() {
    4. count = 0;
    5. }
    6. public void run() {
    7. synchronized(this) {
    8. for (int i = 0; i < 5; i++) {
    9. try {
    10. System.out.println(Thread.currentThread().getName() + ":" + (count++));
    11. Thread.sleep(100);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. }
    17. }
    18. public int getCount() {
    19. return count;
    20. }
    21. }
    22. public class SynchronizedDemo {
    23. public static void main(String args[]){
    24. //test01
    25. // SyncThread s1 = new SyncThread();
    26. // SyncThread s2 = new SyncThread();
    27. // Thread t1 = new Thread(s1);
    28. // Thread t2 = new Thread(s2);
    29. //test02
    30. SyncThread s = new SyncThread();
    31. Thread t1 = new Thread(s);
    32. Thread t2 = new Thread(s);
    33. t1.start();
    34. t2.start();
    35. }
    36. }

    Test01

    test02

    从运行结果test02可以看出当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象

    为什么上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联。

    2)当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。(作业:自行验证)

    3)指定要给某个对象加锁

    1. package CompleteFuture;
    2. /**
    3. * 银行账户类
    4. */
    5. class Account {
    6. String name;
    7. float amount;
    8. public Account(String name, float amount) {
    9. this.name = name;
    10. this.amount = amount;
    11. }
    12. //存钱
    13. public void deposit(float amt) {
    14. amount += amt;
    15. try {
    16. Thread.sleep(100);
    17. } catch (InterruptedException e) {
    18. e.printStackTrace();
    19. }
    20. }
    21. //取钱
    22. public void withdraw(float amt) {
    23. amount -= amt;
    24. try {
    25. Thread.sleep(100);
    26. } catch (InterruptedException e) {
    27. e.printStackTrace();
    28. }
    29. }
    30. public float getBalance() {
    31. return amount;
    32. }
    33. }
    34. /**
    35. * 账户操作类
    36. */
    37. class AccountOperator implements Runnable{
    38. private Account account;
    39. public AccountOperator(Account account) {
    40. this.account = account;
    41. }
    42. public void run() {
    43. synchronized (account) {
    44. account.deposit(500);
    45. account.withdraw(500);
    46. System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
    47. }
    48. }
    49. }
    50. public class SynchDemo2 {
    51. //public static final Object signal = new Object(); // 线程间通信变量
    52. //将account改为Demo00.signal也能实现线程同步
    53. public static void main(String args[]){
    54. Account account = new Account("zhang san", 10000.0f);
    55. AccountOperator accountOperator = new AccountOperator(account);
    56. final int THREAD_NUM = 5;
    57. Thread threads[] = new Thread[THREAD_NUM];
    58. for (int i = 0; i < THREAD_NUM; i ++) {
    59. threads[i] = new Thread(accountOperator, "Thread" + i);
    60. threads[i].start();
    61. }
    62. }
    63. }

    在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。 


    3.3 .1当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。

    1. public void method3(SomeObject obj)
    2. {
    3. //obj 锁定的对象
    4. synchronized(obj)
    5. {
    6. // todo
    7. }
    8. }

    3.3.2 当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

    1. class Test implements Runnable
    2. {
    3. private byte[] lock = new byte[0]; // 特殊的instance变量
    4. public void method()
    5. {
    6. synchronized(lock) {
    7. // todo 同步代码块
    8. }
    9. }
    10. public void run() {
    11. }
    12. }

    3.2、修饰一个方法

    1. public void method()
    2. {
    3. synchronized(this) {
    4. // todo
    5. }
    6. }

    在子类方法中加上synchronized关键字

    1. class Parent {
    2. public synchronized void method() { }
    3. }
    4. class Child extends Parent {
    5. public synchronized void method() { }
    6. }

    在子类方法中调用父类的同步方法

    1. class Parent {
    2. public synchronized void method() { }
    3. }
    4. class Child extends Parent {
    5. public void method() { super.method(); }
    6. }
    1. 在定义接口方法时不能使用synchronized关键字。
    2. 构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。

    3.3、修饰一个静态的方法

    1. /**
    2. * 同步线程
    3. */
    4. class SyncThread implements Runnable {
    5. private static int count;
    6. public SyncThread() {
    7. count = 0;
    8. }
    9. public synchronized static void method() {
    10. for (int i = 0; i < 5; i ++) {
    11. try {
    12. System.out.println(Thread.currentThread().getName() + ":" + (count++));
    13. Thread.sleep(100);
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }
    17. }
    18. }
    19. public synchronized void run() {
    20. method();
    21. }
    22. }
    23. public class Demo00{
    24. public static void main(String args[]){
    25. SyncThread syncThread1 = new SyncThread();
    26. SyncThread syncThread2 = new SyncThread();
    27. Thread t1 = new Thread(syncThread1, "SyncThread1");
    28. Thread t2 = new Thread(syncThread2, "SyncThread2");
    29. t1.start();
    30. t2.start();
    31. }
    32. }

    syncThread1和syncThread2是SyncThread的两个对象,但在t1和t2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。

    3.4、修饰一个类

    其作用的范围是synchronized后面括号括起来的部分,作用对象是这个类的所有对象。

    1. /**
    2. * 同步线程
    3. */
    4. class SyncThread implements Runnable {
    5. private static int count;
    6. public SyncThread() {
    7. count = 0;
    8. }
    9. public static void method() {
    10. synchronized(SyncThread.class) {
    11. for (int i = 0; i < 5; i ++) {
    12. try {
    13. System.out.println(Thread.currentThread().getName() + ":" + (count++));
    14. Thread.sleep(100);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. }
    19. }
    20. }
    21. public synchronized void run() {
    22. method();
    23. }
    24. }

    3.5 经典用法:

    消费者与生产者

    1. package CompleteFuture;
    2. import java.util.Random;
    3. public class ProducerAndConsumer {
    4. public static void main(String[] args) {
    5. //多线程如何编写
    6. // 1、线程操作资源类
    7. // 2、创建资源类 ,在资源类中创建属性和操作资源方法
    8. Product product = new Product();
    9. for (int i = 1; i < 10; i++) {
    10. new Thread(new Runnable() {
    11. @Override
    12. public void run() {
    13. product.product();
    14. }
    15. },"生产者: "+i).start();
    16. }
    17. for (int i = 1; i <10; i++) {
    18. new Thread(new Runnable() {
    19. @Override
    20. public void run() {
    21. product.consume();
    22. }
    23. },"消费者: "+i).start();
    24. }
    25. }
    26. }
    27. // 资源类
    28. class Product{
    29. private volatile int num = 0;
    30. public synchronized void product(){
    31. // 1、馒头有的多我就可以不生产
    32. while (num !=0){
    33. try {
    34. this.wait();
    35. } catch (InterruptedException e) {
    36. e.printStackTrace();
    37. }
    38. }
    39. // 一次生产五个
    40. for (int i = 0; i <5 ; i++) {
    41. ++num;
    42. }
    43. // 产生了馒头通知消费者
    44. this.notifyAll();
    45. System.out.println(Thread.currentThread().getName()+"生产后剩余馒头:"+num);
    46. }
    47. public synchronized void consume(){
    48. // 1、如果没有馒头我就等待,阻塞消费
    49. while (num == 0){
    50. try {
    51. this.wait();
    52. } catch (InterruptedException e) {
    53. e.printStackTrace();
    54. }
    55. }
    56. // 一次吃2个
    57. for (int i = 0; i < 2; i++) {
    58. if(num>0) {
    59. --num;
    60. }
    61. }
    62. // 模拟消耗馒头1s
    63. try {
    64. Thread.sleep(200);
    65. } catch (InterruptedException e) {
    66. e.printStackTrace();
    67. }
    68. this.notifyAll();
    69. System.out.println(Thread.currentThread().getName()+"消费后剩余馒头:"+num);
    70. }
    71. }

    单例模式(双重检测):


     


    总结

    A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。 
    B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。 
    C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。


    本文主要整理的是synchronized主要用法,但它实际的原理没有进行详细的拆解。且看下回剖析。

  • 相关阅读:
    Qt 项目实战 | 多界面文本编辑器
    笔者认为所谓的产业互联网,就是一个产业与互联网深度融合的过程
    AI大模型低成本快速定制法宝:RAG和向量数据库
    k8s部署
    socket进行服务器和客户端通信
    安卓玩机教程---全机型安卓4----安卓12 框架xp edx lsp安装方法
    寄存器、缓存、内存(虚拟、物理地址)、DDR、RAM的关系
    【Docker/K8S/Racher】Docker/K8S/Racher安装Redis-20220817
    并发中级(第一篇)
    理想之光不灭
  • 原文地址:https://blog.csdn.net/u010445301/article/details/133154016