Java中关键字synchronized的使用方法详解

synchronized是Java里的一个关键字,起到的一个效果是“监视器锁”~~,它的功能就是保证操作的原子性,同时禁止指令重排序和保证内存的可见性!

public  class TestDemo {
    static  class Counter{
        public int count = 0;

        public void add(){
            count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.add();
                }
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.add();
                }
            }
        };

        //启动两个线程
        t1.start();
        t2.start();
        //等待两个线程结束
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

此时的线程就是不安全的,如何解决呢?

给我们的Counter对象里的add方法加上synchronized关键字,针对这个方法进行了加锁操作。进入代码块(调用方法)自动加锁,出了代码块(方法结束),自动解锁。

public  class TestDemo {
    static  class Counter{
        public int count = 0;

         //修饰方法
         synchronized public void add{
             count++;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.add();
                }
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    counter.add();
                }
            }
        };

        //启动两个线程
        t1.start();
        t2.start();
        //等待两个线程结束
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

那么这里的代码是如何保证正确的呢?

使用synchronized 就相当于在我们执行的指令里又加入了2条新指令。

LOCK (加锁)

UNLOCK (解锁)

LOCK操作特性:只有一个线程能执行成功!如果第一个线程执行成功,第二个线程也尝试LOCK,就会阻塞等待,直到第一个线程执行UNLOCK 释放锁~

通过LOCK和UNLOCK 就把 LOAD ADD SAVE 这三个指令,给打包成了一个原子的操作(中间不能被打断,也不能被其他线程穿插)。

这里的加锁也是保证原子性的核心操作,所以线程里的没组指令就会顺序执行,不在穿插执行,就保证了线程1执行完之后再去执行线程2。

举个例子:

就好比张三和李四去ATM里去取钱,当张三进去取钱时,进去后就会锁门,李四就会在外面等待,直到张三取完钱出来后,李四在进去取钱。

synchronized 也会禁止编译器进行“内存可见性”和“指令重排序”的优化~ 同时程序运行的效率就会降低,
也会导致线程之间相互去等待,就涉及到系统的一些调度,也会引入一些时间成本。

synchronized修饰的对象有以下几种:

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

public class TestDemo{
    public void methond() {
        // 进入代码块会锁 this 指向对象中的锁;
        // 出代码块会释放 this 指向的对象中的锁
        synchronized (this) {

        }
    }
    public static void main(String[] args) {
        TestDemo demo = new TestDemo();
        demo.methond();
    }
}

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

public class TestDemo{
    public synchronized void methond() {

    }
    public static void main(String[] args) {
        TestDemo demo = new TestDemo();
        demo.methond();
        // 进入方法会锁 demo 指向对象中的锁;
        // 出方法会释放 demo 指向的对象中的锁
    }
}

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

public class TestDemo{
    public synchronized static void methond() {

    }
    public static void main(String[] args) {
        methond();
        // 进入方法会锁 TestDemo.class 指向对象中的锁;
        //出方法会释放 TestDemo.class 指向的对象中的锁
    }
}

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

public class TestDemo{
    public static void methond() {
        // 进入代码块会锁 TestDemo.class 指向对象中的锁;
        //出代码块会释放 TestDemo.class 指向的对象中的锁
        synchronized (TestDemo.class) {

        }
    }
    public static void main(String[] args) {
        TestDemo demo = new TestDemo();
        demo.methond();
    }
}

总结:

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

拓展:

public  class TestDemo {

    static   class Counter{
        public int count = 0;

           public void add(){
               synchronized (this){
                   count++;
               }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    synchronized (counter){
                        counter.add();
                    }
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    synchronized (counter){
                        counter.add();
                    }
                }
            }
        };

        //启动两个线程
        t1.start();
        t2.start();
        //等待两个线程结束
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

此时可以看出上述代码,加了两次锁,会发生什么呢?

但是运行代码发现程序依然正确运行?? 为什么

但是上述分析死锁的思路是对的

只是因为synchronized内部使用特殊手段来处理了这种情况 。

这样的操作特性我们叫做 可重入锁

synchronized 内部记录了当前这把锁是哪个线程持有的~

如果当前加锁线程和持有锁的线程是同一个线程~

此时就并不是真的进行“加锁操作”,而是把一个计数器加一;

如果后续该线程继续尝试获取锁,继续判定加锁线程和持有锁线程是不是同一个线程,只要是同一个线程,就不真的加锁,而是计数器+1;

如果该线程调用解锁操作,也不是立刻就解锁,而是计数器减1

直到计数器减成0了,才认为真的要“释放锁”,才允许其他线程来获取锁~~

总结

