java多线程之synchronized关键字(2)

synchronized的引入

当存在多个线程操作共享数据时,需要保证同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再进行,这种方式称互斥锁,也就是说当一个共享数据被当前正在访问的线程加上互斥锁后,在同一个时刻,其他线程只能处于等待的状态,直到当前线程处理完毕释放该锁。在 Java 中,关键字 synchronized可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块(主要是对方法或者代码块中存在共享数据的操作),同时我们还应该注意到synchronized另外一个重要的作用,synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能)

synchronized的3种应用方式

synchronized关键字最主要有以下3种应用方式,下面分别介绍

  • 修饰实例方法,给调用该方法的对象加锁,进入同步代码前要获得当前对象的锁(this)

  • 修饰静态方法,给当前类对象加锁,进入同步代码前要获得当前类对象的锁(Class对象)

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized修饰非静态方法

public class AccountingSync implements Runnable{
    //共享资源(临界资源)
    static int i=0;

    /**
     * synchronized 修饰非静态方法
     */
    public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        AccountingSync instance=new AccountingSync();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
    /**
     * 输出结果:
     * 2000000
     */
}

同一个对象在两个线程中分别访问该对象的同步方法,第一个线程获取到到对象锁并访问该同步方法的,第二个线程因无法拿到该对象锁而无法访问该同步方法,需要等待第一个线程执行结束并释放锁后才能访问,这样形成互斥!

说明:非静态方法的锁是该对象

如果第一个线程访问A对象的同步方法,第二个线程访问B对象的同步方法,这样不会引起互斥。如下

public static void main(String[] args) throws InterruptedException {
        //new新实例
        Thread t1=new Thread(new AccountingSyncBad());
        //new新实例
        Thread t2=new Thread(new AccountingSyncBad());
        t1.start();
        t2.start();
        //join含义:当前线程A等待thread线程终止之后才能从thread.join()返回
        t1.join();
        t2.join();
        System.out.println(i);
    }

第一个线程和第二个线程分别拿到自己new出来的的对象锁,同步方法各自有各自的锁,不会引起互斥。

synchronized修饰静态方法

public class AccountingSyncClass implements Runnable{
    static int i=0;

    /**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i++;
    }

    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase2(){
        i++;
    }

    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(new AccountingSyncClass());
        Thread t2=new Thread(new AccountingSyncClass());
        //启动线程
        t1.start();t2.start();

        t1.join();t2.join();
        System.out.println(i);
    }
}

上面的两个线程虽然各自拥有不同的对象,但是访问的是静态方法,静态方法的锁是当前类对象,两个线程访问同步静态方法,拿到的是当前类对象的锁,只有一个,任然是互斥的!

synchronized修饰代码块

在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了

public class AccountingSync implements Runnable{
    static AccountingSync instance=new AccountingSync();
    static int i=0;
    @Override
    public void run() {
        //省略其他耗时操作....
        //使用同步代码块对变量i进行同步操作,锁对象为instance
        synchronized(instance){
            for(int j=0;j<1000000;j++){
                    i++;
              }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();t2.start();
        t1.join();t2.join();
        System.out.println(i);
    }
}
synchronized修饰代码块的时候需要指定锁对象,多线程访问的时候需要拿到这个锁对象后才能访问该静态代码块, 如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行。通常我们经常使用this和xx.class对象来作为锁对象,
//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

同步方法和同步代码块的意义是一样的,同步方法不需要显式的指定锁对象,而代码块则需要显式指定,效果和意义是一样的。


总结:

多线程访问同步方法,能不能同时访问,主要看线程拿到的锁对象是不是唯一,如果锁对象是唯一的,意思就是多个线程会互斥访问!

1、调用同一个对象中非静态同步方法的线程是互斥的。如果是不同对象,则每个线程有自己的对象的锁,线程间彼此互不干预。

2、调用同一个类中的静态同步方法的线程将是互斥的,它们都是锁定在相同的Class对象上。 

3、静态同步方法和非静态同步方法将永远不是互斥的,因为静态方法锁定在Class对象上,非静态方法锁定在该类的对象上。

4、对于同步代码块,要看清楚什么对象已经用于锁定(synchronized后面括号的内容)。在同一个对象上进行同步的线程将是互斥的,在不同对象上锁定的线程将永远不会互斥。




猜你喜欢

转载自blog.csdn.net/weixin_37598682/article/details/80077946