Synchronized 关键字

前言

synchronized 关键字是 java 多线程同步体系中最为重要的关键字。关于它修饰方法或者代码块,其不同用法对应不同作用域容易遗忘,因此记下来,等以后遗忘了之后再翻阅加深记忆。

synchronized

sychronized 关键字是为了解决多个线程同时访问同一块代码或者方法,导致同一变量在不同线程中数据不一致的问题。经过 sychronized 修饰的方法或者代码块,在同一时间只有一个线程进行访问,其它线程要访问只能等该线程执行完该方法或者代码块。这里涉及到对象锁这一概念,被 synchronized 修饰的方法或者代码块,只有取得其对象锁,线程才能访问。同一时间内,第一个访问到方法获得代码块的线程,会自动取得它的对象锁,相当于将方法或者代码块锁住,只有我这个线程才能访问,其它线程就访问不了。等取得对象锁的线程执行完之后,就会自动释放锁,其它的线程就可以获取对象锁并访问了。
所以,不同的方式修饰代码块或者方法,对象锁是不一样的,不同的对象锁,其作用域也不一样。要明白哪些线程可以同时访问 synchronized 修饰的方法或者代码块,一定记住,多个线程持有锁对象,如果持有相同的锁对象,则这些线程之间是同步的,如果分别获得不同的锁对象,这些线程之间是异步的。

synchronized 作用域

  1. synchronized 修饰普通方法

synchronized 修饰普通方法,其对象锁是类的实例锁,多个线程通过同一个该类实例访问该方法时会同步。

public class C{
    synchronized public void method(){
        ...
    }
}

此方式的对象锁是类 C 的实例,所以,多个线程用于同一类C 的实例,并同时访问 method() 方法时,就得一个线程执行完另外一个线程继续执行。

public class Main{
    public static void main(String[] args){
        C c = new C();
        ThreadA threadA = new ThreadA(c);
        ThreadB threadB = new ThreadB(c);
        threadA.start();
        threadB.start();
    }

    class ThreadA extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            c.method();
        }
    }

    class ThreadB extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            c.method();
        }
    }
}

线程threadA 和 threadB 方法访问 method() 方法时,都是实例对象 c 的锁,所以会进行同步。

public class Main{
    public static void main(String[] args){
        C c1 = new C();
        C c2 = new C();
        ThreadA threadA = new ThreadA(c1);
        ThreadB threadB = new ThreadB(c2);
        threadA.start();
        threadB.start();
    }
}

这种方式,线程threadA 和 threadB 的对象锁就是一样了,threadA 的对象锁是c1, threadB 的对象锁是实例对象 c2,所以两个线程同时访问 method() 方法不会有冲突。

  1. synchronized 修饰静态方法

synchronized 修饰静态方法,其对象锁是这个类对象,即xxx.class

public class C{
    synchronized public static void method(){
        ...
    }
}

synchronized 修饰静态方法 method() ,其对象锁是 C.class。所以,当ThreadA 和 ThreadB 通过 C.method() 访问该方法还是通过类C 的实例对象访问 nethod(),都会进行线程同步,同一时间只有一个线程访问。对上面的代码

public class Main{
    public static void main(String[] args){
        C c1 = new C();
        C c2 = new C();
        ThreadA threadA = new ThreadA(c1);
        ThreadB threadB = new ThreadB(c2);
        threadA.start();
        threadB.start();
    }
}

此时 method 是静态代码,所以threadA 和 threadB 也会线程同步。因此,synchronized 修饰静态方法比修饰普通方法作用域广。

  1. synchronized 修饰代码块

synchronized 修饰代码块有两种方式,一种是 synchronized(this) { 代码块 }, 另一种是 synchronized(非this对象) {代码块} 。

  • synchronized(this) { 代码块 }

用 this 参数 修饰代码块,其对象锁为该代码所在类的实例。

public class C{
    public void method(){
        synchronized(this) {
            System.out.println("代码块A");
        }
        System.out.println("代码块B");
    }
}

它跟 synchronized 修饰普通方法一样,多个线程持有同一个 C 的对象实例 c 访问 method() 时,会在代码块A 这里进行同步,同一时间只有一个线程访问,但是代码块B 就可以多个线程同时访问了。

  • sychronized(非this对象) { 代码块 }

sychronized(非this对象) 的对象锁就是这个非this对象了。

public class C{
    public void method(Object o){
        synchronized(o) {
            System.out.println("代码块A");
        }
        System.out.println("代码块B");
    }
}

这里代码块A 的对象锁是 o。

public class Main{
    public static void main(String[] args){
        C c = new C();
        ThreadA threadA = new ThreadA(c);
        ThreadB threadB = new ThreadB(c);
        threadA.start();
        threadB.start();
    }

    class ThreadA extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            Object o1 = new Object();
            c.method(o1);
        }
    }

    class ThreadB extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            Object o2 = new Object();
            c.method(o2);
        }
    }
}

线程 threadA 和线程 threadB 访问代码块A 的对象锁不同,一个是对象是 o1, 另一个是 o2,所以不会进行同步。

public class C{
    private Object o = new Object();
    public void method({
        synchronized(o) {
            System.out.println("代码块A");
        }
        System.out.println("代码块B");
    }
}


 class ThreadA extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            c.method();
        }
    }

    class ThreadB extends Thread{
        private C c;
        public ThreadA(C c){
            this.c = c;
        }

        @Override 
        public void run(){
            c.method();
        }
    }

此时,对象锁都是 o,所以 线程 threadA 和线程 threadB 会进行同步。当这个非this对象是 xxx.class 时,其作用域和 synchronized 修饰静态方法一样,锁对象是类对象。如:

public class C{
    private Object o = new Object();
    public void method({
        synchronized(C.class) {
            System.out.println("代码块A");
        }
        System.out.println("代码块B");
    }
}

synchronized 和 volatile 的区别

在 java 中,许多新手会不是很明白 volatile 关键字和 synchronized 的区别。volatile 关键字只能修饰变量,不能修饰方法和代码块。volatile 只能保证变量的可见性,不能保证原子性。子线程访问主线程的变量,是将变量从主内存复制到自己的工作内存保存一份。所以,子线程访问这个变量时,主内存该变量值改变了,但是工作内存的值未更新,子线程一直访问的是变量的旧值。如果变量加上 volatile 关键字,它就保证了子线程每次访问该变量都是取的是主内存最新的值,即主内存该变量的值变化对子线程是可见的。但是多个子线程对主线程 volatile 修饰的变量进行操作,是无法保证线程安全的,比如对主线程的变量i 进行 i++操作,可能导致脏数据。这时候就得用 synchronized 保证各线程同步。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值