java多线程 synchronized关键字

synchronized

课程主要内容

  • synchronized简介:作用、地位、不控制并发的后果
  • 两种用法:对象锁和类锁
  • 多线程访问同步方法的7种情况,是否是static,synchronized方法等。
  • synchronized的性质:可重入、不可中断
  • 原理:加解锁原理,可重入原理、可见性原理
  • synchronized的缺陷:效率低,不够灵活,无法预判是否成功获取到锁。
  • 常见面试问题:使用注意点、如何选择Lock或synchronized等
  • 如何提高性能,JVM如何决定哪个线程获取锁
  • 总结

synchronized简介

能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

synchronized地位

  • synchronized是Java关键字,被Java语言原生支持
  • 是最基本的互斥同步手段
  • 是并发编程中的元老级角色,是并发编程必学内容

不使用并发手段会有什么后果

代码实例

public class DisappearRequest implements Runnable{
    static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        DisappearRequest instance = new DisappearRequest();
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    @Override
    public void run() {
        for(int j = 0; j < 100000; j++){
            i++;
        }
    }
}

上述代码按道理会输出200000,但是输出值往往少于200000,为什么

原因:
count++ 看上去是一个操作,实际上包含了三个动作:

  1. 读取count
  2. 将count加1
  3. 将count的值写入到内存中

synchronized的两个用法

对象锁

包括方法锁(默认对象未this当前实例对象)和同步代码块锁(自己指定锁对象)。
代码块形式:手动指定锁对象
方法锁形式:synchronized修饰普通方法,锁对象默认为this

public class DisappearRequest implements Runnable{
    static int i = 0;
    Object object1 = new Object();
    Object object2 = new Object();

    public static void main(String[] args) throws InterruptedException {
        DisappearRequest instance = new DisappearRequest();
        Thread t1 = new Thread(instance);
        Thread t2 = new Thread(instance);
        t1.start();
        t2.start();
    }
    @Override
    public void run() {
        //对象锁同步代码块有两种使用方式
        //1.synchronized(this)
        //2.synchronized(自己创建的对象)
        synchronized (object1){
            System.out.println(Thread.currentThread().getName()+"进入object1锁");
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出object1锁");
        }
        synchronized (object2){
            System.out.println(Thread.currentThread().getName()+"进入object2锁");
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出object2锁");
        }
    }
}

使用synchronized直接修饰普通方法也是使用对象锁。锁对象默认为this

类锁

Java类虽然可能哟很多的对象,但是只有一个Class对象。

指synchronized修饰静态的方法或指定锁为Class对象

代码演示
类锁的第一种使用方法,用类修斯静态方法。

public class DisappearRequest implements Runnable{
    static int i = 0;
    Object object1 = new Object();
    Object object2 = new Object();
    public static void main(String[] args) throws InterruptedException {
        DisappearRequest instance1 = new DisappearRequest();
        DisappearRequest instance2 = new DisappearRequest();
        Thread t1 = new Thread(instance1);
        Thread t2 = new Thread(instance2);
        t1.start();
        t2.start();
    }
    @Override
    public void run() {
        method1();
        method2();
    }
    public synchronized static void method1(){
        System.out.println(Thread.currentThread().getName()+"进入object1锁");
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"退出object1锁");
    }
    public synchronized static void method2(){
        System.out.println(Thread.currentThread().getName()+"进入object2锁");
        try{
            Thread.sleep(3000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"退出object2锁");
    }
}

类锁的第二种使用方法,同步代码块synchronized(*.class);

@Override
    public void run() {
        //对象锁同步代码块有两种使用方式
        //1.synchronized(this)
        //2.synchronized(自己创建的对象)
        synchronized (Object.class){
            System.out.println(Thread.currentThread().getName()+"进入object1锁");
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出object1锁");
        }
        synchronized (Object.class){
            System.out.println(Thread.currentThread().getName()+"进入object2锁");
            try{
                Thread.sleep(3000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"退出object2锁");
        }
    }

多线程访问同步方法的7种情况

两个线程同时访问一个线程的同步方法

两个线程依次执行,相互等待

两个线程同时访问两个对象的同步方法

修饰普通方法锁的是各自的实例,所以互不影响

两个线程访问两个对象的synchronized修饰的静态方法

因为synchronized修饰的静态方法是对类锁,所以两个线程依次执行,相互等待。

同时访问同步方法和非同步方法

同时运行,相互无影响

同时访问同一个对象不同的普通同步方法

串行运行

同时访问静态synchronized和非静态synchronized方法

同时运行,指定的锁对象不是同一个锁。

方法抛出异常后,会释放锁

JVM帮助我们释放锁。

核心思想

  • 一把锁只能同时被一个线程获取,没有拿到锁的线程必须等待(对应1,5情况)
  • 每个实例都对应有自己的一把锁,不同实例之间互不影响,锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把类锁
  • 无论是方法正常正常结束还是方法抛出异常,都会释放锁。

synchronized性质

可重入

可重入是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。
可重入锁的优点:避免死锁,提升封装性
粒度:
情况1:证明同一个方法是可重入的。
情况2:证明可重入不要求是同一个方法。
情况3:证明可重入不要求是同一个类中的。

//情况1
private synchronized void method1(){
    System.out.println("a");
    if(a==0){
        a++;
        method1();
    }
}
//情况2
private synchronized void method1(){
    System.out.println("a");
    if(a==0){
        a++;
        method2();
    }
}
private synchronized void method2(){
    System.out.println("a");
    if(a==0){
        a++;
    }
}
//情况3
public class SynchronizedSuperClass12{
    public synchronized void doSomething(){
        System.out.println("");
        super.doSomething();
    }
}
class TestClass extends SynchronizedSuperClass12{
    public synchronized void doSomethind(){
        TestClass s = new TestClass();
        s.doSomething();
    }
}

在同一个线程中 如果已经拿到了synchronized锁,还想拿另一把锁,可以。

不可中断

一旦锁已经被别人获得了,如果我还想获得,我只能选择等待或者阻塞,知道别的线程释放这个锁,如果别人永远不释放锁,那么我只能永远的等下去。

synchronized锁的原理

加锁和释放锁的原理:现象,时机,深入JVM看字节码

在这里插入图片描述

monitorenter和monitorexit和锁息息相关,当一个线程去请求一个锁时,查看monitor的计数,如果计数为0,则可以获得该锁,如果不为0,则被阻塞,等待monitor计数变为0.

可重入锁的原理

  • JVM负责跟踪对象被加锁的次数
  • 线程第一次给对象加锁的时候,计数变为1,每当这个相同的线程在此对象上再次获得锁时,计数会递增
  • 当任务离开时,计数递减,当计数为0时,锁被完全释放。

可见性原理

通过JMM控制主内存与每个线程的本地内存的交互来实现线程的可见性。
一旦一个代码块的方法被释放时,被锁定的对象的数据会写入主内存中,而线程进入一个synchronized修饰的代码块,线程会将数据从主内存中读入本地内存。

synchronized的缺陷

  • 效率低:锁的释放情况少、试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程
  • 不够灵活:加锁和释放锁的时机单一,每个锁仅有单一的条件。
  • 无法知道是否成功获取锁。

Lock类如何克服这些缺陷,后续讲解

常见面试问题

  1. 使用注意点:锁对象不能为空,作用域不宜过大,避免死锁
  2. 如何选择Lock和synchronized关键字
  3. 多线程访问同步方法的具体情况
发布了54 篇原创文章 · 获赞 0 · 访问量 1611
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览