目录
案例 静态成员变量 (synchronized锁非静态方法)
2.4ReentrantLock类是可重入、互斥、实现了Lock接口的锁
1 线程安全(库存超卖)
1:多个线程对同一个变量做写的操作 。
2: 集群部署,多个服务对同一个变量做写的操作 (分布式锁)。等等
简单的小案例
public class ThreadRunnable implements Runnable{
private int a=100;
@Override
public void run() {
while (true){
if(a>1){
a--;
System.out.println(Thread.currentThread().getName()+"=="+a);
}
}
}
public static void main(String[] args) {
//同一个对象,同一变量 a
ThreadRunnable thread = new ThreadRunnable();
//两个线程分别减共享变量a
new Thread(thread).start();
new Thread(thread).start();
}
}
多线程下变量的不可见性
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改共享变量后,另一个线程,不能直接看到线程修改后的变量的最新值。
2 锁用法
2.1 同步方法
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
public synchronized void add(){}
2.2.同步代码块
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可
synchronized(this){
}
2.3 synchronized 作用于静态方法
总结
- 当synchronized作用于静态方法时,其锁就是当前类的class对象锁。由于静态成员不专属于任何一个实例对象,是类成员,因此通过class对象锁可以控制静态 成员的并发操作。
- synchronized作用于非静态方法时,也可以直接锁类。也可以控制静态 成员的并发操作。
- synchronized作用于非静态方法时,必须是同一个对象才能控制静态 成员的并发操作。
需要注意的是如果一个线程A调用一个实例对象的非static synchronized方法,而线程B需要调用这个实例对象所属类的静态 synchronized方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的class对象,而访问非静态 synchronized 方法占用的锁是当前实例对象锁
案例 静态成员变量 (synchronized锁非静态方法)
synchronized修饰非静态方法时,锁的是当前实例,不同实例就不是同一把锁了,就不是线程安全
public class ThreadRunnable5 implements Runnable {
private static int a = 100;
@SneakyThrows @Override
public void run() {
while (true) {
if (a > 1) {
increase4Obj();
Thread.sleep(100);
}
}
}
public synchronized void increase4Obj(){
System.out.println(Thread.currentThread().getName() + "==" + a);
a--;
}
public static void main(String[] args) {
ThreadRunnable5 thread = new ThreadRunnable5();
new Thread(thread).start();
new Thread(thread).start();
}
}
普通方法锁的就是 对象的实例,不同的实例对象时,就是两个锁。无法保证线程安全
案例 静态成员变量 (synchronized锁静态方法 或 直接锁类)
2.4ReentrantLock类是可重入、互斥、实现了Lock接口的锁
lock() : 获得锁
unlock() : 释放锁
注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
3 死锁产生与排查
死锁产生
public class ThreadRunnable3 implements Runnable {
private int count = 1;
private String locak = "aa";
@SneakyThrows @Override
public void run() {
while (true) {
count++;
if (count % 2 == 0) {
synchronized (locak) {
a();
}
} else {
synchronized (this) {
b();
}
}
}
}
public synchronized void a() {
System.out.println("我是a方法" + Thread.currentThread().getName());
}
public void b() {
synchronized (locak) {
System.out.println("我是b方法" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadRunnable3 thread = new ThreadRunnable3();
new Thread(thread).start();
new Thread(thread).start();
}
}
死锁排查
jdk安装目录 jdk1.8.0_341\bin\jconsole.exe
4 线程间的(等待与通知机制)
线程等待wait()和通知notify(),主要用于多线程之间的通信协作,而且这两个方法都是属于Object类,说明任何对象都可以调用这两个方法。
当在一个实例对象上调用了wait()方法之后,当前线程就会在这个对象上等待。直到另外的线程调用了该对象的notify()方法,处于等待状态的线程才得以继续进行。
notifyAll(); 通知所有等待在该对象的线程。
这样,多线程之间就可以用这两个方法进行通信协作了
wait() 让出cup释放锁 这个方法一定注意,不要用if判断处理他的逻辑,不要用if判断处理他的逻辑,不要用if判断处理他的逻辑,
写的不错的文章
wait()和notify()方法的使用_Morning sunshine的博客-CSDN博客_wait和notify的用法
wait()方法的注意点_EclipseO2的博客-CSDN博客
个人觉得了解即可。并没有太多应用场景,jdk8已经有很多api满足我们日常开发,线程间协作。了解原理即可。
5 原子性分类(原理需要分文章讲解太长)
1synchronized jdk1.6开始,锁升级过程,偏向锁--->轻量级锁--->重量级锁
2Lock锁,需要自己实现锁的升级过程,底层基于aqs+cas实现
加锁时情况本地工作内存,获取主存上的最新值。 解锁时将新值刷新到主存里。
3ThreadLocal,需要注意内存泄漏。(但是高效)
4原子类CAS 非阻塞