我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Javasynchronized同步方法详解

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Javasynchronized同步方法详解

面试题:

1.如何保证多线程下 i++ 结果正确?

2.一个线程如果出现了运行时异常会怎么样?

3.一个线程运行时发生异常会怎样?

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

(1) 阻塞式的解决方案:synchronized,Lock

(2) 非阻塞式的解决方案:原子变量

synchronized 即俗称的【对象锁】,它采用互斥的方式让同一 时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁 的线程可以安全的执行临界区内的代码,不用担心线程上下文切换。

1. synchronized 同步方法

当使用synchronized关键字修饰一个方法的时候,该方法被声明为同步方法,关键字synchronized的位置处于同步方法的返回类型之前。

public class SafeDemo {
    // 临界区资源
    private static int i = 0;

    // 临界区代码
    public void selfIncrement(){
        for(int j=0;j<5000;j++){
            i++;
        }
    }

    public int getI(){
        return i;
    }
}
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        SafeDemo safeDemo = new SafeDemo();
        // 线程1和线程2同时执行临界区代码段
        Thread t1 = new Thread(()->{
            safeDemo.selfIncrement();
        });
        Thread t2 = new Thread(()->{
            safeDemo.selfIncrement();
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(safeDemo.getI()); // 9906
    }
}

可以发现,当2个线程同时访问临界区的selfIncrement()方法时,就会出现竞态条件的问题,即2个线程在临界区代码段的并发执行结果因为代码的执行顺序不同而导致结果无法预测,每次运行都会得到不一样的结果。因此,为了避免竞态条件的问题,我们必须保证临界区代码段操作具备排他性。这就意味着当一个线程进入临界区代码段执行时,其他线程不能进入临界区代码段执行。

现在使用synchronized关键字对临界区代码段进行保护,代码如下:

public class SafeDemo {
    // 临界区资源
    private static int i = 0;

    // 临界区代码使用synchronized关键字进行保护
    public synchronized void selfIncrement(){
        for(int j=0;j<5000;j++){
            i++;
        }
    }

    public int getI(){
        return i;
    }
}

经过多次运行测试用例程序,累加10000次之后,最终的结果不再有偏差,与预期的结果(10000)是相同的。

在方法声明中设置synchronized同步关键字,保证其方法的代码执行流程是排他性的。任何时间只允许一个线程进入同步方法(临界区代码段),如果其他线程需要执行同一个方法,那么只能等待和排队。

2. synchronized 方法将对象作为锁

定义线程的执行逻辑:

public class ThreadTask {
	
