线程安全性-原子性之synchronized锁

原子性提供了互斥访问:同一时刻只能有一个线程进行操作;

除了Atomic包类之外,还有锁可以实现此功能;

synchronized:  java关键字,依赖于jvm实现锁功能,被此关键字所修饰的,都是在同一时刻,只能有一个线程操作;

Lock: 由jdk提供的锁,Lock类,比如ReentranLock等..;

这次针对synchronized进行介绍:synchronized是一种同步锁,修饰对象有四种;

    一,修饰代码块:大括号括起来的代码,作用于调用的对象,被修饰的代码称为同步语句块

    二,整个方法,作用于调用的对象,被修饰的方法称为同步方法

    三,整个静态方法,作用于所有对象

    四,修饰类,synchronized后面括号括起来的部分,作用于所有对象

package com.example.concurrency.example.sync;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author xiaozhuang
 * @date 2022年04月08日 14:47
 */
@Slf4j
public class SynchronizedExample1 {

    // 修饰代码块 作用于调用的对象
    public void test1(){
        synchronized (this){
            for (int i = 0; i < 3; i++) {
                log.info("修饰代码块:{}",i);
            }
        }
    }
    public static void main(String[] args) {
        SynchronizedExample1 synchronizedExample1=new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(()->{
            synchronizedExample1.test1();
        });
        executorService.execute(()->{
            synchronizedExample1.test1();
        });
         executorService.shutdown();
    }
}

先看看修饰代码块的例子,它是作用于调用的对象,这里我们根据对象 在线程池中调用了2次,test1方法,看一下输出

再改为两个对象调用时

发现它是交替按顺序输出的,它是作用于调用对象,所以不用对象调用之间是相互不影响的,在上面一个对象调用两次test1方法,则是需要一个执行完再执行另一个。

再看看修饰方法,也是作用于调用对象:

再看看两个对象调用时

可以看到输出是与上面的修饰代码块是一样;

可以得出一个结论:如果一个方法内部一整个都是同步代码块,那么它与用synchronized修饰的方法是等同的。

如果当前类是父类,当子类继承父类时,调用父类被synchronized修饰的方法时,是不会带上synchronized关键字的,需要自己手动加上去,synchronized不属于方法声明部分;

再看看修饰静态方法与修饰类的:

这里因为修饰类与修饰静态方法的作用于所有的对象,所以在这个类里,都是需要逐个执行完毕;

结论:如果一个方法里,一整个都是被synchronized所修饰的类包围的时候,那么它与被synchronized修饰的静态方法的作用是等同的。

通过上面对synchronized的了解,所以一个计数的问题也就迎刃而解了;

@Slf4j
public class ConcurrencyTest {
    // 请求访问总数
    public static int clientTotal = 5000;
    // 同时并发执行的线程数
    public static int threadTotal = 200;
    // 计数的值
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        // 线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 信号量  参数为  运行并发的数目
        final Semaphore semaphore = new Semaphore(threadTotal);
        // 递减计数器  参数为 请求数量  没执行成功一次会  减1
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            // 讲请求放入线程池中
            executorService.execute(() -> {
                try {
                    //判断信号量 判断当前线程是否允许被执行
                    semaphore.acquire();
                    add();
                    // 释放进程
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                // 没执行完一次 计算器-1
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        // 关闭线程池
        executorService.shutdown();
        log.info("count:{}", count);
    }
    private static void add() {
        count++;
    }
}

只需要在add()方法加上synchronized,就会变成线程安全了;

最后对比一下synchronized,Lock,Atomic三者的区别

synchronized:在执行到synchronized作用范围内的时候,它是不可中断的,必须等待代码执行完毕。适合竞争不激烈,可读性好,竞争激烈时,性能下降较快;

Lock:是可以中断的,只需要调用unLock就行,多样化同步,竞争激烈时能维持常态;

Atomic: 竞争激烈时能维持常态,比Lock性能好;  缺点就是只能同步一个值;

posted @   超级大菜鸡  阅读(127)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示