到此这篇关于Java中synchronized使用的文章就介绍到这了,更多相关Java中synchronized用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Java中的锁Lock和synchronized

    一.Lock接口 1.Lock接口和synchronized内置锁 a)synchronized:Java提供的内置锁机制,Java中的每个对象都可以用作一个实现同步的锁(内置锁或者监视器Monitor),线程在进入同步代码块之前需要或者这把锁,在退出同步代码块会释放锁.而synchronized这种内置锁实际上是互斥的,即没把锁最多只能由一个线程持有. b)Lock接口:Lock接口提供了与synchronized相似的同步功能,和synchronized(隐式的获取和释放锁,主要体现在线程进

  • Java多线程之synchronized关键字的使用

    一.使用在非静态方法上 public synchronized void syzDemo(){ System.out.println(System.currentTimeMillis()); System.out.println("进入synchronized锁:syzDemo"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } 二.使用在静态方法上 publi

  • [java并发编程之深入理解]Synchronized的使用

    1.为什么要使用synchronized 在并发编程中存在线程安全问题,主要原因有:1.存在共享数据 2.多线程共同操作共享数据.关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile. 2.实现原理 synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性 3.synchronized的三种应

  • JAVAsynchronized原理详解

    目录 1.synchronized的作用 2.synchronized的语法 3.Monitor原理 4.synchronized的原理 4.1偏向锁 4.2轻量级锁 4.3锁膨胀 4.4重量级锁 4.5自旋锁 4.6锁消除 4.7锁粗化 5.锁升级过程 总结 1.synchronized的作用 为了避免临界区的竞态条件发生,有多种手段可以达到目的. 阻塞式的解决方案:synchronized,Lock 非阻塞式的解决方案:原子变量 synchronized,即俗称的[对象锁],它采用互斥的方式

  • 死磕 java同步系列之synchronized解析

    问题 (1)synchronized的特性? (2)synchronized的实现原理? (3)synchronized是否可重入? (4)synchronized是否是公平锁? (5)synchronized的优化? (6)synchronized的五种使用方式? 简介 synchronized关键字是Java里面最基本的同步手段,它经过编译之后,会在同步块的前后分别生成 monitorenter 和 monitorexit 字节码指令,这两个字节码指令都需要一个引用类型的参数来指明要锁定和解

  • Java中关键字synchronized的使用方法详解

    synchronized是Java里的一个关键字,起到的一个效果是"监视器锁"~~,它的功能就是保证操作的原子性,同时禁止指令重排序和保证内存的可见性! public class TestDemo { static class Counter{ public int count = 0; public void add(){ count++; } } public static void main(String[] args) throws InterruptedException {

  • JavaScript中关键字 in 的使用方法详解

    for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称为"枚举". 对于数组 ,迭代出来的是数组元素 但不推荐,因为不能保证顺序,而且如果在Array的原型上添加了属性,这个属性也会被遍历出来,所以 最好数组使用正常的for循环,对象使用for-in循环 对于对象 ,迭代出来的是对象的属性: var obj = { "key1":"value1", "key2":"value2", &q

  • Java中Optional类及orElse方法详解

    目录 引言 Java 中的 Optional 类 ofNullable() 方法 orElse() 方法 案例 orElseGet() 方法 案例 orElse() 与 orElseGet() 之间的区别 引言 为了让我更快的熟悉代码,前段时间组长交代了一个小任务,大致就是让我整理一下某个模块中涉及的 sql,也是方便我有目的的看代码,也是以后方便他们查问题(因为这个模块,涉及的判断很多,所以之前如果 sql 出错了,查问题比较繁琐). 昨天算是基本完成了,然后今天组长就让给我看一个该模块的缺陷

  • Java中对象数组的使用方法详解

    本文实例讲述了Java中对象数组的使用方法.分享给大家供大家参考,具体如下: 一 点睛 对象可以用数组来存放,通过下面两个步骤来实现. 1 声明以类为数据类型的数组变量,并用new分配内存空间给数组. 2 用new产生新的对象,并分配内存空间给它. 下面介绍4种方式来定义对象数组 方式一:静态方式 Person p1[] = { new Person(), new Person(), new Person() }; 方式二:动态初始化化 Person p2[]; p2 = new Person[

  • java集合——Java中的equals和hashCode方法详解

    Java中的equals方法和hashCode方法是Object中的,所以每个对象都是有这两个方法的,有时候我们需要实现特定需求,可能要重写这两个方法,今天就来介绍一些这两个方法的作用. equals()和hashCode()方法是用来在同一类中做比较用的,尤其是在容器里如set存放同一类对象时用来判断放入的对象是否重复. 这里我们首先要明白一个问题: equals()相等的两个对象,hashcode()一定相等,equals()不相等的两个对象,却并不能证明他们的hashcode()不相等.换

  • Java中图像锐化操作的方法详解

    一.该图像锐化的思想: 本文的图像锐化是将图像中的R,G,B的值分别从原图像中提出,然后将分别将这三个R,G,B的值分别与卷积核进行卷积,最终再将最后的三个卷积的结果合成为一个像素值,从而实现图像的锐化效果. 二.整体的图像锐化的代码为: package com.yf1105; import java.awt.Color; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.File; imp

  • Java中获取MongoDB连接的方法详解

    首先是所需jar包,Maven中的配置如下: <dependency> <groupId>org.mongodb</groupId> <artifactId>mongodb-driver</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.mongodb</groupId>

  • java中类加载与双亲委派机制详解

    目录 类加载是什么 类加载器 双亲委派机制 BootStrapClassLoader ExtClassLoader AppClassLoader 为什么使用双亲委派机制 全盘负责委托机制 自定义类加载器 打破双亲委派机制 类加载是什么 把磁盘中的java文件加载到内存中的过程叫做类加载 当我们用java命令运行某个类的main函数启动程序时,首先需要通过类加载器把主类加载到JVM. 有如下 User 类 package dc.dccmmtop; public Class User { publi

  • java中Executor,ExecutorService,ThreadPoolExecutor详解

    java中Executor,ExecutorService,ThreadPoolExecutor详解 1.Excutor 源码非常简单,只有一个execute(Runnable command)回调接口 public interface Executor { /** * Executes the given command at some time in the future. The command * may execute in a new thread, in a pooled thre

  • Java中接口和抽象类的区别详解

    需求:接口是否可继承接口?抽象类是否可实现(implements)接口?抽象类是否可继承实体类(concrete class)?抽象类中是否可以有静态的main方法? 先说明二者的定义,然后聊聊需求,最后分析二者的区别. 含有abstract修饰符的类即为抽象类,抽象类不能创建实例对象.含有抽象方法的类必须定义为abstract class.在abstract class中,方法不必是抽象的,但是抽象方法必须在具体子类中实现,所以,不能有抽象构造方法或抽象静态方法.子类如果没有实现抽象父类中的所

随机推荐