最近有在跟着某讯课堂学习架构师模块,先系统化的回顾一下多线程相关的知识,之后会持续更新。
synchronized内置锁
定义:线程进入同步代码块或方法的时候会自动获得锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
对象锁,锁的是类的对象实例。
对象锁有两种方式
private synchronized void test1(){ //第一种方式,方法上加synchronized
}
private void test1(){
synchronized(this){ //第二种方式,this方式
}
}
需要注意的是,当下面传入的object对象不同的话,两个线程是可以同时进行的。只有同一个的new实例才能先后执行
SynClzAndInst synClzAndInst = new SynClzAndInst();
Thread t1 = new Thread(new InstanceSyn(object));
SynClzAndInst synClzAndInst2 = new SynClzAndInst();
Thread t2 = new Thread(new Instance2Syn(object));
t1.start();
t2.start();
类锁,锁的是每个类的的Class对象,每个类的的Class对象在一个虚拟机中只有一个,所以类锁也只有一个。
private static synchronized void test1(){
System.out.println("类锁方式一");
}
public void methodName2() {
synchronized (TestLock.class) {
System.out.println("类锁方式二");
}
volatile关键字
最轻量的同步机制,效率高,只能保证可见性。这个涉及到线程的内存模型
每个线程都有自己的线程内存,和一个共享的主内存,一般线程对变量的操作都在自己的工作内存中进行,不能直接读写主内存,不同线程之间不能访问别的线程的工作内存内的变量(只能通过主内存来传递)
volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主内存中读取。
volatile仅能使用在变量级别
但不能保证原子性(线程A修改了变量还没结束时,另外的线程B在主内存可以看到已修改的值,而且可以修改这个变量,而不用等待A释放锁,因为Volatile 变量没上锁),举个栗子
private volatile int age =10;
public int getAge(){
return age;
}
public void setAge(){
age =age+1;
}
上面执行加操作还未完成的时候,另一个线程获取到主内存中的age值,对其进行了修改,再更新到主内存,恰好此时栗子中的加操作执行完成,同步到主内存,此时主内存中留存到最后的值肯定不是我们期待的。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行
使用场景
适合于只有一个线程写,多个线程读的场景,因为它只能确保可见性
ThreadLocal
先上代码,定义一个Integer类型的变量local
static ThreadLocal<Integer> local = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 1;
}
};
先来看一段官方的解释
“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”
我的理解
1、ThreadLocal提供了一种访问某个变量的特殊方式:访问到的变量属于当前线程,即保证每个线程的变量不一样,而同一个线程在任何地方拿到的变量都是一致的,这就是所谓的线程隔离。
2、如果要使用ThreadLocal,通常定义为private static类型,在我看来最好是定义为private static final类型。
它和volatile恰好是有些相反的,可以理解为是个map,类型 Map<Thread,Integer>,存储的是线程和对应的变量副本,即每个线程都保有一份变量的副本(因此也比较占用内存)。它提供了以下几个方法
local.get(); //获取当前线程的变量副本值
local.set(9);//重新设置当前线程的变量副本值
local.remove();//移除值,得到null