17 synchronized关键字使用 synchronized方法、synchronized块

synchronized方法、synchronized块

线程的同步

并发:同一个对象被多个线程同时操作。

解决方案:让多个线程排队操作对象。

使用队列和锁解决多线程的并发问题。
同进程的多线程共享同一块存储空间,当多个线程同时访问某块内存空间时,就存在并发问题。为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized;当线程获得对象的排它锁,该线程独占资源,其他线程必须等待其使用后释放锁即可。

同时也伴随着一些问题:

  • 一个线程持有锁导致其它线程所有需要此锁的线程挂起
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,导致性能问题
  • 也可能存在优先级高的线程等待优先级低的线程释放锁,而导致性能问题。

不安全的线程

通过下面例子说明,线程是不安全。

重复执行下面方法,执行结果可能会有多种情况。

示例1:

java 复制代码
//每次执行结果都不是我们预想的结果
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"张三").start();
        new Thread(buyTicket,"李四").start();
        new Thread(buyTicket,"王五").start();

    }
}

class BuyTicket implements Runnable {

    private int ticketNums = 10;
    boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            buy();
        }

    }
    //synchronized 同步方法 锁的是this 即BuyTicket对象
    private /**synchronized*/ void buy() {
        if (ticketNums <= 0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "买到票" + ticketNums--);
    }
}
java 复制代码
张三买到票9
李四买到票8
王五买到票10
李四买到票7
王五买到票6
张三买到票7
张三买到票5
李四买到票4
王五买到票5
李四买到票3
张三买到票3
王五买到票3
王五买到票2
张三买到票2
李四买到票2
李四买到票1
王五买到票1
张三买到票1

示例2

java 复制代码
public class UnsafeBank {
    public static void main(String[] args) {
        Account funds = new Account("家庭基金", 100);
        new TakeMoney(funds,60,"you").start();
        new TakeMoney(funds,80,"youWife").start();
    }


}

class Account{
    String name;
    int money;

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class TakeMoney extends Thread{
    Account account;
    int takeMoney;//取多钱钱
    int nowMoney;//你口袋里面的钱

    public TakeMoney(Account account, int takeMoney, String name) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    //synchronized run 是不行的
    @Override
    public void run() {
        //synchronized (account) {
            if ((account.money - takeMoney) < 0) {
                System.out.println(Thread.currentThread().getName() + "余额不足。");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - takeMoney;
            nowMoney = nowMoney + takeMoney;
            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(this.getName() + "口袋里面的钱:" + nowMoney);
        //}
    }
}
java 复制代码
//执行结果
家庭基金余额为:-40
家庭基金余额为:-40
you口袋里面的钱:60
youWife口袋里面的钱:80

示例3

java 复制代码
public class UnsafeList {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                //synchronized (list) {
                    list.add(Thread.currentThread().getName());
               // }
            }).start();
        }
        Thread.sleep(3000);//确保上面线程能够执行完毕后在打印list.size
        System.out.println(list.size());
    }
}
java 复制代码
//执行结果
9999

针对上面示例,添加synchronized关键字,使其变为线程安全的。

synchronized方法、synchronized块

关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性
synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得该方法对象的锁才能执行,否则会阻塞线程,方法执行时独占该锁,知道方法执行完毕才会释放锁。
同步块:synchronized(object){}

synchronized修饰的代码块,会在object对象上加一个监视器。而同步方法会在当前对象this上增加一个监视器。

synchronized同步代码块一般加载共享资源对象上。

java 复制代码
 //示例1 将buy方法改为同步方法
 private synchronized void buy()
 //示例2 试图按照示例1的方案 将run方法改为同步方法,验证是不行的,因为加上run方法的synchronized将对TakeMoney类添加的监视器,但最终操作的确是account对象,
 //此示例 多线程操作的共共享资源是account,并非TakeMoney
 //示例3 在共享资源list增加synchronized关键字,为其添加监视器
相关推荐
雾月5512 分钟前
LeetCode 1292 元素和小于等于阈值的正方形的最大边长
java·数据结构·算法·leetcode·职场和发展
24k小善1 小时前
Flink TaskManager详解
java·大数据·flink·云计算
想不明白的过度思考者1 小时前
Java从入门到“放弃”(精通)之旅——JavaSE终篇(异常)
java·开发语言
.生产的驴2 小时前
SpringBoot 封装统一API返回格式对象 标准化开发 请求封装 统一格式处理
java·数据库·spring boot·后端·spring·eclipse·maven
猿周LV2 小时前
JMeter 安装及使用 [软件测试工具]
java·测试工具·jmeter·单元测试·压力测试
晨集2 小时前
Uni-App 多端电子合同开源项目介绍
java·spring boot·uni-app·电子合同
时间之城2 小时前
笔记:记一次使用EasyExcel重写convertToExcelData方法无法读取@ExcelDictFormat注解的问题(已解决)
java·spring boot·笔记·spring·excel
椰羊~王小美2 小时前
LeetCode -- Flora -- edit 2025-04-25
java·开发语言
凯酱2 小时前
MyBatis-Plus分页插件的使用
java·tomcat·mybatis
程序员总部2 小时前
如何在IDEA中高效使用Test注解进行单元测试?
java·单元测试·intellij-idea