synchronized用法详解

目录

1.线程安全问题

2.使用同步机制管理共享数据

3.synchronized原理概述

3.1 同步代码块的实现原理

3.2 同步方法的原理

4.synchronized的使用

4.1同步代码块

4.2同步普通方法

4.3同步静态方法

5.synchronized的不可中断性与可重入性

5.1 不可中断性

5.2 可重入性

6.使用synchronized的注意事项

6.1注意synchronized同步块的粒度

6.2 对 String 加锁

6.3 对Integer、Long、Short等包装类加锁


1.线程安全问题

       线程允许程序控制流的多重分支同时存在于一个进程内,它们共享进程范围内的资源,比如内存和文件句柄,但每一个线程都有其自己的程序计数器、栈和本地变量。线程也称为是轻量级的进程,因为线程共享其所属进程的内存地址空间,因此同一进程内的所有线程访问相同的变量,从同一个堆中分配对象,这相对于进程间通信来说实现了良好的数据共享。这是多线程的好处。

       但是,因为线程共享相同的内存地址空间,且并发地运行,它们可能访问或修改其他线程正在使用的变量,当数据意外改变时,如果没有明确的同步来管理共享数据,可能会造成混乱,从而产生意外的结果,引发线程安全问题。造成线程安全问题的原因归结为:

  • 存在共享数据(也称临界资源);
  • 存在多条线程共同操作这些共享数据。

       比如一个简单的场景:取钱。用户输入取款金额,然后系统判断账户余额是否足够,如果足够则取款成功,否则取款失败。

       1、Account账户类,提供draw方法来完成取款操作

/**
 * @author yedashi
 * @version 1.0
 * @date 2022/5/13 9:34
 * @description
 */
public class Account {

    //  账户编号
    private String accountNo;

    // 余额
    private double balance;

    public Account() {}

    public Account(String accountNo, double balance) {
        this.accountNo = accountNo;
        this.balance = balance;
    }

    public String getAccountNo() {
        return accountNo;
    }

    public void setAccountNo(String accountNo) {
        this.accountNo = accountNo;
    }

    // 余额不能随便修改,所以只提供get方法
    public double getBalance() {
        return balance;
    }

    // 提供一个raw方法来完成取钱操作
    public void draw(double drawAccount) {
        //  账户余额大于所取的钱数
        if(balance >= drawAccount) {
            // 吐出钞票
            System.out.println(Thread.currentThread().getName() + " 取钱成功!吐出钞票:" + drawAccount);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 修改余额
            balance -= drawAccount;
            System.out.println("余额为:" + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 取钱失败!余额不足!");
        }
    }
}

      2、取钱线程类,模拟取款操作:

/**
 * @author yedashi
 * @version 1.0
 * @date 2022/5/13 9:41
 * @description
 */
public class DrawThread extends Thread {

    private Account account;
    // 当前线程所希望取到的钱数
    private double drawAccount;

    public DrawThread(String name, Account account, double drawAccount) {
        super(name);
        this.account = account;
        this.drawAccount = drawAccount;
    }

    @Override
    public void run() {
        account.draw(drawAccount);
    }
}

      3、测试,同时启动两个线程,对同一个账户进行取款操作:

package com.stone.crazy.java.ch16.se05;

/**
 * @author yedashi
 * @version 1.0
 * @date 2022/4/19 9:03
 * @description
 */
public class DrawTest {

    public static void main(String[] args) {
        Account account = new Account("1234567", 1000);
        new DrawThread("甲", account, 800).start();
        new DrawThread("乙", account, 800).start();
    }
}

      测试结果 如下:

synchronized用法详解_第1张图片

       从结果得知,账户余额为1000,却总共取出了1600,明显错误了(多次运行,会得到不同的结果,甲乙两个线程取款的顺利也可能不同)。

2.使用同步机制管理共享数据

       针对上面的问题,Java提供了同步机制来协调多线程对共享数据

你可能感兴趣的:(Java并发编程,java,开发语言)