做用:可以保证在同一时刻
最多只有一个线程
执行该代码,以达到保证并发安全的效果
public class DisappearRequest implements Runnable{ static DisappearRequest dr = new DisappearRequest(); static int count = 0; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(dr); Thread t2 = new Thread(dr); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("count="+count); } @Override public void run() { for (int i = 0; i < 10000; i++) { count++; } } } // 结果count小于20000(线程不安全)
1. 对象锁:包括同步代码块锁
(本身指定锁对象)和方法锁
(默认锁对象为this当前实例对象)面试
1.1 代码块形式:手动指定锁对象
public class SynchronizedObjectCodeBlock implements Runnable { static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock(); public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } @Override public void run() { // 两个线程串行操做 synchronized(this){ System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } } }
public class SynchronizedObjectCodeBlock implements Runnable { static SynchronizedObjectCodeBlock instance = new SynchronizedObjectCodeBlock(); Object lock1 = new Object(); Object lock2 = new Object(); public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } @Override public void run() { synchronized(lock1){ System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } synchronized(lock2){ System.out.println("我是对象锁的代码块形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } } } // CountDownLatch、信号量解决线程同步问题。
1.2 方法锁形式:synchronized修饰普通方法,锁对象默认为this
普通方法锁
public class SynchronizedMethodLock implements Runnable{ static SynchronizedMethodLock instance = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 普通方法锁(不能是静态方法。锁对象默认是this) public synchronized void sync(){ System.out.println("我是普通方法锁形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }
2. 类锁:指synchronized修饰静态方法或指定锁为Class对象。Java类可能有不少个对象,但只有一个Class对象。所谓的类锁,不过是Class对象的锁而已。
用法和效果:类锁只能在同一时刻被一个对象拥有,其余对象会被阻塞
对象锁若是不一样的实例建立出来的,互相锁是不受影响的,你能够运行我也能够运行,并行运行,可是类锁只有一个能够运行。
安全
2.1. synchronized加在static方法上。
场景:若是须要在全局状况下同步该方法,而不是一个小范围层面,则应该用这种形式去作同步保护。
public class SynchronizedMethodLock implements Runnable{ // 建立两个实例对象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public static synchronized void sync(){ System.out.println("我是类锁的第一种形式:static形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }
2.2. synchronized(*.class)代码块
public class SynchronizedMethodLock implements Runnable{ // 建立两个实例对象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { sync(); } // 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public void sync(){ // 若是是this的话则并行执行,Class对象则串行执行 synchronized(SynchronizedMethodLock.class){ System.out.println("我是类锁的第二种形式:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); } }
场景:前面【1、Synchronized的做用
】中的demo计数场景。
以下四种方式解决(结果均为20000):
3.1 @Override public synchronized void run() { for (int i = 0; i < 10000; i++) { count++; } }
3.2 @Override public void run() { synchronized (this){ for (int i = 0; i < 10000; i++) { count++; } } }
3.3 @Override public void run() { synchronized(DisappearRequest.class){ for (int i = 0; i < 10000; i++) { count++; } } }
3.4 @Override public void run() { add(); } public static synchronized void add(){ for (int i = 0; i < 10000; i++) { count++; } }
4.一、 两个线程同时访问一个对象的同步方法多线程
两个线程争抢的是同一把锁,线程间相互等待,只能有一个线程持有该锁
public static void main(String[] args) { Thread t1 = new Thread(instance); Thread t2 = new Thread(instance); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.二、 两个线程访问的是两个对象的同步方法并发
建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行,这两个实例真正采用的锁对象不是同一个,因此不会被干扰。
// 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public void sync(){ // 若是是this的话则并行执行,指向的是不一样的实例对象,若为Class对象则串行执行 synchronized(this){ // TO DO... } } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.三、 两个线程访问的synchronized的静态方法app
若是两个线程访问的是同一个对象的同步方法则串行执行,若是访问的是不一样对象的同步方法,若该方法是非静态static方法则并行执行,不然两个线程访问的锁对象为同一把锁,串行执行。
public static synchronized void sync(){ // TO DO... } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance2); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.4 同时访问同步方法合肥同步方法jvm
非同步方法不受到影响
4.5 访问同一个对象的不一样的普通同步方法ide
同一个对象,两个同步方法拿到的this是同样的,同一把锁,因此串行执行
4.6 同时访问静态synchronized和非静态synchronized方法函数
两个线程指定的锁对象不是同一把锁,因此锁之间不冲突,并行执行
// 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public static synchronized void sync(){ // 若是是this的话则并行执行,Class对象则串行执行 System.out.println("我是静态方法:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } // 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public synchronized void sync1(){ // 若是是this的话则并行执行,Class对象则串行执行 System.out.println("我是非静态方法:synchronized(*.class)形式。我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance1); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
4.7 方法抛异常后,是否会释放锁性能
抛出异常以后jvm会释放锁,后面的线程会进入同步方法。
// 建立两个实例对象 static SynchronizedMethodLock instance1 = new SynchronizedMethodLock(); static SynchronizedMethodLock instance2 = new SynchronizedMethodLock(); @Override public void run() { if(Thread.currentThread().getName().equals("Thread-0")){ sync(); }else{ sync1(); } } // 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public synchronized void sync(){ // 若是是this的话则并行执行,Class对象则串行执行 System.out.println("我是方法1:我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } throw new RuntimeException(); // System.out.println(Thread.currentThread().getName() + "运行结束"); } // 建立两个实例对象,若是不是static方法的话,则并行操做,不然串行执行。 public synchronized void sync1(){ // 若是是this的话则并行执行,Class对象则串行执行 System.out.println("我是方法2:我叫:" + Thread.currentThread().getName()); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }catch (Exception e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "运行结束"); } public static void main(String[] args) { Thread t1 = new Thread(instance1); Thread t2 = new Thread(instance1); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){ } System.out.println("finished"); }
【5.1 可重入】:指的是同一线程的外层函数得到锁以后,内层函数能够直接再次获取该锁好处:避免死锁,提高封装性
好比:如今有两个均被synchronized修饰的方法f1和f2,此时线程A执行到了f1,而且得到了这把锁,因为方法二f2也是synchronized修饰(也就是说要执行f2必须拿到f2的锁),若是synchronized不具有可重入性,此时线程A只是拿到了f1的锁,没有拿到f2的锁,因此线程A即想拿到f2的锁又不释放f1锁,那么就会形成永远等待,即死锁,因此synchronized是具备可重入性。this
【可重入粒度以下】
5.1.1 证实同一个方法是可重入的(递归)5.1.2 证实可重入不要求是同一个方法
public synchronized void method1(){ System.out.println("我是method1"); method2(); } public synchronized void method2(){ System.out.println("我是method2"); }
5.1.3 证实同可重入不要求是同一个类中的
public class SyncSuperClass{ public synchronized void doSomething(){ System.out.println("我是父类方法"); } } class TestClass extends SyncSuperClass{ public synchronized void doSomething(){ System.out.println("我是子类方法"); super.doSomething(); } }
【5.2 不可中断性】
一旦这个锁已经被别人得到了,若是我还想得到,我只能选择等待或者阻塞,直到别的线程释放这个锁。若是别人永远不释放锁,那么我只能永远等下去。
Lock类
:相比之下,Lock类,能够拥有中断的能力,第一点,若是我以为我等的时间太长了,有权中断如今已经获取到锁的线程的执行;第二点,若是我以为等待时间太长了不想等了,能够退出。
Lock lock = new ReentrantLock(); // 下面这两种形式的锁是等价的 public synchronized void method1(){ System.out.println("我是Synchronized形式的锁"); } public void method2(){ lock.lock(); try{ System.out.println("我是Lock形式的锁"); }finally{ lock.unlock(); } }
【6.1 效率低】
锁的释放状况少,试图得到锁时不能设定超时、不能中断一个正在试图得到锁的线程。
6.1.一、 当一个线程得到了对应的sync锁的时候,其余线程只能等待我释放以后才能获取该锁。
6.1.二、 只有两种状况才释放锁:1.执行完了这段代码,2.发生异常自动释放锁
6.1.三、 不能中断,可是Lock是有中断能力的
【6.2 不够灵活(如:读写锁比较灵活:读的时候不加锁,写才加锁)】
加锁和释放锁的时机单一,每一个锁仅有单一的条件(某个对象),多是不够的
【6.3 没法知道是否成功获取到锁】
Lock lock = new ReentrantLock(); // 非公平锁 // Lock lock = new ReentrantLock(true); // 公平锁 // Lock lock = new ReentrantReadWriteLock(); lock.lock(); lock.unlock(); lock.tryLock(); // 获取锁 lock.tryLock(10, TimeUnit.SECONDS);
8.1 使用synchroinzed注意点:锁对象不能为空、做用域不宜过大、避免死锁
注:一个对象做为锁对象,这个对象必须是被new过的,或者是被其余方法建立好的,而不是一个空对象,由于锁的信息保存在对象头中的。8.2 如何选择Lock和synchronized关键字
尽可能使用concurrent包下的CountDownLatch或者atomic包,或者信号量
9.1 多个线程等待同一个synchronized锁的时候,JVM如何选择下一个获取锁的是哪一个县城? 9.2 synchronized使得同时只能有一个线程能够执行,性能较差,有什么办法能够提高性能? 9.3 我想更灵活的控制锁的获取和释放(如今释放锁的时机都被规定死了),怎么办? 9.4 什么是锁的升级、降级?什么事JVM里的偏斜所、轻量级锁。重量级锁?