任务定时调度

通过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原子性

Java多线程高级主题的更多相关文章

  1. JAVA高级之路----JAVA多线程

    介绍 这段时间一直在学习和整理一些通往java高级程序猿必备的知识点,有些是工作中必须要知道的,有些是面试必须要知道的, 但是不管怎么样,学习了就不会有坏处,不可能全部记得住,最起码得雁过留痕,知识不 ...

  2. Java多线程 2 线程的生命周期和状态控制

    一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就 ...

  3. java多线程(精华版)

    在 Java 程序中使用多线程要比在 C 或 C++ 中容易得多,这是因为 Java 编程语言提供了语言级的支持.本文通过简单的编程示例来说明 Java 程序中的多线程是多么直观.读完本文以后,用户应 ...

  4. Java多线程系列--“JUC锁”06之 Condition条件

    概要 前面对JUC包中的锁的原理进行了介绍,本章会JUC中对与锁经常配合使用的Condition进行介绍,内容包括:Condition介绍Condition函数列表Condition示例转载请注明出处 ...

  5. Java多线程基础:进程和线程之由来

    转载: Java多线程基础:进程和线程之由来 在前面,已经介绍了Java的基础知识,现在我们来讨论一点稍微难一点的问题:Java并发编程.当然,Java并发编程涉及到很多方面的内容,不是一朝一夕就能够 ...

  6. Java最重要的21个技术点和知识点之JAVA多线程、时间处理、数据格式

    (四)Java最重要的21个技术点和知识点之JAVA多线程.时间处理.数据格式  写这篇文章的目的是想总结一下自己这么多年JAVA培训的一些心得体会,主要是和一些java基础知识点相关的,所以也希望能 ...

  7. 【转】 Java 多线程之一

    转自   Java 多线程 并发编程 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进 ...

  8. 15个顶级Java多线程面试题及答案

    1)现在有T1.T2.T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉.这个多线程问题比 ...

  9. Java 多线程 锁 存款 取款

    http://jameswxx.iteye.com/blog/806968 最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: ja ...

随机推荐

  1. 多项式&生成函数(~~乱讲~~)

    多项式 多项式乘法 FFT,NTT,MTT不是前置知识吗?随便学一下就好了(虽然我到现在还是不会MTT,exlucas也不会用) FTT总结 NTT总结 泰勒展开 如果一个多项式\(f(x)\)在\( ...

  2. JQuery Mobile - 解决页面点击时候,页眉和页脚消失问题!

    当点击页面时候,页眉和页脚会消失!解决方法,在页面和页脚中加入: data-quicklinks="true" 实际使用代码: <div data-role="pa ...

  3. adb shell pm list packages的用法

    abd shell pm list packages     ####查看当前连接设备或者虚拟机的所有包 adb shell pm list packages -d    #####只输出禁用的包. ...

  4. 基本数据类型补充 set集合 深浅拷贝

    一.基本数据类型补充 1,关于int和str在之前的学习中已经介绍了80%以上了,现在再补充一个字符串的基本操作: li = ['李嘉诚','何炅','海峰','刘嘉玲'] s = "_&q ...

  5. iOS-发送短信验证码倒计时

    /** 发送手机验证码 */ -(void)startSenderYzmMessage{ __block ; //倒计时时间 dispatch_queue_t queue = dispatch_get ...

  6. JSONP是什么

    摘自:https://segmentfault.com/a/1190000007935557 一.JSONP的诞生 首先,因为ajax无法跨域,然后开发者就有所思考 其次,开发者发现, <scr ...

  7. [每天解决一问题系列 - 0002] Xcopy cannot copy file with long directory

    现象: 当xcopy的文件的全名(包括目录和文件名)的长度超过255字符时,会copy失败,得到insufficient memory错误 解决方法: 在Server 版的OS中,有robcopy命令 ...

  8. 浏览器中F5和CTRL F5的行为区别及如何强制更新资源

    一.浏览器中F5和CTRL F5的行为区别 我们直接来看效果,下面是我打开qq网页,分别使用F5和CTRL F5,我们来看区别. F5: CTRL F5: 区别: 首先直观上的区别是CTRL F5明显 ...

  9. Maven install [WARNING] The artifact aspectj:aspectjrt:jar:1.5.4 has been relocated to org.aspectj:aspectjrt:jar:1.5.4

    一.背景 最近在给项目打包的时候,在控制台老是出现一行警告:[WARNING] The artifact aspectj:aspectjrt:jar:1.5.4 has been relocated ...

  10. dispatchEvent相关内容

    意思就是:手动触发事件. 我的理解是:类似于jquery中的trigger方法,可以在点击某个dom的时候,触发另一个dom的事件,下面一个我自己尝试的例子: <!DOCTYPE html> ...