java多线程学习笔记-2

本文深入探讨了任务调度的实现方式,包括Java中的Timer和TimerTask类、Quartz框架的使用,以及多线程环境下的单例模式实现。同时,详细解析了Happen-before指令重排、volatile关键字的作用、ThreadLocal的使用场景和可重入锁的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. 任务定时调度
  • 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();

  }

}
  1. Happen-before指令重排
  • 前提是指令没有依赖
  • 代码的执行顺序与预期的不一致
  • 提高性能
  • 每个线程都有一个独立的工作内存,工作内存要与主内存交互。
  1. 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;
	}

}
  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());
	}

}
  1. 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. 可重入锁
    大多数内置锁都是可重入的。如果某个线程试图获取一个已经由它持有的锁时,那么这个请求会立即成功,并且会将这个锁的计数值加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

  1. 锁的分类
  • 悲观锁:synchronized是悲观锁(独占锁),会导致其他所有需要锁的线程挂起,等待持有锁的线程释放锁。
  • 乐观锁:每次不加锁而是假设没有冲突而去完成某项工作。如果因为冲突失败就重试,直到成功为止。
  1. 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的思想。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值