    // 临界区代码使用synchronized关键字进行保护
    public synchronized void test() {
        try {
            System.out.println(Thread.currentThread().getName()+" begin");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName()+" end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

分别创建两个线程,在两个线程的执行体中执行线程逻辑:

public class ThreadA extends Thread {

    ThreadTask threadTask ;

    public ThreadA(ThreadTask threadTask){
        super();
        this.threadTask = threadTask;
    }

    @Override
    public void run() {
        threadTask.test();
    }
}
public class ThreadB extends Thread {
    ThreadTask threadTask ;

    public ThreadB(ThreadTask threadTask){
        super();
        this.threadTask = threadTask;
    }

    @Override
    public void run() {
        threadTask.test();
    }
}

创建一个锁对象,传给两个线程:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadTask threadTask = new ThreadTask();
        ThreadA t1 = new ThreadA(threadTask);
        ThreadB t2 = new ThreadB(threadTask);
        t1.start();
        t2.start();
    }
}

执行结果:

Thread-0 begin
Thread-0 end
Thread-1 begin
Thread-1 end

这里两个线程的锁对象都是threadTask,所以同一时间只有一个线程能拿到这个锁对象,执行同步代码块。另外,需要牢牢记住“共享”这两个字,只有共享资源的写访问才需要同步化,如果不是共享资源,那么就没有同步的必要。

总结:

(1) A线程先持有object对象的锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需等待,也就是同步;

(2) 在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象;

(3) 在Java中只有将对象作为锁,并没有锁方法这种说法;

(4) 在Java语言中,锁就是对象,对象可以映射成锁,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的synchronized同步方法;

(5) 如果在X对象中使用了synchronized关键字声明非静态方法,则X对象就被当成锁;

3. 多个锁对象

创建两个线程执行逻辑ThreadTask对象,即产生了两把锁

public class Main {
    public static void main(String[] args) throws InterruptedException {
        ThreadTask threadTask1 = new ThreadTask();
        ThreadTask threadTask2 = new ThreadTask();
        // 两个线程分别执行两个不同的线程执行逻辑对象
        ThreadA t1 = new ThreadA(threadTask1);
        ThreadB t2 = new ThreadB(threadTask2);
        t1.start();
        t2.start();
    }
}

执行结果:

Thread-0 begin
Thread-1 begin
Thread-0 end
Thread-1 end

test()方法使用了synchronized关键字,任何时间只允许一个线程进入同步方法,如果其他线程需要执行同一个方法,那么只能等待和排队。执行结果呈现了两个线程交叉输出的效果,说明两个线程以异步方式同时运行。

在系统中产生了两个锁,ThreadA的锁对象是threadTask1,ThreadB的锁对象是threadTas2,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在锁的争抢关系,所以运行结果是异步的。

synchronized方法的同步锁实质上使用了this对象锁,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象作为锁(哪个对象调用了带有synchronized关键字的方法,哪个对象就是锁),其他线程只能等待,前提是多个线程访问的是同一个对象。

4. 如果同步方法内的线程抛出异常会发生什么?

public class SafeDemo {
    public synchronized void selfIncrement(){
        if(Thread.currentThread().getName().equals("t1")){
            System.out.println("t1 线程正在运行");
            int a=1;
            // 死循环,只要t1线程没有执行完这个方法,就不会释放锁
            while (a==1){
                
            }
        }else{
            System.out.println("t2 线程正在运行");
        }
    }
}
public class SafeDemo {
    public synchronized void selfIncrement(){
        if(Thread.currentThread().getName().equals("t1")){
            System.out.println("t1 线程正在运行");
            int a=1;
            while (a==1){
                Integer.parseInt("a");
            }
        }else{
            System.out.println("t2 线程正在运行");
        }
    }
}

执行结果:t2线程得不到执行

t1 线程正在运行

此时,如果我们在同步方法中制造一个异常:

public class SafeDemo {
    public synchronized void selfIncrement(){
        if(Thread.currentThread().getName().equals("t1")){
            System.out.println("t1 线程正在运行");
            int a=1;
            while (a==1){
                Integer.parseInt("a");
            }
        }else{
            System.out.println("t2 线程正在运行");
        }
    }
}

在这里插入图片描述

线程t1出现异常并释放锁,线程t2进入方法正常输出,说明出现异常时,锁被自动释放了。

5. 静态的同步方法

在Java世界里一切皆对象。Java有两种对象:Object实例对象和Class对象。每个类运行时的类型信息用Class对象表示,它包含与类名称、继承关系、字段、方法有关的信息。JVM将一个类加载入自己的方法区内存时,会为其创建一个Class对象,对于一个类来说其Class对象是唯一的。Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机调用类加载器中的defineClass方法自动构造的,因此不能显式地声明一个Class对象。

普通的synchronized实例方法,其同步锁是当前对象this的监视锁。如果某个synchronized方法是static(静态)方法,而不是普通的对象实例方法,其同步锁又是什么呢?

public class StaticSafe {
    // 临界资源
    private static int count = 0;
    // 使用synchronized关键字修饰static方法
    public static synchronized void test(){
        count++;
    }
}

静态方法属于Class实例而不是单个Object实例,在静态方法内部是不可以访问Object实例的this引用的。所以,修饰static方法的synchronized关键字就没有办法获得Object实例的this对象的监视锁。

实际上,使用synchronized关键字修饰static方法时,synchronized的同步锁并不是普通Object对象的监视锁,而是类所对应的Class对象的监视锁。

为了以示区分,这里将Object对象的监视锁叫作对象锁,将Class对象的监视锁叫作类锁。当synchronized关键字修饰static方法时,同步锁为类锁;当synchronized关键字修饰普通的成员方法时,同步锁为对象锁。由于类的对象实例可以有很多,但是每个类只有一个Class实例,因此使用类锁作为synchronized的同步锁时会造成同一个JVM内的所有线程只能互斥地进入临界区段。

public class StaticSafe {
    // 临界资源
    private static int count = 0;
    // 对JVM内的所有线程同步
    public static synchronized void test(){
        count++;
    }
}
z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z'z

所以,使用synchronized关键字修饰static方法是非常粗粒度的同步机制。

通过synchronized关键字所抢占的同步锁什么时候释放呢?一种场景是synchronized块(代码块或者方法)正确执行完毕,监视锁自动释放;另一种场景是程序出现异常,非正常退出synchronized块,监视锁也会自动释放。所以,使用synchronized块时不必担心监视锁的释放问题。

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注编程网的更多内容!  

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Javasynchronized同步方法详解

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Javasynchronized与CAS使用方式详解

提到Java的知识点一定会有多线程,JDK版本不断的更迭很多新的概念和方法也都响应提出,但是多线程和线程安全一直是一个重要的关注点。比如说我们一入门就学习的synchronized怎么个实现和原理,还有总是被提到的CAS是啥,他和synchronized关系是啥?请往下看
2023-01-16

Java实现多线程同步五种方法详解

Java实现多线程同步的五种方法包括:1. synchronized关键字:使用synchronized关键字可以实现对共享资源的互斥访问。通过在方法或代码块前加上synchronized关键字,只有获得锁的线程才能执行该方法或代码块,其他
2023-08-14

详解微信小程序 同步异步解决办法

详解微信小程序 同步异步解决办法小程序中函数体还没有完成,下一个函数就开始执行了,而且两个函数之间需要传参。那是因为微信小程序函数是异步执行的。但微信小程序增加了ES6的promise特性支持,微信小程序新版本中移除了promise的支持,
2023-05-31

详解MySQL的半同步

前言年后在进行腾讯二面的时候,写完算法的后问的第一个问题就是,MySQL的半同步是什么?我当时直接懵了,我以为是问的MySQL的两阶段提交的问题呢?结果确认了一下后不是两阶段提交,然后面试官看我连问的是啥都不知道,就直接跳过这个问题,直接聊
2022-05-14

Win8打不开同步助手怎么办?Win8同步助手打不开解决方法详细步骤

Win8用户若遇到打不开同步助手的情况,有可能是因为您的系统未安装.NET Framework 3.5。您可以根据同步助手的引导来安装.NET Framework 3.5,若双击同步助手没有任何引导提示,则需要手动来安装.NET Frame
2023-06-05

浅谈同步监视器之同步代码块、同步方法

如果有多个线程访问共享资源,可能会出现当一个线程没有处理完业务,然后另一个线程进入,从而导致共享资源出现不安全的情况。日常例子:银行取钱,A和B有拥有同一个银行账户,A用存折在柜台取钱,B在取款机取钱。取钱有两个关键步骤:(1)判断账户里的
2023-05-31

Java线程同步的四种方式详解

这篇文章主要介绍了Java线程同步的四种方式详解,需要的朋友可以参考下
2023-02-18

Python同步方法怎么变为异步方法

本文小编为大家详细介绍“Python同步方法怎么变为异步方法”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python同步方法怎么变为异步方法”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。背景在我们平时的Fas
2023-06-29

Linux/CentOS系统同步网络时间的2种方法详解

由于硬件的原因,机器或多或少的跟标准时间对不上,一个月的误差几秒到几分钟不等。对于服务器来说时间不准,会有很多麻烦。例如,支付的时候,无法下单,游戏无法登录等。 方法一:用 ntpdate从时间服务器更新时间 如果系统没有 ntpdate
2022-06-04

redis实现多级缓存同步方案详解

目录前言多级缓存数据同步如何使用Redis6客户端缓存总结前言前阵子参加业务部门的技术方案评审,故事的背景是这样:业务部门上线一个专为公司高管使用的系统。这个系统技术架构形如下图按理来说这个系统因为受众很小,可以说基本上没并发,业务也没很
2022-12-21

AndroidNTP时间同步机制详解

这篇文章主要为大家介绍了AndroidNTP时间同步机制实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

阿里云数据库同步方案设置详解

阿里云数据库同步方案是阿里云为用户提供的数据库备份与恢复服务,通过此服务,用户可以轻松地进行数据库的同步与备份,提高数据安全性,确保数据的稳定性和可靠性。本文将详细介绍阿里云数据库同步方案的设置方法。一、数据库同步方案的设置步骤登录阿里云控制台,选择管理控制台。在管理控制台中,选择“数据库服务”下的“数据库同步”
阿里云数据库同步方案设置详解
2023-10-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录