一、volatile
1.1回顾线程不安全因素
a.抢占式执⾏
b. 每个线程操作⾃⼰的变量
c. ⾮原⼦操作
d. 内存可⻅性
e. 指令重排序
1.2 volatile 解决内存可⻅性和指令重排序
a.代码在写⼊ volatile 修饰的变量的时候:
改变线程⼯作内存中volatile变量副本的值
将改变后的副本的值从⼯作内存刷新到主内存
b.从主内存中读取volatile变量的最新值到线程的⼯作内存中
从⼯作内存中读取volatile变量的副本
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了
volatile 的缺点:
volatile 虽然可以解决内存可⻅性和指令重排序的问题,但是解决不了原子性问题。
二、锁(synchronized和lock)
java中的锁是解决线程安全问题的最主要手段。
2.1内存锁synchronized锁
2.1.1 synchronized 基本使用
a.修饰普通⽅法
public class ThreadSynchronized2 {
private static int num=0;
static class Counter{
private static int MAX_COUNT=100000;
//++方法
public synchronized void increment(){
for (int i = 0; i < MAX_COUNT; i++) {
num++;
}
}
//--方法
public synchronized void decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread thread1=new Thread(()->{
counter.increment();
});
Thread thread2=new Thread(()->{
counter.decrement();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果:"+num);
}
}
b.修饰静态⽅法
public class ThreadSynchronized {
private static int num=0;
static class Counter{
private static int MAX_COUNT=100000;
//++方法
public static synchronized void increment(){
for (int i = 0; i < MAX_COUNT; i++) {
num++;
}
}
//--方法
public static synchronized void decrement(){
int temp=0;
for (int i = 0; i < MAX_COUNT; i++) {
num--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()->{
Counter.increment();
});
Thread thread2=new Thread(()->{
Counter.decrement();
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果:"+num);
}
}
c. 修饰代码块
注意事项:
a.修饰代码块时,对于同一个业务的多个对象的加锁对象,注意加锁的应是同一个对象(即加同一把锁)
b.synchronized修饰代码块,代码块在静态方法中时,不能使用this。
synchronized中的对象可以有以下三种形式:
public void method(){
//1.使用this锁当前对象
synchronized (this){
}
//2.锁当前类对象
synchronized (SynchronizedDemo.class){
}
//3.自定义的锁对象
Object obj=new Object();
synchronized (obj){
}
}
2.1.2synchronized 特性
1.互斥性
synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待.
a.进⼊ synchronized 修饰的代码块, 相当于 加锁
b.退出 synchronized 修饰的代码块, 相当于 解锁
2.刷新内存
synchronized 的⼯作过程:
a.获得互斥锁
b.从主内存拷⻉变量的最新副本到⼯作的内存
c.执⾏代码
d.将更改后的共享变量的值刷新到主内存
e.释放互斥锁
3.可重入性
synchronized 同步块对同⼀条线程来说是可重⼊的,不会出现⾃⼰把⾃⼰锁死的问题。
public class ThreadSynchronized {
public static void main(String[] args) {
synchronized (ThreadSynchronized.class){
System.out.println("当前主线程已经得到了锁");
synchronized (ThreadSynchronized.class){
System.out.println("当前主线程再次得到了锁");
}
}
}
}
2.1.3synchronized的底层实现
a.在JVM层面:依靠监视器Monitor实现的
b.在操作系统层面:基于操作系统的互斥锁(Mutex)来实现的
监视器是一个概念或者说是一个机制,它用来保障在任何时候,只有一个线程能够执行指定区域的代码。
以下是synchronized在字节码层面的实现:
public class SynchronizedToMonitorExample {
public static void main(String[] args) {
int count = 0;
synchronized (SynchronizedToMonitorExample.class) {
for (int i = 0; i < 10; i++) {
count++;
}
}
System.out.println(count);
}
}
将上述代码翻译成字节码:
在main方法中多了monitorenter和moniterexit两个指令,他们分别表示进入监视器和退出监视器,由此得出在JVM层面synchronized是依靠监视器实现的。
2.1.4锁升级
在jdk1.6之前synchronized使用的较少,因为synchronized默认使用重量级锁实现,所以性能较差。jdk1.6对synchronized做了优化,实现了锁升级。
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
2.2可重入锁lock(ReentrantLock)
2.2.1手动锁的实现
实现步骤:
a.创建Lock
b.加锁lock.lock()
c.释放锁lock.unlock()
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**\
* 关于可重入锁的基本使用
*/
public class ThreadLock {
public static void main(String[] args) {
//1.创建锁对象
Lock lock = new ReentrantLock();
//2.加锁操作
lock.lock();
try{
System.out.println("你好 ReentrantLock");
}finally {//释放锁一定要放在try...finally中
//3.释放锁
lock.unlock();
}
}
}
注意事项:
1.释放锁unlock操作一定要放在finally代码块中,防止因为业务代码有异常直接结束执行,而导致的锁资源永久占用的问题。
2.加锁lock.lock()一定要放在try之前,或者是try的首行。
原因有两个:①如果没放在try的首行或try前面,如果因为try中代码异常导致加锁失败,还会执行finally中释放锁的操作;②释放锁的异常会覆盖try中的业务异常,增加排查难度。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock1 {
public static void main(String[] args) {
//1、创建锁对象
Lock lock = new ReentrantLock();
try {
//业务代码(可能会非常复杂->导致异常)
int y=10/0;
//2、加锁操作
lock.lock();
}finally {//unlock一定要放在finally中
//3.释放锁
lock.unlock();
}
}
}
2.2.2非公平锁
Lock可以指定锁的类型,默认情况下创建一个非公平锁,非公平锁的执行效率较高。
创建锁时参数默认为false,传递参数true时会创建一个公平锁
2.2.3synchronized VS Lock
1.Lock更灵活,有更多的方法:比如tryLock()…
2.锁的类型不同,Lock默认是非公平锁,但可以设置为公平锁,而synchronized只能是非公平锁。
3.synchronized可以修饰方法(静态方法、普通方法)和代码块,而Lock只能修饰代码块。
4.synchronized是JVM层面提供的锁,它是自动进行加锁和释放锁操作,对于开发者是无感的,而 Lock需要开发者自己进行加锁和释放锁的操作。
5.调用lock()方法和synchronized线程等待锁的状态不同,lock方法会让线程状态变为waiting,而synchronized会让线程状态变成blocked。
使用synchronized加锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadSynchronized1 {
public static void main(String[] args) throws InterruptedException {
Lock lock=new ReentrantLock();
Thread t1=new Thread(()->{
synchronized (ThreadLock1.class) {
System.out.println("线程1得到了锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1释放了锁");
}
});
t1.start();
Thread t2=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (ThreadLock1.class) {
System.out.println("线程2获取到了锁");
}
});
t2.start();
Thread.sleep(1500);
System.out.println("线程2的状态:"+t2.getState());
}
}
调用lock方法加锁:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLock2 {
public static void main(String[] args) throws InterruptedException {
Lock lock=new ReentrantLock();
Thread t1=new Thread(()->{
lock.lock();
System.out.println("线程1得到了锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("线程1释放锁");
lock.unlock();
}
});
t1.start();
Thread t2=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
System.out.println("线程2获取到了锁");
}finally {
lock.unlock();
}
});
t2.start();
Thread.sleep(1500);
System.out.println("线程2的状态:"+t2.getState());
}
}