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