引用了
Lock与synchronized 的区别
详解synchronized与Lock的区别与使用
Java并发编程:volatile关键字解析
volatile
['vɒlətaɪl] 易变化的
synchronized
['sɪŋkrənaɪzd] 同步的
reentrant (lock)
[ri:'entrənt] 可重入
为什么要有锁
①cpu处理以线程为单位,cpu又有一块硬件叫cache(快速缓存区),每个线程对应了自己的一块快速缓存区。因为cpu的处理速度很快,内存处理速度太慢了,所以中间有一块缓存区,我们熟悉的cache,经常听到的几级缓存。
②假设现在是单核cpu,所有的线程看起来并行,其实是串行。只是不停地在线程之间切换。
③现在两个线程执行a++,a=10。如果线程1读入a,cpu切换到了线程2,线程2读入a,结果a=11,本来应该是a=12。
④所以我们要保护值,保持值是新鲜的。于是我们使用了volatile。单线程成功了。线程1执行了a++,数据马上会被写入内存,并要求线程2重新读取a的值。我们成功了,好像不需要锁了。
⑤回到常用的多核cpu,线程1与线程2真正的同时执行了,两个核心同一时刻执行,更新变量来不及了。
⑥所以又想出了另外的一个办法,那就只能去管理过程了,这那些危险操作一个一个执行,所以到了锁。
很推荐这个----Java并发编程:volatile关键字解析
区别
√----保证 ×----不保证
3个属性更好的解释请到这里----Java并发编程:volatile关键字解析
属性 | Synchronized | lock | volatile | 解释 |
---|---|---|---|---|
可见性 | √ | √ | √ | 变量被操作之后,能够快速写入内存,并提醒其他线程重读,加锁是通过一个一个执行保证了可见性。 |
原子性 | √ | √ | × | 做的过程中,不要有相关的来打扰,不相关的我们也不关心,加锁是通过一个一个执行保证了流程不会被相关的打扰。 |
有序性 | √ | √ | √ | 在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。 |
很明显如果我们满足了原子性,我们就可以只使用volatile
使用场景
Volatile
通常来说,使用volatile必须具备以下2个条件:
1)对变量的写操作不依赖于当前值
2)该变量没有包含在具有其他变量的不变式中
实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
事实上,我的理解就是上面的2个条件需要保证操作是原子性操作,才能保证使用volatile关键字的程序在并发时能够正确执行。
(转自Java并发编程:volatile关键字解析)
public class Main {
public static volatile int a;
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i < 10; i++){
int finalI = i;
new Thread(){
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
a = finalI * j;
}
}
}.start();
}
for (int i = 0; i < 100; i++){
System.out.println(a);
}
}
}
Synchronized(线程少的时候效率更高)----2线程 1000000次增加
public class Main{
public volatile int inc = 0;
public synchronized void increase() {
inc++;
}
public static void main(String[] args) throws InterruptedException {
final Main test = new Main();
long a = System.currentTimeMillis();
for(int i=0;i<2;i++){
new Thread(){
public void run() {
for(int j=0;j<1000000;j++) {
test.increase();
}
System.out.println(System.currentTimeMillis() - a + "ms");
}
}.start();
}
Thread.sleep(1000);
System.out.println(test.inc);
}
}
输出:65ms 2000000
public class Main{
public volatile int inc = 0;
public ReentrantLock lock = new ReentrantLock();
public void increase() {
lock.lock();
inc++;
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
final Main test = new Main();
long a = System.currentTimeMillis();
for(int i=0;i<2;i++){
new Thread(){
public void run() {
for(int j=0;j<1000000;j++) {
test.increase();
}
System.out.println(System.currentTimeMillis() - a + "ms");
}
}.start();
}
Thread.sleep(1000);
System.out.println(test.inc);
}
}
输出:100ms 2000000
ReentrantLock(线程多的时候效率更高)----200线程 10000次增加
public class Main{
public volatile int inc = 0;
public ReentrantLock lock = new ReentrantLock();
public void increase() {
lock.lock();
inc++;
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
final Main test = new Main();
long a = System.currentTimeMillis();
for(int i=0;i<200;i++){
new Thread(){
public void run() {
for(int j=0;j<10000;j++) {
test.increase();
}
System.out.println(System.currentTimeMillis() - a + "ms");
}
}.start();
}
Thread.sleep(1000);
System.out.println(test.inc);
}
}
输出:91ms 2000000
public class Main{
public volatile int inc = 0;
public synchronized void increase() {
inc++;
}
public static void main(String[] args) throws InterruptedException {
final Main test = new Main();
long a = System.currentTimeMillis();
for(int i=0;i<200;i++){
new Thread(){
public void run() {
for(int j=0;j<10000;j++) {
test.increase();
}
System.out.println(System.currentTimeMillis() - a + "ms");
}
}.start();
}
Thread.sleep(1000);
System.out.println(test.inc);
}
}
输出:196ms 2000000
比较
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java的关键字,在jvm层面上 | 是一个类 |
锁的释放 | 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 | 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入 不可中断 非公平 | 可重入 可判断 可公平(两者皆可) |
性能 | 少量同步 | 大量同步 |
回到为什么线程少的时候Synchronized快,多的时候慢
引用了:https://blog.csdn.net/ganyao939543405/article/details/52486316
A).一般认为synchronized关键字的实现是源自于像信号量之类的线程同步机制,涉及到线程运行状态的切换,在高并发状态下,CPU消耗过多的时间在线程的调度上,从而造成了性能的极大浪费。
B).lock实现原理则是依赖于硬件,现代处理器都支持CAS指令,所谓CAS指令简单的来说Compare And Set,CPU循环执行指令直到得到所期望的结果,换句话来说就是当变量真实值不等于当前线程调用时的值的时候(说明其他线程已经将这个值改变),就不会赋予变量新的值。这样就保证了变量在多线程环境下的安全性。
当JDK版本高于1.6的时候,synchronized已经被做了CAS的优化:具体是这样的,当执行到synchronized代码块时,先对对象头的锁标志位用lock cmpxchg的方式设置成“锁住“状态,释放锁时,在用lock cmpxchg的方式修改对象头的锁标志位为”释放“状态,写操作都立刻写回主内存。JVM会进一步对synchronized时CAS失败的那些线程进行阻塞操作(调用操作系统的信号量)(此段来摘自别处)。也就是先CAS操作,不行的话继而阻塞线程。
除此之外,系统环境,CPU架构,虚拟机环境都会影响两者的性能关系。
网友评论