synchronized 关键字最主要的三种使用方式:
1.修饰实例⽅法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁。
synchronized void method() {
//业务代码
}
2.修饰静态⽅法: 也就是给当前类加锁,会作⽤于类的所有对象实例 ,进⼊同步代码前要获得当
前 class 的锁。因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个
静态资源,不管 new 了多少个对象,只有⼀份)。所以,如果⼀个线程 A 调⽤⼀个实例对象的
⾮静态 synchronized ⽅法,⽽线程 B 需要调⽤这个实例对象所属类的静态 synchronized ⽅法,
是允许的,不会发⽣互斥现象,因为访问静态 synchronized ⽅法占⽤的锁是当前类的锁,⽽访
问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁。
synchronized void staic method() {
//业务代码
}
3.修饰代码块 :指定加锁对象,对给定对象/类加锁。 synchronized(this|object) 表示进⼊同步代码
库前要获得给定对象的锁。 synchronized(.class) 表示进⼊同步代码前要获得当前 class 的锁
synchronized(this) {
//业务代码
}
总结:
- synchronized 关键字加到 static 静态⽅法和 synchronized(class) 代码块上都是是给 Class
类上锁。 - synchronized 关键字加到实例⽅法上是给对象实例上锁。
- 尽量不要使⽤ synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能。
双重检验锁方式实现单例模式的原理
双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
- 另外,需要注意 uniqueInstance 采⽤ volatile 关键字修饰也是很有必要。uniqueInstance 采⽤ volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton(); 这段代码其实是分为三步执⾏:
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
- 但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不
会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执
⾏了 1 和 3,此时 T2 调⽤ getUniqueInstance () 后发现 uniqueInstance 不为空,因此返回
uniqueInstance ,但此时 uniqueInstance 还未被初始化。 - 使⽤ volatile 可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。
synchronized 关键字和 volatile 关键字的区别
synchronized 关键字和 volatile 关键字是两个互补的存在,⽽不是对⽴的存在!
- volatile 关键字是线程同步的轻量级实现,所以 volatile 性能肯定⽐ synchronized 关键字要好。但是 volatile 关键字只能⽤于变量⽽ synchronized 关键字可以修饰⽅法以及代码块。
- volatile 关键字能保证数据的可⻅性,但不能保证数据的原⼦性。 synchronized 关键字两
者都能保证。 - volatile 关键字主要⽤于解决变量在多个线程之间的可⻅性,⽽ synchronized 关键字解决
的是多个线程之间访问资源的同步性。
ThreadLocal
通常情况下,我们创建的变量是可以被任何⼀个线程访问并修改的。如果想实现每⼀个线程都有
自己的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。
ThreadLocal 类主要解决的就是让每个线程绑定⾃⼰的值,可以将 ThreadLocal 类形象的⽐喻成
存放数据的盒⼦,盒⼦中可以存储每个线程的私有数据。
如果你创建了⼀个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副
本,这也是 ThreadLocal 变量名的由来。他们可以使⽤ get()和 set() ⽅法来获取默认值
或将其值更改为当前线程所存的副本的值,从⽽避免了线程安全问题。
举个简单的例⼦:
⽐如有两个⼈去宝屋收集宝物,这两个共⽤⼀个袋⼦的话肯定会产⽣争执,但是给他们两个⼈每
个⼈分配⼀个袋⼦的话就不会出现这样的问题。如果把这两个⼈⽐作线程的话,那么ThreadLocal 就是⽤来避免这两个线程竞争的。
ThreadLocal 原理
public class Thread implements Runnable {
......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
......
}
从上⾯ Thread 类 源代码可以看出 Thread 类中有⼀个 threadLocals 和 ⼀个inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把ThreadLocalMap 理解为 ThreadLocal 类实现的定制化的 HashMap 。默认情况下这两个变量都是 null,只有当前线程调⽤ ThreadLocal 类的 set 或 get ⽅法时才创建它们,实际上调⽤这两个⽅法的时候,我们调⽤的是 ThreadLocalMap 类对应的 get() 、 set() ⽅法。
ThreadLocal 类的 set() ⽅法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
通过上⾯这些内容,我们⾜以通过猜测得出结论:最终的变量是放在了当前线程的ThreadLocalMap 中,并不是存在 ThreadLocal 上, ThreadLocal 可以理解为只是 ThreadLocalMap 的封装,传递了变量值。 ThrealLocal 类中可以通过 Thread.currentThread()获取到当前线程对象后,直接通过 getMap(Thread t) 可以访问到该线程的 ThreadLocalMap 对象。
ThreadLocal 内部维护的是⼀个类似 Map 的 ThreadLocalMap 数据结构, key 为当前对象的 Thread 对象,值为 Object 对象。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
......
}
⽐如我们在同⼀个线程中声明了两个 ThreadLocal 对象的话,会使⽤ Thread 内部都是使⽤仅有
那个 ThreadLocalMap 存放数据的, ThreadLocalMap 的 key 就是 ThreadLocal 对象,value 就是
ThreadLocal 对象调⽤ set ⽅法设置的值。
ThreadLocalMap 是 ThreadLocal 的静态内部类。