• Java多线程高级主题


    任务定时调度

    通过Timer和TimerTask,我们可以实现定时启动某个线程。

    • java.util.Timer:类似闹钟的功能,本身实现的就是一个线程
    • java.util.TimerTask:一个抽象类,该类实现了Runnable接口,所以该类具备了多线程的能力
    /**
     * 任务调度:借助Timer 和 TimerTask 实现
     */
    public class TimerTask {
        public static void main(String[] args) {
            Timer timer = new Timer();
            // 只执行一次
            // timer.schedule(new MyTask(), 6000L);
            // 多次执行,五秒后执行,每隔两秒打印一次
            Calendar c = new GregorianCalendar(2019, 3, 7, 16, 11, 00);
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(sdf.format(c.getTime()));
            timer.schedule(new MyTask(), c.getTime(), 2000L);
        }
    }
    // 任务类 (多线程)
    class MyTask extends java.util.TimerTask {
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("放空大脑,休息一下。 :-)");
            }
            System.out.println("本次结束了。。。。");
        }
    }
    
    任务调度框架(Quartz)
    Quartz介绍

    Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。

    Spring框架已经集成了 Quartz

    Quartz分为组成部分:

    • Scheduler:调度器,控制所有调度
    • Trigger:触发条件,采用DSL模式
    • JobDetail:需要处理的Job
    • Job:执行逻辑

    DSL模式:Domain-specific language领域特定语言,针对一个特定的领域,具有受限表达性的一种计算机程序语言,即领域专用语言,声明式编程。特点就是 简洁、连贯。例如:StringBuilder/StringBuffer的append()方法。

    举例(例子用到了Quartz相关jar包,下载地址:http://www.quartz-scheduler.org/downloads

    /**
     * 任务
     */
    public class HelloJob implements Job {
        @Override
        public void execute(JobExecutionContext context) {
            System.out.println("--------开始--------");
            System.out.println("Hello World! - " + new Date());
            System.out.println("--------结束--------");
        }
    
    }
    ---------------------------------------------------------------------------------------
    /**
     * 任务处理
     */
    public class QuartzTest {
    
      public static void main(String[] args) throws Exception {
        // 1、创建Schedule工厂
        SchedulerFactory sf = new StdSchedulerFactory();
        // 2、从工厂获取调度器
        Scheduler sched = sf.getScheduler();
        // 3、创建JobDetail withIdentity() 方法参数 放入唯一标识 job1、group1
        JobDetail job = JobBuilder.newJob(HelloJob.class)
                .withIdentity("job1", "group1").build();
        // 获取下一秒时间 下一秒开始执行
        Date runTime = DateBuilder.evenSecondDateAfterNow();
        // 4、触发器 withIdentity() 方法参数 放入唯一标识 trigger1、group1
        // 执行一次
        // Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
        // 间隔执行  间隔五秒,重复三次
        SimpleScheduleBuilder simpleSchedule = SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3);
        SimpleTrigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startAt(runTime).withSchedule(simpleSchedule).build();
        //5、 注册任务和触发条件
        sched.scheduleJob(job, trigger);
        // 启动
        sched.start();
        // 等待20秒
        try {
          Thread.sleep(20L * 1000L);
        } catch (Exception e) {
          e.printStackTrace();
        }
        // 20秒后停止任务
        sched.shutdown(true);
      }
    }
    

    HappenBefore

    代码的执行顺序与预期的不一致。
    在这里插入图片描述

    代码重排建立在代码和代码之间没有任何依赖的情况下的。

    数据依赖

    如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖。

    数据依赖分为以下三种类型:

    名称 代码示例 说明
    写后读 a = 1; b = a; 写一个变量之后,再读这个变量
    写后写 a = 1; a = 2; 写一个变量之后,再写这个变量
    读后写 a = b; b = 1; 读一个变量之后,再写这个变量

    上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序。

    执行步骤:
    1、获取指令
    2、从寄存器中存储值
    3、操作
    4、写回

    volatile

    volatile保证了线程间变量的可见性,简单地说就是当线程A对变量X进行了修改,在线程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:

    • 线程对变量进行修改之后,要立刻回写到主内存
    • 线程对变量读取的时候,要从主内存中读,而不是缓存
      在这里插入图片描述
      各线程的工作内存间彼此独立、互补可见,在线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量(非线程内构造的对象)的副本,即为提高执行效率

    volatile是不错的机制,但是volatile不能保证原子性

    /**
     * volatile 用于保证数据的同步,也就是可见性
     */
    public class VolatileTest {
        private volatile static int num = 0;
        public static void main(String[] args) throws InterruptedException {
            new Thread(() -> {
                // 此处没有在多线程情况下同步 num ,所以即使num不为0时还是会一直运行
                // 解决办法,加上 volatile 关键字修饰 num 变量
                for (; num == 0; ) {
                    // 此处不写代码
                }
            }).start();
            Thread.sleep(1000L);
            num = 1;
        }
    }
    

    ThreadLocal

    代表每个线程本地存储区域。

    • 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程
    • ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且又能够达到线程安全的目的。说白了,ThreadLocal就是在多线程环境下保证成员变量的安全,常用方法有:
      • get
      • set
      • initialValue

    每个线程自身的存储本地、局部区域

    /**
     * ThreadLocal:每个线程自身的存储本地、局部区域
     */
    public class ThreadLocalTest01 {
        // 初始化
        // private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
        // 更改初始值
        private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
    
        public static void main(String[] args) {
            threadLocal.set(99);
            System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
    
            new Thread(new MyRun()).start();
            
            new Thread(new MyRun()).start();
        }
    
        public static class MyRun implements Runnable {
            @Override
            public void run() {
                threadLocal.set((int) (Math.random() * 99));
                System.out.println(Thread.currentThread().getName() + "--->" + threadLocal.get());
            }
        }
    }
    

    每个线程自身的数据,更改后不会影响其它线程

    /**
     * 利用ThreadLocal实现发糖果小案例
     * 每个线程自身的数据,更改后不会影响其它线程
     */
    public class ThreadLocalTest02 {
        private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                new Thread(new MyRun()).start();
            }
        }
        public static class MyRun implements Runnable {
            @Override
            public void run() {
                Integer left = threadLocal.get();
                System.out.println(Thread.currentThread().getName() + "得到了-->" + left);
                threadLocal.set(left - 1);
                System.out.println(Thread.currentThread().getName() + "还剩下->" + threadLocal.get());
            }
        }
    }
    

    分析ThreadLocal上下文(环境)

    /**
     * 分析ThreadLocal上下文(环境)
     */
    public class ThreadLocalTest03 {
        private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
        public static void main(String[] args) {
            new Thread(new MyRun()).start();
        }
        public static class MyRun implements Runnable {
            public MyRun() {
                // 这里的 Thread.currentThread().getName() 实际上时 main 方法里的
                // 在这里修改 threadLLocal的值,只对main线程有影响
                // 构造器 哪里调用,就属于哪里
                System.out.println(Thread.currentThread().getName() + "-->" + threadLocal.get());
            }
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "还剩下->" + threadLocal.get());
            }
        }
    }
    

    可重入锁

    锁作为并发共享数据保证一致性的工具,大多数内置锁都是可重入的,也就是说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次企图获取锁时将会进入死锁状态。

    举例:

    不可重入锁

    /**
     * 不可重入锁:锁不可以延续使用
     */
    public class NotReentrantLock {
    
        private Lock lock = new Lock();
    
        public void a() {
            lock.lock();
            b();
            lock.unLock();
        }
    
        // 不可重入锁
        public void b() {
            lock.lock();
    
            lock.unLock();
        }
    
        public static void main(String[] args) {
            NotReentrantLock lock = new NotReentrantLock();
            lock.a();
            lock.b();
        }
    
    }
    
    class Lock {
        // 是否占用
        private boolean isLockd = false;
    
        // 使用锁
        public synchronized void lock() {
            for (; isLockd; ) {
                try {
                    wait(); // 等待,线程进入阻塞状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            isLockd = true;
        }
        // 释放锁
        public synchronized void unLock() {
            isLockd = false;
            notify();   // 唤醒等待线程
        }
    
    }
    

    可重入锁(JUC包下JDK提供了ReentrantLock类实现重入锁)

    /**
     * 可重入锁:锁可以延续使用
     */
    public class ReentrantLock {
    
        // private java.util.concurrent.locks.ReentrantLock lock = new java.util.concurrent.locks.ReentrantLock();
        private ReLock lock = new ReLock();
    
        public void a() {
            lock.lock();
            System.out.println(lock.getHoldCount());
            b();
            lock.unlock();
            System.out.println(lock.getHoldCount());
        }
    
        // 不可重入锁
        public void b() {
            lock.lock();
            System.out.println(lock.getHoldCount());
            lock.unlock();
            System.out.println(lock.getHoldCount());
        }
    
        public static void main(String[] args) {
            ReentrantLock lock = new ReentrantLock();
            lock.a();
    
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println(lock.lock.getHoldCount());
        }
    
    }
    
    class ReLock {
        // 是否占用
        private boolean isLockd = false;
        // 存储线程
        private Thread lockedBy = null;
        // 计数器
        private int holdCount = 0;
    
        // 使用锁
        public synchronized void lock() {
            Thread thread = Thread.currentThread();
            for (; (this.isLockd && (this.lockedBy != thread)); ) {
                try {
                    wait(); // 等待,线程进入阻塞状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.isLockd = true;
            this.lockedBy = thread;
            this.holdCount ++;
        }
        // 释放锁
        public synchronized void unlock() {
            if (this.lockedBy == Thread.currentThread()) {
                this.holdCount--;
                // 等于0 标识没有使用了
                if (this.holdCount == 0) {
                    this.isLockd = false;
                    notify();   // 唤醒等待线程
                    this.lockedBy = null;
                }
            }
        }
        public int getHoldCount() {
            return holdCount;
        }
    }
    

    CAS 原子操作

    锁分为两类:

    • 悲观锁:synchronized时独占锁即悲观锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
    • 乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。

    Compare and Swap 比较并交换:

    • 乐观锁的表现

    • 有三个值:一个当前内存值V、旧的预期值A、将更新的值B。先获取到内存当中当前的内存值V,再将内存值V和原值A作比较,要是相等就修改为要修改的值B并返回true,否则什么都不做,并返回false。

    • CAS是一组原子操作,不会被外部打断。

    • 属于硬件级别的操作(利用CPUCAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。

    • ABA问题:如果变量V初次读取的时候是A,并且在准备赋值的时候检查到它仍然是A,那能说明它的值没有被其它线程修改过了吗?如果在这期间曾经被改成B,然后又改回A,那CAS操作就会误认为它从来没有被修改过。

    /**
     * CAS:比较并交换
     */
    public class CAS {
    
        // 库存
        private static AtomicInteger stock = new AtomicInteger(5);
    
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    try {
                        // 模拟网络延时
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    int result = stock.decrementAndGet();
                    String name = Thread.currentThread().getName();
                    if (result < 1) {
                        System.out.println(name + "--->抢完了。。。");
                        return;
                    }
                    System.out.println(name + "抢了一个商品--->还剩" + result + "商品");
                }).start();
            }
        }
    }
    

    Java中JUC包下的atomic包下类具有CAS原子性

  • 相关阅读:
    Spring spEL
    Spring 使用外部部署文件
    Spring 自动装配
    spring 属性配置细节
    hdu 1054 Strategic Game
    fzu 2037 Maximum Value Problem
    将博客搬至CSDN
    HDU 4714 Tree2Cycle
    HDU 1009 The Shortest Path in Nya Graph
    POJ 1942 Paths on a Grid 组合数的优化
  • 原文地址:https://www.cnblogs.com/yliucnblogs/p/10674465.html
Copyright © 2020-2023  润新知