目录页:https://blog.youkuaiyun.com/u011294519/article/details/88367808
1.synchronized关键字
- 对象锁:synchronized修饰方法或代码块
当一个对象中有synchronized method 或synchronized block 的时候,调用此对象的同步方法或进入其同步区域时,就必须先获得对象锁。
如果此对象的对象锁已被其他调用者占用,则需要等待此锁被释放。java的所有对象都含有一个互斥锁,这个锁由jvm自动获取和释放。
线程进入synchronized 方法的时候获取该对象的锁,当然如果已经有线程获取了这个对象的锁,那么当前线程会等待;
synchronized方法正常返回或者抛异常而终止,jvm会自动释放对象锁。这里也体现了用synchronized来加锁的一个好处,即 :方法抛异常的时候,锁仍然可以由jvm来自动释放。
- 类锁:synchronized修饰静态的方法或者代码块
由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被声明为synchronized。此类所有的实例对象在调用此方法,共用同一把锁,我们称之为类锁。
对象锁是用来控制实例方法之间的同步,而类锁是用来控制静态方法(或者静态变量互斥体)之间的同步的。类锁只是一个概念上的东西,并不是真实存在的,他只是用来帮助我们理解锁定实例方法和静态方法的区别的。
java类可能会有很多对象,但是只有一个Class(字节码)对象,也就是说类的不同实例之间共享该类的Class对象。Class对象其实也仅仅是1个java对象,只不过有点特殊而已。
由于每个java对象都有1个互斥锁,而类的静态方法是需要Class对象。所以所谓的类锁,只不过是Class对象的锁而已。
以上是我恬不知耻的在网上扒的,下面就喊翠花上代码
对象锁代码:
package com.concurrent.coline.part2.syn;
/**
* 对象锁
*/
public class ObjectLockSyn {
/**
* 使用对象锁的线程
*/
private static class InstanceSyn implements Runnable {
private ObjectLockSyn objectLockSyn;
private InstanceSyn(ObjectLockSyn objectLockSyn) {
this.objectLockSyn = objectLockSyn;
}
@Override
public void run() {
System.out.println("TestInstance is running..." + objectLockSyn);
objectLockSyn.instance();
}
}
/**
* 使用对象锁的线程
*/
private static class Instance2Syn implements Runnable {
private ObjectLockSyn objectLockSyn;
private Instance2Syn(ObjectLockSyn objectLockSyn) {
this.objectLockSyn = objectLockSyn;
}
@Override
public void run() {
System.out.println("TestInstance2 is running..." + objectLockSyn);
objectLockSyn.instance2();
}
}
//锁对象方式一
private synchronized void instance() {
try {
Thread.sleep(3000);
System.out.println("objectLockSyn is going..." + this.toString());
Thread.sleep(3000);
System.out.println("objectLockSyn ended " + this.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//锁对象
private void instance2() {
synchronized (this) {
try {
Thread.sleep(3000);
System.out.println("objectLockSyn2 is going..." + this.toString());
Thread.sleep(3000);
System.out.println("objectLockSyn2 ended " + this.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ObjectLockSyn objectLockSyn = new ObjectLockSyn();
Thread t1 = new Thread(new ObjectLockSyn.InstanceSyn(objectLockSyn));
Thread t2 = new Thread(new ObjectLockSyn.Instance2Syn(objectLockSyn));
t1.start();
t2.start();
}
}
运行结果:
代码在sharing-collaboration模块的part2
类锁代码:
package com.concurrent.coline.part2.syn;
/**
* 类锁
*/
public class ClassLockSyn {
private static int total = 10;
// 类锁方式一
public static void classLockMethod() {
int num = 5;
while (num > 0) {
--num;
--total;
System.out.println("Thread name: " + Thread.currentThread().getName() + "---total: " + total);
}
}
// 类锁方式二
public void classLockMethod1() {
synchronized (ClassLockSyn.class) {
int num = 5;
while (num > 0) {
--num;
--total;
System.out.println("Thread name: " + Thread.currentThread().getName() + "---total: " + total);
}
}
}
public static void main(String[] args) throws InterruptedException {
ClassLockSyn classLockSyn = new ClassLockSyn();
new Thread(() -> {
ClassLockSyn.classLockMethod();
}).start();
new Thread(() -> {
classLockSyn.classLockMethod1();
}).start();
}
}
运行结果:
代码在sharing-collaboration模块的part2
2.Volatile关键字
先科普一下:
原子性:即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在Java虚拟机规范中试图定义一种Java内存模型(Java Memory Model,JMM)来屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。那么Java内存模型规定了哪些东西呢,它定义了程序中变量的访问规则,往大一点说是定义了程序执行的次序。注意,为了获得较好的执行性能,Java内存模型并没有限制执行引擎使用处理器的寄存器或者高速缓存来提升指令执行速度,也没有限制编译器对指令进行重排序。也就是说,在java内存模型中,也会存在缓存一致性问题和指令重排序的问题。
Java内存模型规定所有的变量都是存在主存当中(类似于物理内存),每个线程都有自己的工作内存(类似于CPU的高速缓存)。线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作。并且每个线程不能访问其他线程的工作内存。
volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
volatile关键字的可见性和原子性:
被volatile关键字修饰的变量具有可见性,当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去主存中读取新值。但volatile并不能保证原子性,比如两个线程同时从主存获取了变量值,并复制到自己的工作内存中,线程一对变量加1,变量的修改马上被写入主存,但是线程二的工作内存中存储的仍是变量修改前的值,这时候变量加2,修改被写入主存,导致之前线程一的修改结果被覆盖,并没有达到预期结果加3。或许这就是为什么volatile在英语中的意思是不稳定的。
上代码:
package com.concurrent.coline.part2.vola;
/**
* 类说明:演示violate无法提供操作的原子性
*/
public class VolatileUnsafe {
private static class VolatileVar implements Runnable {
private volatile int a = 0;
@Override
public void run() {
try {
String threadName = Thread.currentThread().getName();
a = ++a;
System.out.println(threadName + ":======" + a);
Thread.sleep(100);
a = a + 1;
System.out.println(threadName + ":======" + a);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
VolatileVar v = new VolatileVar();
Thread t1 = new Thread(v);
Thread t2 = new Thread(v);
Thread t3 = new Thread(v);
Thread t4 = new Thread(v);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果如下:
多尝试几次,有惊喜。
那么问题来了,这货不能保证原子性,那有啥用,仔细想想可以在一写多读的情况下使用。
3.ThreadLocal
ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。
- ThreadLocal.get(): 获取ThreadLocal中当前线程共享变量的值。
- ThreadLocal.set(): 设置ThreadLocal中当前线程共享变量的值。
- ThreadLocal.remove(): 移除ThreadLocal中当前线程共享变量的值。
- ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
测试代码:
package com.concurrent.coline.part2.threadlocal;
import java.util.Arrays;
/**
* 类说明:演示ThreadLocal的使用
*/
public class UseThreadLocal {
//可以理解为 一个map,类型 Map<Thread,Integer>
private static ThreadLocal<Integer> threadLaocl = ThreadLocal.withInitial(() -> 1);
/**
* 类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
*/
public static class TestThread implements Runnable {
int id;
private TestThread(int id) {
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":start");
//获得变量的值
Integer s = threadLaocl.get();
s = s + id;
threadLaocl.set(s);
System.out.println(Thread.currentThread().getName() + ":" + threadLaocl.get());
threadLaocl.remove();
System.out.println(Thread.currentThread().getName() + ": After remove :" + threadLaocl.get());
}
}
/**
* 运行3个线程
*/
private void StartThreadArray() {
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
Arrays.stream(runs).forEach(
Thread::start
);
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
运行结果:
ThreadLocalMap的内存泄漏:
由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。