问题:线程安全问题的主要诱因是什么?java
---》算法
一、存在共享数据(也称临界资源)缓存
二、存在多条线程共同操做这些共享数据安全
解决问题的根本方法:多线程
同一时刻有且只有一个线程在操做共享数据,其余线程必须等到该线程处理完数据后再对共享数据进行操做(串行)app
互斥锁的特性异步
一、互斥性:在同一时间只容许一个线程持有某个对象锁,经过这种特性来实现多线程的协调机制,这样在同一实际只有一个线程对学院同步的代码块(复合操做)进行访问。互斥性也成为操做的原子性。jvm
二、可见性:必须确保在锁被释放以前,对共享变量所作的修改,对于随后得到该锁的另外一个线程是可见的(则在得到锁时应得到最新共享变量的值),不然另外一个线程多是在本地缓存的某个副本上继续操做,从而引发不一致async
注意的是:synchronized锁的不是代码,锁的都是对象ide
根据获取的锁的分类:获取对象锁和获取类锁
获取对象锁的两种用法:
一、同步代码块(synchronized(this),synchronized(类实例对象)),锁时小括号中的实例对象
二、同步非静态方法(synchronized method), 锁时当前对象的实例对象
获取类锁的两种方法:
一、同步代码块(synchronized(类.class)),锁是小括号()中的类对象(Class对象)
二、同步静态方法(synchronized static method) ,锁是当前对象的类对象(Class对象)
public class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { syncObjectBlock1(); } else if (threadName.startsWith("C")) { syncObjectMethod1(); } else if (threadName.startsWith("D")) { syncClassBlock1(); } else if (threadName.startsWith("E")) { syncClassMethod1(); }else if(threadName.startsWith("F")){ syncObjectMethod2(); } } /** * 异步方法 */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 方法中有 synchronized(this|object) {} 同步代码块 */ private void syncObjectBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized 修饰非静态方法 */ private synchronized void syncObjectMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } private synchronized void syncObjectMethod2() { // 注意这里的syncObjectMethod2方法和syncObjectMethod1方法锁的对象实例都是同一个!!! System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } private void syncClassBlock1() { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } private synchronized static void syncClassMethod1() { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }
public class SyncDemo { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); Thread D_thread1 = new Thread(syncThread, "D_thread1"); Thread D_thread2 = new Thread(syncThread, "D_thread2"); Thread E_thread1 = new Thread(syncThread, "E_thread1"); Thread E_thread2 = new Thread(syncThread, "E_thread2"); Thread F_thread1 = new Thread(syncThread, "F_thread1"); Thread F_thread2 = new Thread(syncThread, "F_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); D_thread1.start(); D_thread2.start(); E_thread1.start(); E_thread2.start(); F_thread1.start(); F_thread2.start(); } }
总结:
一、有线程访问对象的同步代码块时,另外的线程能够访问该对象的非同步代码块
二、若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另外一个访问对象的同步代码块的线程会被阻塞
三、若锁住的是同一个对象,一个线程在访问对象的同步方法时,另外一个访问对象同步方法的线程会被阻塞
四、若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另外一个访问对象同步方法的线程会被阻塞,反之亦然
五、同一个类的不一样对象的对象锁互不干扰
六、类锁因为也是一种特殊的对象锁,所以表现和上述1,2,3,4一致,而因为一个类只有一把对象锁,因此同一个类的不一样对象使用类锁将会是同步的
七、类锁和对象锁互补干扰
JVM优化锁的策略有哪一些?
自旋锁
一、许多状况下,共享数据的锁定状态持续时间较短,切换线程不值得
二、经过让线程执行忙循环等待锁的释放,不让出CPU
三、缺点:若锁被其余线程长时间占用,会带来许多性能上的开销
自适应自旋锁
一、自旋的次数再也不固定
二、由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
(JVM会愈来愈聪明,预测的时间愈来愈精确)
锁消除
更完全的优化
一、JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁
public class StringBufferWithoutSync {
public void add(String str1, String str2) {
//StringBuffer是线程安全,因为sb只会在append方法中使用,不可能被其余线程引用
//所以sb属于不可能共享的资源,JVM会自动消除内部的锁
StringBuffer sb = new StringBuffer();
sb.append(str1).append(str2);
}
public static void main(String[] args) {
StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
for (int i = 0; i < 1000; i++) {
withoutSync.add("aaa", "bbb");
}
}
}
锁粗化
另外一种极端
一、经过扩大加锁的范围,避免反复加锁和解锁
public class CoarseSync { public static String copyString100Times(String target){ int i = 0; StringBuffer sb = new StringBuffer(); while (i<100){ sb.append(target);
// 因为append是同步方法,循环调用这个同步方法,
//会致使锁的不断加锁和释放锁的操做,影响没必要要的资源浪费。
//而这个时候jvm会把锁进行粗化,粗化到整块区域(红色标记) } return sb.toString(); } }
自旋锁,自适应自旋锁,锁消除,锁粗化
synchronized的四种状态
一、无锁、偏向锁、轻量级锁、重量级锁
锁膨胀方向:无锁---》偏向锁---》轻量级锁----》重量级锁
偏向锁:减小同一线程获取锁的代价
一、大多数状况下,锁不存在多线程竞争,老是由同一线程屡次得到
核心思想:若是一个线程得到了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,无需再作任何同步操做,则获取锁的过程只须要检查Mark Word的锁标记位为 偏向锁以及当前线程Id等于Mark Word 的ThreadID便可,这样就省去了大量有关锁申请的操做。
不适合于锁竞争比较激烈的多线程场合
轻量级锁
轻量级锁时由偏向锁升级来的,偏向锁运行在一个线程进入同步块的状况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁
适应的场景:线程交替执行同步块
若存在同一时间访问同一锁的状况,就会到只轻量级锁膨胀为重量级锁
问题:轻量级锁的加锁过程和减锁过程是怎样的?
----》
锁的内存语义
当线程释放锁时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;
而当线程获取锁时,Java内存模型会把该线程对应的本地内存设置为无效,从而使得被监控器包含的临界区代码必须从主内存中读取共享变量
理解上:本地内存A就是指该A线程的栈帧里面的displaced mark word ,因为栈帧是线程私有的,其余线程是没法看到的,所以须要把这个值放到公共的地方,其余线程才能看到,从而作出下一步动做。
CAS(Compare And Swap ) 能够简单地理解为:是一种无锁的算法