zoukankan      html  css  js  c++  java
  • 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原子性

  • 相关阅读:
    2021上半年下午第二题
    21年软件设计师上半年下午试题一
    软考下午第三题-用例图和类图
    类图
    用例图-包含、扩展、泛化
    软考下午题二------数据库设计
    软件设计师下午题-数据流图
    IP练习题
    2020软件工程作业02
    2020软件工程作业01
  • 原文地址:https://www.cnblogs.com/yliucnblogs/p/10674465.html
Copyright © 2011-2022 走看看