JAVA并发课题

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: JAVA并发课题

守护线程

public class DaemonThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> System.out.println("jvm exit success!! ")));
        Thread testThread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(5000);
                    System.out.println("thread still running ....");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        testThread.setDaemon(true);
        testThread.start();
    }
}

1.使用arthas查看线上的程序线程情况 deamon为守护线程

image.png

  1. redis分布式锁续命用守护线程实现

public Boolean tryLock(String key, String value, long expireTime) {
        try {
            //自旋上限
            int waitCount = timeout;
            while (waitCount > 0) {
                //SET命令返回OK ,则证明获取锁成功
                Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(key, value, expireTime, TimeUnit.SECONDS);
                if (setIfAbsent) {
                    //续命
                    Thread demo = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            while (true) {
                                Boolean expire = redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
//有可能已经主动删除key,不需要在续命
                                if(!expire){
                                    return;
                                }
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    });
                    demo.setDaemon(true);
                    demo.start();
                    return true;
                }
                //否则循环等待,在timeout时间内仍未获取到锁,则获取失败
                Thread.sleep(3000);
                waitCount--;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
 
        }
        //没设置到锁,即表示超时了
        return false;
    }

redission使用的就是这种形式,比如上锁10s,10s后就解锁了,守护线程会一直给锁续命,当主线程退出的时候,守护线程也会跟着退出。

  1. 那OOM时会不会调用钩子方法?

image.png
OOM后钩子方法仍然生效,spring的销毁方法也是生效,分布式锁也可使用,nocas服务取消的时候也在使用

线程优先级

“优先级”这个参数通常并不是那么地“靠谱”,理论上说线程的优先级越高,分配到时间片的几率也就越高,但是在实际运行过程中却并非如此,优先级只能作为一个参考数值,而且具体的线程优先级还和操作系统有关

