Java高并发之从零到放弃

简介: 本篇主要讲解如何去优化锁机制或者克服多线程因为锁可导致性能下降的问题

前言
本篇主要讲解如何去优化锁机制
或者克服多线程因为锁可导致性能下降的问题
ThreadLocal线程变量
有这样一个场景,前面是一大桶水,10个人去喝水,为了保证线程安全,我们要在杯子上加锁
导致大家轮着排队喝水,因为加了锁的杯子是同步的,只能有一个人拿着这个唯一的杯子喝水
这样子大家都喝完一杯水需要很长的时间
如果我们给每个人分发一个杯子呢?是不是每人喝到水的时间缩小到了十分之一
多线程并发也是一个道理
在每个Thread中都有自己的数据存放空间(ThreadLocalMap)
而ThreadLocal就是在当前线程的存放空间中存放数据
下面这个例子,在每个线程中存放一个arraylist,而不是大家去公用一个arraylist

public class ThreadLocalTest {
    public static ThreadLocal threadLocal = new ThreadLocal();
    public static ArrayList list = new ArrayList();
    public static class Demo implements Runnable {
        private int i;
        public Demo(int i) {
            this.i = i;
        }
        @Override
        public void run() {
            list.add(i);
            threadLocal.set(list);
            System.out.println(threadLocal.get());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int j = 0; j < 200; j++) {
            es.execute(new Demo(j));
        }
        Thread.sleep(3000);
        System.out.println(list.size());
        es.shutdown();
    }
}

在每个线程内部有一块存储区域叫做ThreadLocalMap
可以看到,ThreadLocal采用set,get存取值方式
只有线程完全关闭时,在ThreadLocalMap中的数据才会被GC回收
这时有一个值得考虑的问题
我们使用线程池来开发的时候,线程池中的线程并不会关闭,它只是处于空闲状态
也就是说,我们如果把过大的数据存储在当前线程的ThreadLocalMap中,线程不断的调用,被空闲...
最后会导致内存溢出
解决方法是当不需要这些数据时
使用ThreadLocal.remove()方法将变量给移除
CAS操作
还有一种脱离锁的机制,那就是CAS
CAS带着三个变量,分别是:
V更新变量:需要返回的变量
E预期值:原来的值
N新值,传进来的新变量
只有当预期值和新值相等时,才会把V=N,如果不相等,说明该操作会让数据无法同步
根据上面的解释,大概就能知道CAS其实也是在保护数据的同步性
当多个线程进行CAS操作时,可想只有一个线程能成功更新,之后其它线程的E和V会不地进行断比较
所以CAS的同步锁的实现是一样的
CAS操作的并发包在Atomic包中,atomic实现了很多类型
不管是AtomicInteger还是AtomicReference,都有相同点,请观察它们的源码:

private volatile V value;
private static final long valueOffset;

以上是AtomicReferenc

private volatile int value;
private static final long valueOffset;

以上是AtomicIntege
都有value,这是它们的当前实际值

valueOffset保存的是value的偏移量
下面给出一个简单的AtomicIntege例子:

public class AtomicTest {
    public static AtomicInteger atomicInteger = new AtomicInteger();
    //public static AtomicReference atomicReference = new AtomicReference();
    public static class Demo implements Runnable{
        @Override
        public void run() {
            for (int j=0;j<1000;j++){
                atomicInteger.incrementAndGet();        //当前值加1并且返回当前值
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i =0;i<10;i++){
            es.submit(new Demo());
        }
        Thread.sleep(5000);
        System.out.println(atomicInteger);
    }
}

你试着执行一下,如果打印出10000说明线程安全

使用CAS操作比同步锁拥有更好的性能
我们来看下incrementAndGet()的源码:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

来看下getAndAddInt()源码:

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
    }

这里有一个循环,再细看源码发现是native的,虽然看不到原生代码,但是可以看出它这里做了一个CAS操作,不断地进行多个变量的比较,只有预设值和新值相等时,才跳出循环
var5就是需要更新的变量,var1和var2是预设值和新值
死锁
讲了那么多无锁的操作,我们来看一下一个死锁的现象
两个线程互相占着对方想得到的锁,就会出现死锁状况

