定时调度+指令重排+volatile+ThreadLocal+可重入锁+锁分类

定时调度

简单的任务调度可以使用Timer,TimerTask两个方法,TimerTask是一个线程的子类,用来指定时间干什么事,Timer用来指定任务的时间。

Timer mt = new Timer();
//1秒后执行任务一次
//mt.schedule(new MyTimer(), 1000);
//2秒后,每个1秒执行一次
//mt.schedule(new MyTimer(), 2000,1000);
//指定时间运行
Calendar cc = new GregorianCalendar(2020,1,18,14,29);
mt.schedule(new MyTimer(), cc.getTime(),1000);

class MyTimer extends TimerTask{
	public void run() {
		for(int i = 0;i < 1;i++) {
			System.out.println("helloWorld");
		}
	}
}

更高级的任务调度可以选用quartz,这是一个框架,需要到官网下载,不过需要科学上网才能访问。

指令重排(HappenBefore)

我们写的代码可能没有按照我们所预期的顺序执行,因为编译器和CPU会尝试重排指令使代码更快的执行。这种情况发生在代码之间没有关系的地方,即这几行代码换顺序对结果没有影响的时候

执行一条指令有四个步骤:

  • 从内存中读取下一个指令(代码)
  • 解码,从寄存器中复制要操作的数据
  • 操作数据
  • 将操作完成的数据覆盖掉寄存器中原来的数据

执行一条指令会有延时,这时cpu会查看下一条指令与这一条指令所需要的数据有没有关系,如果没有关系,就会提前执行。在多线程中指令重排会出现问题

数据依赖

  • 写后读 a = 1; b = a;
  • 写后写 a = 1; a = 2;
  • 写后读 a = b; b = 1;

上面三种情况都会产生数据依赖(语句交换后,结果发生变化),不会发生数据重排。

volatile

线程本身是由自己的工作空间的,即缓存。当线程操作内存中的数据,是先把数据复制到工作空间,操作完后再把数据复制回去。如果不用同步,可能会导致数据储存。而用volatile修饰的变量,线程会直接再内存中修改,不再复制到工作空间操作,保证可见性。这样虽然后面的线程会立即看到数据的改动,但是会降低效率(缓存的加入就是为了提高效率),并且不能保证原子性(原子性:复制数据副本,操作数据副本,把数据覆盖回去同时发生)。现在一般不用了。

volatile int a = 0;

ThreadLocal

ThreadLocal类定义了一大块内存,每个线程在里面都有自己的一块区域,线程只对自己区域内的数据进行修改操作,不会对其他线程的数据造成影响。
ThreadLocal里面有三个常用的方法:get()/set()/initial()。get(),set()方法让线程可以获取,设置自己区域的数据,可以重写initial()方法设置区域内的初始值,用泛型来确定区域内存的数据类型。
API建议定义为private static,
一个类的run()方法和构造方法是两个线程

package ThreadClass;

/**
 * 测试ThreadLocal
 * ThreadLocal:每个线程自身的存储本地,局部区域
 * get/set/initialValue 
 * @author 王星宇
 * @date 2020年2月18日
 */
public class ThreadLocalTest {
	//更改初始值只需要重写initialValue方法
	//1,匿名类
//	private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {
//		protected Integer initialValue() {
//			return 200;
//		};
//	};
	//2,λ推导
	private static ThreadLocal<Integer> tl = ThreadLocal.withInitial(()->200);
	public static void main(String[] args) {
		//获取值
		System.out.println(Thread.currentThread().getName() + "-->" + tl.get());
		//设置值
		tl.set(100);
		System.out.println(Thread.currentThread().getName() + "-->" + tl.get());
		//另一个线程
		new Thread(new MyRun()).start();
	}
	
	static class MyRun implements Runnable{
		public void run() {
			tl.set((int)(Math.random() * 99));
			System.out.println(Thread.currentThread().getName() + "-->" + tl.get());
		}
	}
}

InheritableThreadLocal

用来继承上下文环境的数据,子线程继承(复制)父线程的数据

package ThreadClass;
/**
 * InheritableThreadLocal测试
 * 
 * @author 王星宇
 * @date 2020年2月18日
 */
public class ThreadLocalTest02 {
	private static ThreadLocal<Integer> tl = new InheritableThreadLocal<Integer>();
	public static void main(String[] args) {
		tl.set(99);
		System.out.println(Thread.currentThread().getName() + "-->" + tl.get());
		new Thread(()-> {//由main方法开辟的线程继承了main里面的数据
			System.out.println(Thread.currentThread().getName() + "-->" + tl.get());
		}).start();
	}
}

可重入锁

大多数锁时可重入的,也就是说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立即成功,并且会将这个锁的计数加1,而当线程退出同步代码块时,计数器将会递减,当计数值等于0时,锁释放
如果没有可重入锁的支持,在第二次获取锁的时候会进入死锁的状态。
可重入锁的简单实现:

package ThreadClass;
/**
* lock的实现
* @author 王星宇
* @date 2020年2月18日
*/
public class LockTest {
  Lock lock = new Lock();
  public void a() throws InterruptedException {//第一次上锁
  	lock.lock();
  	dosomething();
  	lock.unlock();
  }
  public void dosomething() throws InterruptedException {//第二次上锁
  	lock.lock();
  	//TODO
  	lock.unlock();
  }
  public static void main(String[] args) throws InterruptedException {
  	LockTest lt = new LockTest();
  	lt.a();
  }
}
class Lock{
  //是否被占用
  private boolean isLocked = false;
  private Thread useBy = null;//锁被谁用
  private int count = 0;//计数锁
  //使用锁
  public synchronized void lock() throws InterruptedException {
  	Thread t = Thread.currentThread();
  	while(isLocked&&useBy != t) {
  		wait();
  	}
  	useBy = t;
  	isLocked = true;
  	count++;
  	System.out.println("锁了第" + count + "次锁");
  }
  //释放锁
  public synchronized void unlock() {
  	Thread t = Thread.currentThread();
  	if(useBy == t) {
  		count--;
  		if(count == 0) {
  			isLocked = false;
  			notify();
  			useBy = null;
  		}
  		System.out.println("解锁第" + count + "个锁");
  	}
  }
}

锁分类

按照是否可重入:

  • 可重入锁:可以重复持有(上述)
  • 不可重入锁:不可以重复持有

按照等待进程是否采用队列

  • 公平锁:线程在等待锁的时候不能插队(按照队列来)
  • 不公平锁:线程在等待锁的时候可以插队

按照是否独占锁

  • 悲观锁:synchronized是独占锁即悲观锁,会导致其他需要锁的线程挂起,等待持有锁的线程释放锁
  • 乐观锁: 每次不加锁,而是假设如果没有冲突就进行某项操作,如果因为冲突失败则重试,知道成功为止

乐观锁的实现

比较并交换(compare and swap,CAS):多个线程改变a值,其中一个线程最先把a值由a1改成了a2,后面的线程来把a1改成a3,改之前要先进行比较,后面的线程发现a值变成了a2,说明自己不是第一个,所以不会修改。
优点:1.效率高 2.CAS是一组原子操作,不会被打断。
缺点:如果后面的线程在比较之前a值又被改回了a1,线程会认为a值没有被改变,会继续修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值