j建设网站备案流程,做中英文网站要注意什么,上海进出口博览会,贵阳手机银行app目录1.前言1.synchronized 关键字1. 互斥2.保证内存可见性3.可重入2. volatile 关键字1.保证内存可见性2.无法保证原子性3.synchronized 与 volatile 的区别1.前言 synchronized关键字和volatile是大家在Java多线程学习时接触的两个关键字#xff0c;很多同学可能学习完就忘记…
目录1.前言1.synchronized 关键字1. 互斥2.保证内存可见性3.可重入2. volatile 关键字1.保证内存可见性2.无法保证原子性3.synchronized 与 volatile 的区别1.前言 synchronized关键字和volatile是大家在Java多线程学习时接触的两个关键字很多同学可能学习完就忘记了本文帮助大家回顾以及学习两个关键字的作用以及说出它们的区别同时也为了自己学习巩固。
1.synchronized 关键字
1. 互斥 属于synchronized最关键的特性可以起到互斥的作用当某个线程执行到某个对象的synchronized中时其他线程如果也执行到同一个对象 的synchronized 时就会进行阻塞等待。
进入synchronized 修饰的代码块此时相当于 加锁退出synchronized 修饰的代码块此时相当于 释放锁
其解决的问题是在多线程环境下多个线程对于同一个变量进行读写操作时可能产生的线程安全问题。 如下图代码
public class Main {static int count 0;static void add() {count;}public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int i 0; i 50000; i) {add();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}按照预期我们希望count的值应该是100000当执行后输出的答案却是 无论多次执行多少次答案总是与预期相差甚远。这是最简单的多线程安全问题。因为count这个操作需要先将count从主内存读入到工作内存然后增值再将更改后的值写回主内存这一系列操作必须保证原子性而在多线程环境下是无法保证的所以我们需要加上synchronized进行上锁以此保证自增这个操作的原子性。 只需更改add方法如下 synchronized static void add() {count;}再次运行后答案与预期相符合 需要注意一点synchronized修饰方法时如果是静态方法则加的是该类对象的锁如果是成员方法则加的是对象锁。
2.保证内存可见性
从上面也可以看出synchronized的工作过程
1.获得互斥锁2.从主内存拷贝变量的最新副本到工作内存3.执行代码4.讲更改后的共享变量的值更新回工作内存5.释放互斥锁
这样的工作流程是一定可以保证内存可见性的。当然有的同学并不了解什么是内存可见性下文讲volatile时我们稍微讲一下因为它也能保证内存可见性。
3.可重入
synchronized同步块对于同一条线程来说是可重入的不会出现将自身锁死的情况。
当然大家可能对 自身锁死 这个情况不太理解我们举例一个代码
public class Main {//锁对象public static Object lock new Object();public static void main(String[] args) {//一次加锁synchronized (lock) {//二次加锁synchronized (lock) {System.out.println(正确输出);}}}
}当线程在一次加锁时会成功加锁当第二次加锁时此时lock已被上锁于是该线程进行阻塞等待但其实这个锁是被它自己拿着的它又不进行释放锁操作于是将自己锁死。这样的锁称之为 不可重入锁。
当我们Java中的synchronized是可重入锁不会出现上面的问题它可以正确打印 如果对上述代码还不够理解可以再看一个二次加锁的例子
public class Main {public int count 0;synchronized void increase() {count;}synchronized void increase2() {increase();}
}在上诉代码中
increase和increase2两个方法都加了synchronized 而且它们的锁对象都是针对当前对象加锁的。在调用increase2时会先给该对象上锁执行调用increase时会二次上锁此时上个锁还未释放这是没问题的因为synchronized是可重入锁。
那是否真的上了两把锁呢 其实并非如此在可重入锁的内部包含了 线程持有者 和 计数器 两个信息。
如果某个线程加锁时发现锁已被占用但又发现占用的恰好是自己时那么然后可以获取到这个锁并让计数器自增解锁时首先会让计数器自减但只有真正自减到0时我们才会真正意义上的将该锁释放以供其他线程获取到。
2. volatile 关键字
相对于 synchronized来说大家可能对volatile会比较陌生我们来看看其有哪些作用。
1.保证内存可见性 简单来说线程在工作时会去主内存中读取数据到工作内存中然后从工作内存读取数据。但是线程从工作内存读取数据的速度要远远的大于从主内存读取数据。
当一个线程大量地从主内存请求同一个变量的值时它会发现这个值一直没变此时jvm会 “自作主张” 的进行优化直接从工作内存读取之前读到的值。这就会导致一个问题其他线程对这个共享变量值进行修改这个线程不能及时地被看到也就读到了一个错误的值。
比如如下代码
public class Main{static int isQuit 0;public static void main(String[] args) {Thread t new Thread(() - {while (isQuit 0) {}System.out.println(t线程执行结束);});t.start();Scanner sc new Scanner(System.in);isQuit sc.nextInt();System.out.println(main线程执行结束);}
}执行以后随便输入一个非零整数 发现t线程仍然在进行而main线程已经结束但按照逻辑其实此时isQuit值被修改为非零t线程也应该结束。这就是由于内存可见性产生的问题main线程修改了isQuit的值t线程并不能及时的接收到。
解决的方法也很简单只需要给isQuit加上volatile关键字这样每次t线程都会强制去主内存中读取isQuit的值从而保证了内存可见性。
2.无法保证原子性 volatile相较于synchronized来说主要在于其无法保证原子性也就是对于下面这个程序即使给count加上volatile我们也无法让count的值为100000。
public class Main {static int count 0;static void add() {count;}public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {for (int i 0; i 50000; i) {add();}});Thread t2 new Thread(() - {for (int i 0; i 50000; i) {add();}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}3.synchronized 与 volatile 的区别 根据上面的总结我们可知synchronized既可以保证原子性还可以保证内存可见性而volatile只能保证内存可见性。那有的人是不是肯定想那我们无脑使用synchronized不就好了吗 那肯定不对synchronized会进行加锁使得效率大大的较低而volatile却不会影响效率所以只有必须要保证原子性的操作我们才选择synchronized进行加锁。