public class DeadLock extends Thread{
    protected String suo;
    public static String zuo = new String();
    public static String you = new String();
    public DeadLock(String suo){
        this.suo=suo;
    }
    @Override
    public void run(){
        if (suo==zuo){
            synchronized (zuo){
                System.out.println("拿到了左,正在拿右......");
                synchronized (you){
                    System.out.println("拿到了右,成功了");
                }
            }
        }
        if (suo==you){
            synchronized (you){
                System.out.println("拿到了右,正在拿左......");
                synchronized (zuo){
                    System.out.println("拿到了zuo,成功了");
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10000;i++){
            DeadLock t1 = new DeadLock(zuo);
            DeadLock t2 = new DeadLock(you);
            t1.start();t2.start();
        }
        Thread.sleep(50000);
    }
}

如图:

image

出现了两个线程的死锁现象,所以说去锁不仅能提升性能,也能防止死锁的产生。

文章来源:https://segmentfault.com/a/1190000012218687

更多参考内容请登录:http://www.roncoo.com/article/index?tn=%E9%AB%98%E5%B9%B6%E5%8F%91

相关文章
|
5月前
|
Java
在高并发环境下,再次认识java 锁
在高并发环境下,再次认识java 锁
36 0
|
5月前
|
消息中间件 NoSQL Java
Java高级开发:高并发+分布式+高性能+Spring全家桶+性能优化
Java高架构师、分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师之路
|
18天前
|
存储 NoSQL Java
探索Java分布式锁:在高并发环境下的同步访问实现与优化
【4月更文挑战第17天】Java分布式锁是解决高并发下数据一致性问题的关键技术,通过Redis、ZooKeeper、数据库等方式实现。它确保多节点共享资源时的同步访问,防止数据不一致。优化策略包括锁超时重试、续期、公平性和性能优化。合理设计分布式锁对支撑大规模分布式系统至关重要。
|
19天前
|
缓存 负载均衡 Java
Java高并发性能指标
Java高并发是指在Java编程环境中,系统能够同时处理大量并发请求或操作的能力。这里的“高”强调的是并发处理的数量级较大,需要系统能够有效地管理多个并发的执行单元,如线程或进程,以确保它们能够高效且正确地执行。
12 0
|
24天前
|
JavaScript Java 测试技术
基于Java的高并发慕课网的设计与实现(源码+lw+部署文档+讲解等)
基于Java的高并发慕课网的设计与实现(源码+lw+部署文档+讲解等)
24 2
|
2月前
|
编解码 分布式计算 网络协议
一文让你深入了解 Java-Netty高性能高并发
一文让你深入了解 Java-Netty高性能高并发
61 1
|
5月前
|
Java Go C语言
高并发时代到底是Go还是Java?
作为一名用过Java和Go开发过微服务架构程序的在校学生的角度思考,本文将从以下几个方便来讲述Go和Java的区别。
|
5月前
|
Java 数据库连接 微服务
Java程序员必学知识:高并发+微服务+数据结构+Mybatis实战实践
BATJ最全架构技术合集:高并发+微服务+数据结构+SpringBoot 关于一线互联网大厂网站的一些特点:用户多,分布广泛、大流量,高并发、海量数据,服务高可用、安全环境恶劣,易受网络攻击、功能多,变更快,频繁发布、从小到大,渐进发展、以用户为中心。 如果你工作中够仔细,你会发现这些特点跟高并发、分布式、微服务、Nginx这些技术密切相关的,是因为只要你的公司在上升,用户量级都会与日俱增,高性能、高并发的问题自然避免不了,话不多说往下看。
|
5月前
|
算法 NoSQL Java
2023年阿里高频Java面试题:分布式+中间件+高并发+算法+数据库
又到了一年一度的金九银十,互联网行业竞争是一年比一年严峻,作为工程师的我们唯有不停地学习,不断的提升自己才能保证自己的核心竞争力从而拿到更好的薪水,进入心仪的企业(阿里、字节、美团、腾讯.....)
|
6月前
|
缓存 容灾 网络协议
Java面试题 -高并发、高可用、分布式
Java面试题 -高并发、高可用、分布式
86 0