Java线程的关键字

synchronized

synchronized关键字解决的是多个线程之间访问资源的同步性,可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。

synchronized使用方式

修饰实例方法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
修饰静态方法: 也就是给当前加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个静态资源,不管new了多少个对象,只有⼀份)。所以如果⼀个线程A调⽤⼀个实例对象的⾮静态 synchronized ⽅法,⽽线程B需要调⽤这个实例对象所属类的静态 synchronized ⽅法,是允许的,不会发⽣互斥现象,因为访问静态synchronized ⽅法占⽤的锁是当前类的锁,⽽访问⾮静态 synchronized ⽅法占⽤的锁是当前实例对象锁。
修饰代码块: 指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁。

volatile的作用

保证变量可见性:把变量声明为volatile,这就指示 JVM,这个变量是不稳定的,每次使⽤它都到主存中进⾏读取。
原因:在当前的 Java 内存模型下,线程可以把变量保存本地内存(⽐如机器的寄存器)中,⽽不是直接在主存中进⾏读写。这就可能造成⼀个线程在主存中修改了⼀个变量的值,⽽另外⼀个线程还继续使⽤它在寄存器中的变量值的拷⻉,造成数据的不⼀致。
防止指令重排序:Java 在编译器以及运⾏期间的优化,代码的执⾏顺序未必就是编写代码时候的顺序,比如说new一个对象一共分为三步:
1.堆中开辟一块区域,为对象分配内存空间;
2.初始化对象;
3.将对象指定分配的内存地址。
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1>3>2。在多线程的场景下,例如,线程 1 执⾏了 1 和 3,此时线程2获取对象时,对象还并没有执行第2步初始化,最终只能得到一个未被初始化的对象。

synchronized和volatile的区别

1.多线程访问volatile关键字不会发⽣阻塞,⽽synchronized关键字可能会发⽣阻塞;
2.volatile比synchronized更轻量级,性能更好;
3.volatile只能作用于变量,而synchronized能作用于类、对象和方法;
4.volatile关键字能保证数据的可⻅性,但不能保证数据的原⼦性。synchronized关键字两者都能
保证。

并发编程的三个重要特性

1.原子性 : ⼀个的操作或者多次操作,要么所有的操作全部都得到执⾏并且不会收到任何因素的⼲扰⽽中断,要么所有的操作都执⾏,要么都不执⾏。 synchronized 可以保证代码⽚段的原⼦性。
2. 可见性 :当⼀个变量对共享变量进⾏了修改,那么另外的线程都是⽴即可以看到修改后的最新值。 volatile 关键字可以保证共享变量的可⻅性。
3. 有序性 :代码在执⾏的过程中的先后顺序,Java 在编译器以及运⾏期间的优化,代码的执⾏顺序未必就是编写代码时候的顺序。 volatile 关键字可以禁⽌指令进⾏重排序优化。

ThreadLocal

ThreadLocal可以使每一个线程都有自己的本地变量。
如果你创建了⼀个 ThreadLocal 变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是 ThreadLocal 变量名的由来。他们可以使⽤ get() 和 set()⽅法来获取默认值或将其值更改为当前线程所存的副本的值,从⽽避免了线程安全问题。

ThreadLocal 内存泄露问题

ThreadLocalMap 中使⽤的 key 为 ThreadLocal 的弱引⽤,⽽ value 是强引⽤。所以,如果ThreadLocal 没有被外部强引⽤的情况下,在垃圾回收的时候,key 会被清理掉,⽽ value 不会被清理掉。这样⼀来, ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远⽆法被GC 回收,这个时候就可能会产⽣内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调⽤ set() 、 get() 、 remove() ⽅法的时候,会清理掉 key 为 null 的记录。使⽤完ThreadLocal ⽅法后 最好⼿动调⽤ remove() ⽅法。

猜你喜欢

转载自blog.csdn.net/weixin_43278644/article/details/111130212