- 任务定时调度
- Timer 和 TimerTask类
/**
* 任务调度: Timer 和 TimerTask类
*
* @author beeworkshop
*
*/
public class TimerTest01 {
public static void main(String[] args) {
Timer timer = new Timer();
// 执行安排
// timer.schedule(new MyTask(), 1000); //执行任务一次(1秒之后执行)
timer.schedule(new MyTask(), 1000, 200); // 执行多次(每隔200ms执行一次)
// Calendar cal = new GregorianCalendar(2020, 12, 31, 21, 53, 54);
// timer.schedule(new MyTask(), cal.getTime(), 200); // 指定时间开始(每隔200ms执行一次)
// timer.schedule(new MyTask(), new Date(System.currentTimeMillis()+1000), 1000);
}
}
//任务类
class MyTask extends TimerTask {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("放空大脑休息一会");
}
System.out.println("------end-------");
}
}
- 任务调度框架Quartz
下载网址:http://www.quartz-scheduler.org/
Scheduler:调度器,控制所有的调度
Trigger:触发条件,采用DSL(Domain Specific Language)模式
JobDetail:需要处理的Job
Job:执行逻辑
-
DSL:
- Method chaining(不需要接收引用,直接引用),Fluent style,builder
- Nested Functions
- Lambda Expressions/Closures
- Functional Sequence
public class QuartzTest {
public void run() throws Exception {
// 1、创建 Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//2、从工厂中获取调度器
Scheduler sched = sf.getScheduler();
// 3、创建JobDetail
JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
// 时间
Date runTime = evenSecondDateAfterNow();
// 4、触发条件
//Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
.withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
// 5、注册任务和触发条件
sched.scheduleJob(job, trigger);
// 6、启动
sched.start();
try {
// 100秒后停止
Thread.sleep(100L * 1000L);
} catch (Exception e) {
}
sched.shutdown(true);
}
public static void main(String[] args) throws Exception {
QuartzTest example = new QuartzTest();
example.run();
}
}
- Happen-before指令重排
- 前提是指令没有依赖
- 代码的执行顺序与预期的不一致
- 提高性能
- 每个线程都有一个独立的工作内存,工作内存要与主内存交互。
- volatile
保证线程间变量的可见性,但不能保证原子性。
需符合的规则:
- 线程对变量进行修改后,要立即回写到主内存。
- 线程对变量读取的时候,要从主内存中读,而不是缓存。
各线程的工作内存间彼此独立,互不可见。在线程启动的时候,虚拟机为每个线程分配一块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要的共享变量(非线程内构造的对象)的副本,为了提高执行效率。
volatile用于保证数据的同步,也就是可见性。
public class VolatileTest {
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while(num==0) { //此处不要编写代码
//让主线程忙得不可开交
}
}) .start();
Thread.sleep(1000);
num = 1;
}
}
- 多线程环境下的单例模式
- double checking+volatile(DCL模式)
public class DoubleCheckedLocking {
//2、提供私有的静态属性
//没有volatile其他线程可能访问一个没有初始化的对象(防止指令重排的影响)
private static volatile DoubleCheckedLocking instance;
//1、构造器私有化
private DoubleCheckedLocking() {
}
//3、提供公共的静态方法 --> 获取属性
public static DoubleCheckedLocking getInstance() {
//再次检测
if(null!=instance) { //避免不必要的同步 ,已经存在对象
return instance;
}
synchronized(DoubleCheckedLocking.class) {
if(null == instance) {
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
}
return instance;
}
public static DoubleCheckedLocking getInstance1(long time) {
if(null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance());
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance());
}
}
- ThreadLocal
- 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量,只有线程自己能看见,不会影响其他线程。
- ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享使用,并且能达到线程安全的目的。ThreadLocal就是想在多线程环境下去保证成员变量的安全。常用的方法就是get/set/initialValue方法。
- JDK建议ThreadLocal定义为private static。
- ThreadLocal最常用的地方就是为每一个线程绑定一个数据库连接,HTTP请求,用户身份信息等。这样一个线程所有调用的方法都可以非常方便地访问这些资源。
~ Hibernate的Session工具类HibernateUtil
~ 通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
ThreadLocal:每个线程自身的存储本地、局部区域。
public class ThreadLocalTest01 {
//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> ();
//更改初始化值
/*private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> () {
protected Integer initialValue() {
return 200;
};
};*/
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);
public static void main(String[] args) {
//获取值
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//设置值
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{
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{
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:分析上下文 环境 起点
构造器: 哪里调用 就属于哪里 找线程体
run方法:本线程自身的
public class ThreadLocalTest03 {
private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 1);
public static void main(String[] args) {
new Thread(new MyRun()).start();
new Thread(new MyRun()).start();
}
public static class MyRun implements Runnable{
public MyRun() {
threadLocal.set(-100);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
}
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//new Thread(new MyRunxxx()).start();
}
}
}
InheritableThreadLocal:继承上下文 环境的数据 ,拷贝一份给子线程
public class ThreadLocalTest04 {
private static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
//子线程由main线程开辟
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
threadLocal.set(200);
System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
}) .start();
}
}
- 可重入锁
大多数内置锁都是可重入的。如果某个线程试图获取一个已经由它持有的锁时,那么这个请求会立即成功,并且会将这个锁的计数值加1。当线程退出同步代码块时,计数器会递减。当计数值为0时,锁释放。如果没有可重入锁的支持,在第二次企图获得锁时,线程将会进入死锁状态。
可重入锁: 锁可以延续使用 + 计数器
public class LockTest03 {
ReLock lock = new ReLock();
public void a() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
doSomething();
lock.unlock();
System.out.println(lock.getHoldCount());
}
//不可重入
public void doSomething() throws InterruptedException {
lock.lock();
System.out.println(lock.getHoldCount());
//...................
lock.unlock();
System.out.println(lock.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
LockTest03 test = new LockTest03();
test.a();
Thread.sleep(1000);
System.out.println(test.lock.getHoldCount());
}
}
// 可重入锁
class ReLock{
//是否占用
private boolean isLocked = false;
private Thread lockedBy = null; //存储线程
private int holdCount = 0;
//使用锁
public synchronized void lock() throws InterruptedException {
Thread t = Thread.currentThread();
while(isLocked && lockedBy != t) {
wait();
}
isLocked = true;
lockedBy = t;
holdCount ++;
}
//释放锁
public synchronized void unlock() {
if(Thread.currentThread() == lockedBy) {
holdCount --;
if(holdCount ==0) {
isLocked = false;
notify();
lockedBy = null;
}
}
}
public int getHoldCount() {
return holdCount;
}
}
系统实现的可重入锁:ReentrantLock
- 锁的分类
- 悲观锁:synchronized是悲观锁(独占锁),会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
- 乐观锁:每次不加锁而是假设没有冲突而去完成某项工作。如果因为冲突失败就重试,直到成功为止。
- CAS(Compare and swap)
- 乐观锁的实现
- 三个值:
当前内存值V;
旧的预期值A;
将要更新的值B;
先获取到内存值V,再将内存值V和原值A作比较。如果相等就修改为要修改的值B并返回true。否则什么都不做,并返回false。
- CAS是一组原子操作。
- 数据硬件级别的操作(利用CPU的CAS指令,同时借助JNI来完成的非阻塞算法),效率比加锁操作高。
- ABA问题:
如果变量A初次读取的时候是A,并且在准备赋值的时候检查到它任然是A,那能说明它的值没有被其他线程修改过吗?如果在这段时间内曾经被改成B,然后又改回A,那CAS操作就会误以为它从来都没有被修改过。需通过log来判定是否曾经被修改过。
要修改的值num对应一个版本号ver。每当num被更新就ver++。多个线程要来更新num,只有当线程携带的ver’与ver相等时才能更新num,否则不能更新。
类似Atomic的操作都是应用CAS的思想。