synchronized关键字的理解与实践

项目中,我们经常会碰到多线程并发的问题,而这些问题往往会导致数据错误等各种千奇百怪的现象。那么我们有什么办法避免呢?当然有。java给我们提供了synchronized关键字;本文将通过以下几点来展开叙述。

1. synchronized的作用

2.synchronized方法(普通方法和静态方法)

3.synchronized代码块(this、object和class)

 

synchronized的作用

保证用synchronized修饰的部分,在同一时间只有一个线程能访问,其他的线程必须等待该部分代码执行完毕后,依次进行访问,从而实现线程同步。是不是会有点抽象,没关系,我们通过下面的各种示例来理解。

 

synchronized方法(普通方法和静态方法)

1. 修饰普通方法

只需在我们的普通方法前面加上synchronized关键字修饰即可,我们直接举个栗子

public synchronized void printA(){
        for(int i = 0; i < 5; i++){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + ": i in SynchronizedMethod is " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

这就是一个synchronized方法了。下面我们试着开两个线程(thread1和thread2)同时调用上面的printA()方法

/**
 *  两个线程并发,同一对象调用同一方法(结果:线程同步)
 */
SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();

Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronizedMethod1.printA();
    }
}, "thread1");

Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronizedMethod1.printA();
    }
 }, "thread2");
        
 thread1.start();
 thread2.start();

运行后发现,结果如下:

thread1: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 4
thread2: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4

至此已经实现了线程同步。但是上面这个代码中,thread1和thread2中调用的都是同一个对象synchronizedMethod1的printA()方法;

那如果再创建一个对象叫synchronizedMethod2,在thread1中调用synchronizedMethod1的printA()方法,在thread2中调用synchronizedMethod2的printA()方法,那么还能实现线程同步吗?

/**
 *  两个线程并发,不同对象调用同一个方法(结果:线程不同步)
 */
SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();
SynchronizedMethod synchronizedMethod2 = new SynchronizedMethod();

Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronizedMethod1.printA();
    }
 }, "thread1");

 Thread thread2 = new Thread(new Runnable() {
    @Override
    public void run() {
        synchronizedMethod2.printA();
    }
 }, "thread2");
       
 thread1.start();
 thread2.start();

运行结果如下:

thread2: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4
thread1: i in SynchronizedMethod is 4

我们发现,如果是两个对象的话,虽然都调用了printA()方法,且printA()方法是synchronized修饰的同步方法,但是并不能实现线程同步。

所以我们总结一下:对于synchronized修饰的普通方法,只有是在同一个对象的情况下,才可以实现线程同步;

那么肯定有小伙伴想问:怎么才让printA()方法在多个不同对象调用的情况下,实现线程同步呢?我们只需要将printA()方法改成静态方法即可,当然这只是一种实现方式,更多的实现方式请继续阅读后面的synchronized代码块。

2. 修饰静态方法

老规矩,废话不多说,直接上栗子

public static synchronized void printA(){
        for(int i = 0; i < 5; i++){
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + ": i in SynchronizedMethod is " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

我们直接把上一个例子中的printA()方法加上static修饰,接着我们依然通过上面第二个例子中的代码运行一下:

SynchronizedMethod synchronizedMethod1 = new SynchronizedMethod();
SynchronizedMethod synchronizedMethod2 = new SynchronizedMethod();

Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedMethod1.printA();
        }
    }, "thread1");

Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedMethod2.printA();
        }
    }, "thread2");

thread1.start();
thread2.start();

结果如下:

thread1: i in SynchronizedMethod is 0
thread1: i in SynchronizedMethod is 1
thread1: i in SynchronizedMethod is 2
thread1: i in SynchronizedMethod is 3
thread1: i in SynchronizedMethod is 4
thread2: i in SynchronizedMethod is 0
thread2: i in SynchronizedMethod is 1
thread2: i in SynchronizedMethod is 2
thread2: i in SynchronizedMethod is 3
thread2: i in SynchronizedMethod is 4

哈,现在又变成线程又同步啦。

总结:对于synchronized修饰的普通方法,只有是在同一个对象的情况下,才可以实现线程同步;而synchronized修饰的静态方法,即使是不同的对象,也可以实现线程同步;因为普通方法仅属于当前类的一个对象,而静态方法则属于这个类;

但是有很多时候,我们并不需要整个方法都是同步的,这样会很大程度地降低效率,那么我们是否可以对方法中的一段代码实现线程同步呢?当然是可以的啦,仅需使用synchronized修饰代码块即可。

 

synchronized代码块(this、object和class)

1.synchronized(this)

用这种方式修饰代码块,是对当前类的对象设置一个同步锁,当不同线程,同一个对象执行到该代码块时,代码块里面的部分才会实现同步,当然代码块外面的部分依然是不同步的。

