任务定时调度

通过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. XML随笔:语法快速入门及当下流行的RSS简析

    今天是本人第一次写博客,之前闭门造车闹出过很多笑话,恰巧这几天刚刚重温了一遍XML的知识,决定把XML的知识再来从头到尾的理一遍,感触颇多,今天分享给大家.希望大家能多多注意其中的要点. 1.定义 首 ...

  2. 背水一战 Windows 10 (50) - 控件(集合类): ItemsControl - 基础知识, 数据绑定, ItemsPresenter, GridViewItemPresenter, ListViewItemPresenter

    [源码下载] 背水一战 Windows 10 (50) - 控件(集合类): ItemsControl - 基础知识, 数据绑定, ItemsPresenter, GridViewItemPresen ...

  3. [leetcode.com]算法题目 - Pow(x, n)

    Implement pow(x, n). class Solution { public: double pow(double x, int n) { // Start typing your C/C ...

  4. python-i春秋验证码识别

    i春秋作家:hlpureboy python+机器学习+验证码识别+源码 简单介绍 最近在写某网站的自动注册,在注册的过程中遇到一些问题,如js的执行.验证码的识别等等,今天给大家如何用python通 ...

  5. Linux上安装java JDK

    yum方式 1.查看yum中的各个版本 yum -y list java* 2.选择一个版本安装(如1.7) yum -y install java-1.7.0-openjdk* 3.安装完成后可查看 ...

  6. iOS开发-带Placeholder的UITextView实现

    iOS中UITextField带有PlaceHolder属性,可以方便用于提示输入.但是同样可以进行文本输入的UITextView控件则没有PlaceHolder属性,还是有些不方便的,尤其是对于略带 ...

  7. [Leetcode]44.跳跃游戏Ⅰ&&45.跳跃游戏Ⅱ

    跳跃游戏链接 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 示例 1: 输入: [2,3,1,1,4] 输出 ...

  8. [原创]内网渗透JSP webSehll连接工具

    工具: JspShellExec编译: VS2012  C# (.NET Framework v2.0)组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.b ...

  9. vue仿微信网页版|vue+web端聊天室|仿微信客户端vue版

    一.项目介绍 基于Vue2.5.6+Vuex+vue-cli+vue-router+vue-gemini-scrollbar+swiper+elementUI等技术混合架构开发的仿微信web端聊天室— ...

  10. 关于一点儿对仓储(Repository)的理解

    仓储(Repository) 内容来源于dudu的 关于Repository模式一文 Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的 ...