网站内容管理,网站建设基本步骤包括哪些,推广型网站开发软件,亚马逊雨林现状Java Synchronized 关键字
壹. Java并发编程存在的问题
1. 可见性问题
可见性问题是指一个线程不能立刻拿到另外一个线程对共享变量的修改的结果。
如#xff1a;
package Note.concurrency;public class Demo07 {private static boolean s true;public static void mai…Java Synchronized 关键字
壹. Java并发编程存在的问题
1. 可见性问题
可见性问题是指一个线程不能立刻拿到另外一个线程对共享变量的修改的结果。
如
package Note.concurrency;public class Demo07 {private static boolean s true;public static void main(String[] args) {new Thread(() - {while(s) {}}).start();try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}new Thread(() - {s false;}).start();System.out.println(s);}
}
运行之后第一个线程一直没有停止说明第二个线程对s的修改没有立刻被线程1拿到
2. 原子性问题
原子性问题是指一条Java语句有可能会被编译成多条语句执行多线程环境下修改同一个变量就会导致结果错误如
package Note.concurrency;import java.util.ArrayList;
import java.util.List;public class Demo08 {private static int num 0;public static void main(String[] args) {Runnable runnable () - {num ;};ListThread list new ArrayListThread();for (int i 0; i 5; i) {Thread thread new Thread(runnable);thread.start();list.add(thread);}for (Thread t :list) {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(num);}
}//~ 4
由于不是原子性操作通过反编译一个自增语句会被翻译成四条语句执行
private static void lambda$main$0();descriptor: ()Vflags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETICCode:stack2, locals0, args_size00: getstatic #16 // Field num:I3: iconst_14: iadd5: putstatic #16 // Field num:I8: returnLineNumberTable:line 10: 0line 11: 8
3. 有序性问题
Java编译时会优化代码这时如果两个无关联的语句Java可能会调整他的顺序导致有序性问题。
贰. 线程状态 叁. Synchronized的用法
1. 修饰成员方法
在定义成员方法时添加关键字Synchronized可以保证同时只有一个线程执行此成员方法线程进入成员方法时需要先获取锁方法执行完毕后会自动释放锁Synchronized修饰成员方法时使用的锁对象是this
2. 修饰静态方法
因为静态方法所有实例共享一份所以相当于给类加锁锁对象默认是当前类的字节码文件所以用Synchronized修饰的成员方法和静态方法是可以并发运行的。
例双重检验锁实现线程安全的单例模式
package Note.concurrency;public class Singleton {private volatile static Singleton singleton;private Singleton(){}public static Singleton getSingleton() {if (singleton null) {synchronized (Singleton.class) {if(singleton null)singleton new Singleton();}}return singleton;}
}
singleton new Singleton();不是原子操作会被翻译成下面四句之类为保证线程安全需要放在synchronized代码块中。
17: new #3 // class Note/concurrency/Singleton
20: dup
21: invokespecial #4 // Method init:()V
24: putstatic #2 // Field 为了避免不可见性问题共享变量使用Volatile关键字修饰
3. 修饰代码块
修饰代码块时要指定锁对象可以是任意的Object对象尽量不要是String xxx , 因为String池有缓存每个线程需要执行Synchronized代码块中的代码时要先获取锁对象否则就会被阻塞 代码块结束锁对象自动释放。
肆. Synchronized的特性
1. 可重入
Synchronized是一个可重入锁意思是在获得锁后可以再次获得该锁而不会陷入死锁
package Note.concurrency;public class ReentrantLockDemo {private int num 0;final Object object new Object();private void method1() {synchronized (object) {num ;}}private void method2() {synchronized (object) {method1();}}public static void main(String[] args) {ReentrantLockDemo reentrantLockDemo new ReentrantLockDemo();new Thread(reentrantLockDemo::method2).start();new Thread(reentrantLockDemo::method2).start();}
}2. 不可中断
如果有AB两个线程竞争锁如果使用SynchronizedA获得锁后如果不释放B将一直等下去不能中断。
package Note.concurrency;public class UninterruptibleDemo {private static final Object o new Object();public static void main(String[] args) throws InterruptedException {// 线程1 拿到锁后阻塞3snew Thread(() - {synchronized (o) {System.out.println(线程1的同步代码块开始执行);try {Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println( 线程1的同步代码块执行结束);}}).start();// 让线程1先执行Thread.sleep(100);Thread t2 new Thread(() - {synchronized (o) {System.out.println(线程2的同步代码块开始执行);}});t2.start();// 主线程休眠3s后锁o 依然被线程1拿着线程2处于BLOCKED状态Thread.sleep(3000);System.out.println(线程2的状态 t2.getState());// 尝试中断线程2如果synchronized允许被中断那线程2此时的状态应该会变为Terminated死亡状态// 反之synchronized如果不可中断线程2的状态会保持BLOCKED阻塞状态t2.interrupt();System.out.println(线程2的状态 t2.getState());}
}//线程1的同步代码块开始执行
//线程2的状态BLOCKED
//线程2的状态BLOCKED
//线程1的同步代码块执行结束
//线程2的同步代码块开始执行
伍. Synchronized的原理
通过javap反汇编一下代码
package Note.concurrency;public class Demo10 {public static synchronized void testMethod() {}public static void main(String[] args) {testMethod();synchronized(Demo10.class) {System.out.println();}}
}
可以看到通过Synchronized修饰的代码块 public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack2, locals3, args_size10: ldc #2 // class Note/concurrency/Demo102: dup3: astore_14: monitorenter5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc #4 // String10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto 2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return
Exception table:from to target type5 15 18 any18 21 18 any
对比普通语句多了monitorenter和monitorexit,monitorenter是同步代码块开始的地方monitorexit是同步代码块结束的地方当线程运行到monitorenter后会试图获取monitor对象这个对象定义在JVM层是一个C的对象每个Java对象都可以和一个monitor关联这个monitor对象保存在Java对象的对象头中这也是为什么任意的object对象都可以作为锁的原因这个monitor对象中有一个recursions属性用来保存被锁的次数第一次运行到monitorenter时检查要获取的锁关联的monitor对象的recursions是不是0如果是0说明该锁没有被任何人获取就可以获取锁把锁的recursions加一并将monitor对象的owner属性设置为当前的Java对象。当执行monitorexit时 会将recursions减一当recursions减为0时标志着当前占有锁的线程释放锁。 第20行还有一个monitorexit最下面的异常表显示如果5到15行发生异常从18行开始执行说明如果同步代码块中发生异常 锁会被自动释放。 synchronized修饰方法的情况 public static synchronized void testMethod();descriptor: ()Vflags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZEDCode:stack0, locals0, args_size00: returnLineNumberTable:line 5: 0
如果使用synchronized修饰方法该方法会被添加上ACC_SYNCHRONIZED标记添加了该标记的方法在执行时会隐式的调用monitorenter和monitorexit
陆. Synchronized和ReentrantLock的区别
Synchronized是一个关键字依赖于JVM而ReentrantLock是一个类依赖于APISynchronized可以修饰方法ReentrantLock只能修饰代码块synchronized可以自动释放锁哪怕被它修饰的方法或代码块发生异常它也可以把锁释放了但ReentrantLock需要手动调用unlock()释放锁与try...finally配合使用避免死锁ReentrantLock有比Synchronized更丰富的功能如 ReentrantLock可以做公平锁也可以做非公平锁但Synchronized就是非公平锁。ReentrantLock可以判断对象有没有拿到锁。ReentrantLock提供了一种能够中断等待锁的线程的机制通过lock.lockInterruptibly()等待锁的线程可以放弃等待锁去干别的事情。Lock可以通过使用读锁提高性能。
柒. Java 6 对Synchronized的优化
1. 偏向锁
大多数情况下锁总是右同一线程多次获得不存在线程竞争所以偏向锁就是适用于这种情况当有线程第一次获取锁时JVM会把对象头中的标志位设置为01即偏向模式并且将线程ID记录下来以后线程每次访问同步代码块只需要判断线程ID是不是记录的偏向线程的ID如果是就直接不进行同步了。但如果一旦发生线程竞争偏向锁就会升级为轻量级锁。由于偏向锁只能在全局安全点所有线程全部停止撤销所以在存在线程竞争的环境下使用偏向锁会得不偿失。 在JDK5中偏向锁默认是关闭的而到了JDK6中偏向锁已经默认开启。但在应用程序启动几秒钟之后才 激活可以使用 -XX:BiasedLockingStartupDelay0 参数关闭延迟如果确定应用程序中所有锁通常 情况下处于竞争状态可以通过 XX:-UseBiasedLockingfalse 参数关闭偏向锁。 2. 轻量级锁
偏向锁失效后会升级为轻量级锁轻量级锁适用于线程交替执行同步代码块的情况下它使用CAS操作代替重量级锁使用操作系统互斥变量的操作因此避免了程序频繁在系统态和用户态之间切换的开销但之所以使用轻量级锁是基于“对于绝大部分锁在整个同步周期内都是不存在竞争的”的经验数据如果有多个线程同时进入临界区那CAS操作的效率可能反而不如重量级锁如果存在线程竞争轻量级锁就会膨胀为重量级锁。
3. 自旋锁
一般情况下同步代码块中的代码执行时间都比较短所以一时间获取不到锁可能再重试一次就可以了而不用升级为重量级锁自旋锁就是基于这个原理它允许获取不到锁的线程重复几次尝试获取锁默认是10次JDK 1.6 开始加入自适应自旋锁会根据之前自旋的情况动态确定自旋的次数。
4. 重量级锁
经过自旋后还是获取不到锁那就会升级为重量级锁也就是monitor
5. 锁消除 锁消除理解起来很简单它指的就是虚拟机即使编译器在运行时如果检测到那些共享数据不可能存在竞争那么就执行锁消除。锁消除可以节省毫无意义的请求锁的时间。 5. 锁粗化 JVM会探测到一连串细小的操作都使用同一个对象加锁将同步代码块的范围放大放 到这串操作的外面这样只需要加一次锁即可。 捌. 使用Synchronized时的优化
减少Synchronized的范围让同步代码块中的代码执行时间尽可能短降低锁粒度将一个大锁改为多个不同锁对象的小锁如HashTable和ConcurrentHashMap读写分离读不加锁写加锁。
拾. 参考
https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md
https://www.bilibili.com/video/BV1aJ411V763