synchronized详解,java程序员需要懂

一、为什么使用synchronized

在多线程并发修改共享数据时无法保证线程安全。synchronized关键字可以保证在某一时刻只有一个线程可以访问某个方法或者代码块,只有当前线程执行完成其他线程才能够重新拿到执行权力。

下面我们来演示一下如果不是用synchronized关键字的情况下,使用多线程操作会出现什么情况。

首先我们创建一个类SynchronizedMethod,里面只有一个变量就是number
里面有一个addOne()方法,每调用一次会使当前对象的number+1

package com.example.demo1.entity;

/**

  • create by c-pown on 2020-06-04
    */
    public class SynchronizedMethod {

    private int number;

    public int getNumber() {
    return number;
    }

    public void setNumber(int number) {
    this.number = number;
    }

    public void addOne(){
    setNumber(getNumber() + 1);
    }

}

我们新建一个测试类测试一下,多线程连续调用1000次是什么结果

package com.example.demo1.entity;

import org.junit.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static org.junit.Assert.*;

public class SynchronizedMethodTest {

<span class="token annotation punctuation">@Test</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addOne</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException<span class="token punctuation">{</span>
    SynchronizedMethod synchronizedMethod <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">SynchronizedMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    ExecutorService executor <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    IntStream<span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>count<span class="token operator">-</span><span class="token operator">&gt;</span>executor<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span>synchronizedMethod<span class="token operator">:</span><span class="token operator">:</span>addOne<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    executor<span class="token punctuation">.</span><span class="token function">awaitTermination</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>
    executor<span class="token punctuation">.</span><span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"当前数值:"</span><span class="token operator">+</span>synchronizedMethod<span class="token punctuation">.</span><span class="token function">getNumber</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

1)Executors.newFixedThreadPool() 方法可以创建一个指定大小的线程池服务 ExecutorService

2)通过 IntStream.range(0, 1000).forEach() 来执行 addOne() 方法 1000 次。
在这里插入图片描述
很不幸,我们发现结果并不是1000,这是因为多线程操作数据时,可变的共享数据没有得到保护。

下面我们给addOne()方法里面的方法体添加一些打印信息,看下具体执行过程是怎样的。

public  void addOne(){
        System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
        try {
        //为了使结果更加明显我们延迟5ms
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setNumber(getNumber() + 1);
        System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    }

 
 

我们在调用一下看看:
在这里插入图片描述
可以明显的看出,在线程1访问方法addOne()的时候,并没有执行结束,线程2、线程3就已经开始访问方法了,很明显,线程不安全。

二、synchronized关键字的使用

1.我们给addOne()方法前边加上synchronized关键字在看一下
public synchronized void addOne(){
        System.out.println("当前线程:"+Thread.currentThread().getName()+"开始");
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        setNumber(getNumber() + 1);
        System.out.println("当前线程:"+Thread.currentThread().getName()+"结束");
    }

 
 

在这里插入图片描述
可以发现当前线程在执行过程中,其他线程是无法加入的,就好比一个屋子,一个线程进去了就会关上门,其他人只能在门外侯着,他出来了别人才能进去,这就保证了线程安全。
在这里插入图片描述
结果也是没有问题。

2.给静态方法加上synchronized

package com.example.demo1.entity;

/**

  • create by c-pown on 2020-06-04
    */
    public class SynchronizedMethod {

    public static int number;

    public synchronized static void addOne(){
    System.out.println(“当前线程:”+Thread.currentThread().getName()+“开始”);
    try {
    Thread.sleep(5);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    number=number+1;
    System.out.println(“当前线程:”+Thread.currentThread().getName()+“结束”);
    }
    }

package com.example.demo1.entity;

import org.junit.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static org.junit.Assert.*;

public class SynchronizedMethodTest {

<span class="token annotation punctuation">@Test</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addOne</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException<span class="token punctuation">{</span>

    ExecutorService executor <span class="token operator">=</span> Executors<span class="token punctuation">.</span><span class="token function">newFixedThreadPool</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    IntStream<span class="token punctuation">.</span><span class="token function">range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">forEach</span><span class="token punctuation">(</span>count<span class="token operator">-</span><span class="token operator">&gt;</span>executor<span class="token punctuation">.</span><span class="token function">submit</span><span class="token punctuation">(</span>SynchronizedMethod<span class="token operator">:</span><span class="token operator">:</span>addOne<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    executor<span class="token punctuation">.</span><span class="token function">awaitTermination</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>SECONDS<span class="token punctuation">)</span><span class="token punctuation">;</span>
    executor<span class="token punctuation">.</span><span class="token function">shutdown</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    System<span class="token punctuation">.</span>out<span class="token punctuation">.</span><span class="token function">println</span><span class="token punctuation">(</span><span class="token string">"当前数值:"</span><span class="token operator">+</span>SynchronizedMethod<span class="token punctuation">.</span>number<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

}

静态方法无需实例化,直接使用类名调用即可
在这里插入图片描述
也是没毛病的。

三、synchronized作用于同步代码块

package com.example.demo1.entity;

/**

  • create by c-pown on 2020-06-04
    */
    public class SynchronizedMethod {

    private int number;

    public int getNumber() {
    return number;
    }

    public void setNumber(int number) {
    this.number = number;
    }

    public void addOne(){
    System.out.println(“当前线程:”+Thread.currentThread().getName()+“开始”);
    try {
    Thread.sleep(5);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    synchronized (this){
    setNumber(getNumber() + 1);
    }
    System.out.println(“当前线程:”+Thread.currentThread().getName()+“结束”);
    }
    }

我们方法里面最核心的部分加上synchronized代码块,在执行一次。

synchronized (this){
      setNumber(getNumber() + 1);
    }

 
 

在这里插入图片描述
在这里插入图片描述

我们会发现线程1进入addOne()方法的时候线程2线程3也是可以进来的,说明方法体的其他地方并不是线程安全的,但是setNumber(getNumber() + 1)这一块时却是线程安全的,从而保证结果的正确与一致性。


-END-

关注微信公众号

java学长

学习更多java技术干货,提升职场技术水平!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值