终止线程

  1. interrupt方法

 static class TestThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.print(i+" ");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    static class TestThreadWithSync implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                for (int i = 20; i < 30; i++) {
                    System.out.print(i+" ");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    static class TestThreadWithLock implements Runnable {
        ReentrantLock reentrantLock = new ReentrantLock();
        @Override
        public void run() {
            reentrantLock.lock();
            try {
                for (int i = 30; i < 40; i++) {
                    System.out.print(i+" ");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                reentrantLock.unlock();
            }
        }
    }

    /**
     * 增加判断标识 如果标记中断就暂停执行
    */
    static class TestInterruptedStop implements Runnable {

        @Override
        public void run() {
            System.out.println("开始执行");
            synchronized (this) {
                //如果当前线程被中断,这里需要主动退出
                while (!Thread.currentThread().isInterrupted()) {
                }
                System.out.println("end");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
//        Thread testThread = new Thread(new TestThread());
//        testThread.start();
        Thread testThreadWithSync = new Thread(new TestThreadWithSync());
        testThreadWithSync.start();
//        Thread testThreadWithLock = new Thread(new TestThreadWithLock());
//        testThreadWithLock.start();
//        Thread forEverThread = new Thread(new ForEverThread());
//        forEverThread.start();
//        Thread testInterruptedStop = new Thread(new TestInterruptedStop());
        Thread.sleep(2000);
//        testInterruptedStop.interrupt();
        // 如果线程正常执行 那么调用这个函数是无效的
//        forEverThread.interrupt();
//        testThread.interrupt();
        testThreadWithSync.interrupt();
//        testThreadWithLock.interrupt();

    }
  1. shutdownshutDownNow

网上说的shutDownNow会终止线程池中正在执行的线程,实际操作并不是,其实区别是shutDownNow将队列中没有执行的任务放入到一个 List 集合中,并且返回给调用线程。

  1. 锁升级

当加锁后一个线程访问时,会进入偏向锁状态,当多个线程访问会进入轻量级锁,当多个竞争的线程抢夺该 monitor 的时候,会采用 CAS 的方式,当抢夺次数超过 10 次,或者当前 CPU 资源占用大于 50% 的时候,该锁就会从轻量级锁的状态上升为了重量级锁。

  1. synchronized与lock

    • 支持获取锁超时机制;
    public void tryLockMethod_2() {
    try {
        if (reentrantLock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                i++;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                reentrantLock.unlock();
            }
        } else {
            //todo
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    }
    
    • 支持非阻塞方式获取锁;
    • 支持可中断方式获取锁。

线程池

image.png
1 spring 内部的异步注解


@Service
public class AsyncService {

    //加入注解之后,该方法自动就带有了异步的效果
    @Async
    public void testAsync(){
        try {
            Thread.sleep(1000*2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" async test");
    }
}

直接使用会有一个问题,会无限创建线程,所以要增加配置


@Configuration
public class AsyncExecuteConfig extends AsyncConfigurerSupport {

    @Bean
    public ThreadPoolTaskExecutor asyncExecutor() {
        ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
        threadPool.setCorePoolSize(3);
        threadPool.setMaxPoolSize(3);
        threadPool.setWaitForTasksToCompleteOnShutdown(true);
        threadPool.setAwaitTerminationSeconds(60 * 15);
        return threadPool;
    }

    @Override
    public Executor getAsyncExecutor() {
        return asyncExecutor();
    }
}

@Async会失效吗?
其实他也是通过代理来实现的,如果同一个类中使用@Async也会失效

线程本地变量

ThreadLocal 对象中提供了线程局部变量,它使得每个线程之间互不干扰,一般可以通过重写它的 initialValue 方法机械能赋值。当线程第一次访问它的时候,就会直接触发 initialValue 方法。
是典型的以空间换时间的处理

  1. 原理
public void set(T value) {
    //获取当前请求的线程    
    Thread t = Thread.currentThread();
    //取出Thread类内部的threadLocals变量,这个变量是一个哈希表结构
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //将需要存储的值放入到这个哈希表中
        map.set(this, value);
    else
        createMap(t, value);
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}


static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Java中当一个对象仅被一个弱引用引用时,如果GC运行, 那么这个对象就会被回收。

弱引用的一个特点是它何时被回收是不可确定的;

思考了一下,如果key是弱引用,那么被回收先不说内存泄漏问题,数据本身就回丢失呀,所以操作了一下


public class Demo {
    static ThreadLocal<OOMObject> local = new ThreadLocal<>();
    public static void main(String[] args) {
        local.set(new OOMObject("千云"));
        System.gc();
        OOMObject oomObject = local.get();

        System.out.println(oomObject);
    }
}

赋值GC后仍然可以得到结果,就很奇怪,弱引用并没有被回收,还要进一步的思考

image.png

  1. 应用

获取路径内方法的执行时长

@Configuration
public class TimeCountInterceptor implements HandlerInterceptor
{
    static class CommonThreadLocal<Long> extends ThreadLocal{
        @Override
        protected Object initialValue() {
            return -1;
        }
    }

    private static ThreadLocal<Long> timeCount = new CommonThreadLocal<>();


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        System.out.println("提前赋值的获取:"+ timeCount.get());
        //中间写逻辑代码,比如判断是否登录成功,失败则返回false
        timeCount.set(System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
        long currentTime = System.currentTimeMillis();
        long startTime = timeCount.get();
        long timeUse = currentTime - startTime;
        System.out.println(Thread.currentThread().getName() + "耗时:" + timeUse + "ms");
        timeCount.remove();
    }
}

多线程优化查询速度

CompletableFutureFuture都可以进行优化查询,java8的parallelStream很实用

public List<UserInfoDTO> batchQueryWithParallelV1(List<Long> userIdList) {
   List<UserInfoDTO> resultList = new ArrayList<>();
   //并发调用
   userIdList.parallelStream().forEach(userId -> {
       UserInfoDTO userInfoDTO = userQueryService.queryUserInfoWrapper(userId);
       resultList.add(userInfoDTO);
   });
   return resultList;
}

线程限流

单机版限流,使用Semaphore来实现,如果超过定义的数量那么就丢弃,如果是分布式的服务部署,这种形式就不ok了,要采用redis的形式了

/**
 * @Description:单机版限流
 * @author: yjw
 * @date: 2021/12/20
 */
@Slf4j
@RestController
public class SimpleLimitController {
    private Semaphore semaphore = new Semaphore(2);

    @GetMapping("do-test-limit")
    public void doTest() {
        boolean status = false;
        try {
            //限制流量速度
            status = semaphore.tryAcquire();
            if (status) {
                this.doSomeBiz();
            }
        } catch (Exception e) {
            log.error("[doTest] error is ", e);
        } finally {
            if (status) {
                semaphore.release();
            }
        }
    }


    /**
     * 执行业务逻辑
     */
    private void doSomeBiz() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(20000);
    }
}
相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
21天前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
43 2
|
1月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
53 1
|
20天前
|
存储 Java
Java 中 ConcurrentHashMap 的并发级别
【8月更文挑战第22天】
31 5
|
20天前
|
存储 算法 Java
Java 中的同步集合和并发集合
【8月更文挑战第22天】
20 5
|
19天前
|
缓存 Java 调度
【Java 并发秘籍】线程池大作战:揭秘 JDK 中的线程池家族!
【8月更文挑战第24天】Java的并发库提供多种线程池以应对不同的多线程编程需求。本文通过实例介绍了四种主要线程池:固定大小线程池、可缓存线程池、单一线程线程池及定时任务线程池。固定大小线程池通过预设线程数管理任务队列;可缓存线程池能根据需要动态调整线程数量;单一线程线程池确保任务顺序执行;定时任务线程池支持周期性或延时任务调度。了解并正确选用这些线程池有助于提高程序效率和资源利用率。
30 2
|
21天前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
17 2
|
2月前
|
Java 开发者
Java中的多线程与并发控制
【7月更文挑战第31天】在Java的世界中,多线程是提升程序性能和响应能力的关键。本文将通过实际案例,深入探讨Java多线程的创建、同步机制以及并发包的使用,旨在帮助读者理解并掌握如何在Java中高效地实现多线程编程。
38 3
|
2月前
|
负载均衡 NoSQL Java
|
2月前
|
安全 Java 开发者
探索Java内存模型:可见性、有序性和并发
在Java的并发编程领域中,内存模型扮演了至关重要的角色。本文旨在深入探讨Java内存模型的核心概念,包括可见性、有序性和它们对并发实践的影响。我们将通过具体示例和底层原理分析,揭示这些概念如何协同工作以确保跨线程操作的正确性,并指导开发者编写高效且线程安全的代码。
|
2月前
|
安全 算法 Java
Java 中的并发控制:锁与线程安全
在 Java 的并发编程领域,理解并正确使用锁机制是实现线程安全的关键。本文深入探讨了 Java 中各种锁的概念、用途以及它们如何帮助开发者管理并发状态。从内置的同步关键字到显式的 Lock 接口,再到原子变量和并发集合,本文旨在为读者提供一个全面的锁和线程安全的知识框架。通过具体示例和最佳实践,我们展示了如何在多线程环境中保持数据的一致性和完整性,同时避免常见的并发问题,如死锁和竞态条件。无论你是 Java 并发编程的新手还是有经验的开发者,这篇文章都将帮助你更好地理解和应用 Java 的并发控制机制。