public void printA(){
        System.out.println(Thread.currentThread().getName() + ": printer A is already");
        synchronized (this){
            for(int i = 0; i < 5; i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

同样开起thread1和thread2两个线程执行以下printA()方法。

/**
*  两个线程并发,同一对象调用同一个方法(结果:synchronized代码块内线程同步,外部不同步)
*/
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();

Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock1.printA();
        }
    }, "thread1");

Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock1.printA();
        }
    }, "thread2");

thread1.start();
thread2.start();

运行结果如下:

thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4

与我们预想的一样,synchronized代码块里面的是线程同步的,但是代码块外面的逻辑是线程不同步的。

与之前的synchronized修饰普通方法一样,如果是两个对象,那么还会是线程同步吗?答案是线程不同步,具体大家可以自行去尝试一波,这里不多赘述了。

那synchronized代码块又如何保证多个对象的情况下,也实现线程同步呢?这里就需要用synchronized(Xxx.class),这里Xxx是类名。

2.synchronized(Xxx.class)

在代码块第1小节中的示例的基础上,我们将synchronized(this)改为synchronized(SynchronizedBlock.class)

public void printA(){
        System.out.println(Thread.currentThread().getName() + ": printer A is already");
        synchronized (SynchronizedBlock.class){
            for(int i = 0; i < 5; i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

然后我们在执行的时候,先创建两个对象,再在thread1和thread2中分别用这两个不同的对象调用printA()方法

/**
 *  两个线程并发,不同对象调用同一个方法(结果:synchronized代码块内线程同步,外部不同步)
 */
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock();

Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock1.printA();
        }
    }, "thread1");

Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock2.printA();
        }
    }, "thread2");

thread1.start();
thread2.start();

执行结果如下,代码块内是可以实现线程同步的。

thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4

3.synchronized(object)

我们再来研究一下synchronized(object),这种方式其实是给object添加了一个同步锁,所以能不能实现线程同步,需要看object是否是同一个对象。

示例1:

使用synchronized(object)修饰代码块

public class SynchronizedBlock {
    private Object mObject;
    public SynchronizedBlock(){}

    public SynchronizedBlock(Object object){
        this.mObject = object;
    }

    public void printA(){
        System.out.println(Thread.currentThread().getName() + ": printer A is already");
        synchronized (mObject){
            for(int i = 0; i < 5; i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

下面是执行代码,但是在执行之前,我们先创建两个对象,给这两个对象初始化时传入同一个object对象,再开两个线程分别让每一个对象分别执行printA()方法。


Object object = new Object();
SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock(object);
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock(object);

Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock1.printA();
        }
    }, "thread1");

Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock2.printA();
        }
    }, "thread2");

thread1.start();
thread2.start();

执行结果如下,可以实现线程同步。

thread1: printer A is already
thread2: printer A is already
thread1: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread1: i in SynchronizedBlock is 4
thread2: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4

示例2:

我们对示例1中的SynchronizedBlock方法做如下修改:

public class SynchronizedBlock {
    private Object mObject = new Object();
    public SynchronizedBlock(){}
    
    public void printA(){
        System.out.println(Thread.currentThread().getName() + ": printer A is already");
        synchronized (mObject){
            for(int i = 0; i < 5; i++){
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ": i in SynchronizedBlock is " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

下面是执行代码,直接创建两个对象,再开两个线程分别让每一个对象分别执行printA()方法。

SynchronizedBlock synchronizedBlock1 = new SynchronizedBlock();
SynchronizedBlock synchronizedBlock2 = new SynchronizedBlock();

Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock1.printA();
        }
    }, "thread1");

Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            synchronizedBlock2.printA();
        }
    }, "thread2");

thread1.start();
thread2.start();

结果当然是不能实现线程同步。我们看一下打印的结果,验证一下。

thread1: printer A is already
thread2: printer A is already
thread2: i in SynchronizedBlock is 0
thread1: i in SynchronizedBlock is 0
thread2: i in SynchronizedBlock is 1
thread1: i in SynchronizedBlock is 1
thread2: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 2
thread1: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 3
thread2: i in SynchronizedBlock is 4
thread1: i in SynchronizedBlock is 4

果然,打印的结果的确是无法做到线程同步的。

总结:synchronized(this)修饰代码块,代码块内的代码仅针对同一个对象实现线程同步;

           synchronized(Xxx.class)修饰代码块,只要是同一个类,不管有多少个对象,代码块内的代码也同样实现线程同步;

           synchronized(object)修饰代码块,这里代码块内的代码能不能实现线程同步,取决于object是否是同一个对象。如果是则同步;如果不是,则不同步;

至此,今天关于synchronized的知识点就到这里了,如果大家看完之后还有什么疑惑或者有什么建议的,可以相互交流^.^

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值