synchronized(JVM实现的锁)#
通过这两个关键字,我们可以很容易的实现同步多个任务的行为,可以实现同一时刻,只能有一条线程去访问共享资源
一: 修饰普通方法#
多个线程,共同去竞争访问,方法内部的变量,永远是线程安全的!!!#
public class HasSelfPrivateNum{ public void add(String name){ try{ int num=0; //num在方法内部,永远是线程安全的!!! if(name.equals("a"){ num =100; }else{ num =200; } System.out.println("num=="+num); }catch(InterruptedException e){ e.printStackTrace(); } } }
多个线程,共同去竞争访问对象中的实例变量,可能导致非线程安全#
public class HasSelfPrivateNum{ private int num=0; //num是本类的实例变量,可能出现非线程安全问题!!! public void add(String name){ try{ if(name.equals("a"){ num =100; }else{ num =200; } System.out.println("num=="+num); }catch(InterruptedException e){ e.printStackTrace(); } } }
结论:#
只有共享的资源才可能出现非线程安全性问题,需要同步化...就像方法内部的变量,根本不可能共享,所以说没必要同步化
解决非线程安全问解决#
这一问题引入了Synchronized关键字,让出现非线程安全问题的方法,保持前后的同步,让线程拿到对象锁(this对象锁),当某一个线程进去后,其他线程只能等它释放对象锁之后,获取到对象锁在进入同步方法(Synchronized可以保证它修饰的方法实现原子性的操作)
拓展:A线程持有object对象的锁,B线程可以异步的调用object对象中的非Synchronized类型的方法
public class HasSelfPrivateNum{ private int num=0; //num是本类的实例变量,可能出现非线程安全问题!!! Synchronized public void add(String name){ try{ if(name.equals("a"){ num =100; }else{ num =200; } System.out.println("num=="+num); }catch(InterruptedException e){ e.printStackTrace(); } } }
多个对象多个锁#
比如说,分别给两个线程分别去访问同一个类的两个不同不对象的同名同步方法,结果不是顺序执行,而是异步执行的...
结论:#
关键字Synchronized获取到的锁全部是对象锁,而不是把一段代码或者一个方法当作锁!真出现了多个线程访问多个对象,那么JVM就会创建出,多把对象锁...
内置锁#
java中每一个对象都可被当作同步的锁,这些锁就叫做内置锁
互斥锁#
一个线程进来之后,另一个线程不能进来, Synchronized就是典型的互斥锁
二: 修饰代码块#
Synchronized的弊端:
声明方法在某些情况下是有弊端的,比如说A线程抢到了cpu的执行权,在调用同步方法执行某一个比较长的任务的,A还没来得及释放这个对象锁,紧接着B线程抢到了cpu的执行权,他也想去访问A线程访问的方法,然而B线程拿不到这个对象锁,所以被拒绝访问,因此B线程必须等待较长的时间
同步代码块语法:
synchronized(Object){ //同步执行的任务 }
sychronized代码块里面的方法是同步的,另一个线程只有等待当前线程执行完这个代码块之后,才能执行此代码块
使用同步代码块解决同步方法的弊端,提升效率 一半同步一半异步!-->
public task{ //类的实例属性 --> 共享资源 private int a; private int b; try{ .... // 非共享资源 .... // 非共享资源 synchronized(this){ a++; b++; } }catch(InterruptedExceotion e){ e.printStackTrace(); } }
可以看到,我们只是把可能出现非线程的共享资源放在同步代码块中...
当一个线程访问object的一个Synchronized(this){}同步代码块里面的内容时,另一个线程可以访问此object对象中的非synchronized代码块!!!
也就是一半同步,一半异步,不在同步代码块中的代码,就是异步执行,在同步代码块中的代码,就是同步执行!
Synchronized代码块的本身具有同步性,而且,Synchronized代码块之间也具有同步性!#
当一个线程访问object对象中的Synchronized代码块的时候,其他的任何线程对object的其他任何Synchronized同步代码块的访问也是阻塞的,这也说明Synchronized同步代码块和Synchronized修饰的方法一样,使用的对象监视器是一个
将任意对象,作为对象监视器:#
Synchronized(非this对象){} 同步代码块可以是任意对象,这个非this对象大多数是,方法的参数,类的实例变量
Synchronized(非this对象){}优点#
如果一个类中有很多的Synchronized同步方法,虽然可以实现同步,但是,会发生阻塞而降低效率,因为所有的Synchronized同步方法,他们拥有的都是this锁,而Synchronized(非this对象){}同步代码块可以使用的是非this锁,拥有不同的锁,因此他们两者之间是异步执行的,对他们自己来说又是同步执行,提升了效率
注意点:#
同步代码块放在非同步Synchronized方法中进行声明,不能保证调用方法的线程执行同步,因为线程调用方法的顺序是无序的,虽然在同步代码块中执行的顺序依然有序,但如果有分支逻辑判断(逻辑判断没有在同步代码块中),就可能会出现脏读
//创建一个只能存储一个元素的集合... List list = new ArrayList(); public void unsafe(){ try{ if(list.size()<0){ Thread.sleep(2000); synchronized(){ list.add("a"); } }catch(InterruptedException e){ e.printStackTrace(); } } //此段代码就会出现脏读... //解决方法: 同步化 List list = new ArrayList(); synchronized public void unsafe(){ try{ if(list.size()<0){ Thread.sleep(2000); synchronized(){ list.add("a"); } }catch(InterruptedException e){ e.printStackTrace(); } }
Synchronized(非this对象){}总结:#
- 在多个线程持有"对象监视器"为同一个对象的前提下,同一时间,只有一个线程可以执行synchronized(非this对象){}同步代码块中的代码.
- 同一时间,线程想去执行synchronized(非this对象){}同步代码块里面的代码,它必须拿到这个非this对象
- 其他线程调用Synchronized(非this##### 象X){}中的X的同步代码块,同步方法,依然是线程安全的.
- 在多线程访问的情况下,拥有不同对象监视器的Synchronized(){}同步代码块之间是异步执行的
三: 修饰静态XX#
Synchronized public static void ...... 静态同步锁#
Synchronized方法同样可以修饰静态方法,运行的结果证明它同样可以实现线程安全,顺序执行,但是Synchronized修饰普通方法 和 Synchronized修饰静态方法是有本质上的区别的,Synchronized修饰静态方法实际上是给Class类上锁 而前者是给this对象上锁.
换言之,两个自身顺序执行,同时出现则异步执行,(锁不同)
四 修饰同步代码块#
Synchronized(类名.class){...}
作用和其修饰静态方法一样
五 . synchronized锁重入#
synchronized关键字具有锁重入的功能,也就是说,当一个线程拿到锁对象之后,当它在本synchronized方法中访问本类对象的其他synchronized方法时,它是可以重复拿到锁的!
六. 出现异常锁自动释放#
在synchronized修饰的同步方法中,若在运行时,出现了异常,对象锁会自动释放,意味着其他线程可以直接在此获取到对象锁
七. 数据类型String的常量池特性#
- JVM中具有String常量池缓存的功能,如下一段代码返回true
public static void mainString[] avgs(){ String a ="a"; String b = "a"; System.out.println(a==b); }
这也就是意味着,假如在两个线程给一个Synchronized同步代码块中传递进同样的字符串,也就意味着,他们要去竞争同一把锁,难免会出现阻塞的情况.所以绝大多数情况下,我们是不使用String字符串,来当作锁对象的
八. 同步Synchronized方法的无限循环的等待和解决#
同步方法synchronized同步方法,容易造成死循环,如下代码,method永远不可能得到执行
public class Service{ synchronized public void methodA(){ System.out.println("methodA Begin"); boolean tag = true; while(tag){} System.out.println("methodA end"); } synchronized public void methodB(){ System.out.println("methodB Begin"); System.out.println("methodB end"); } }
使用同步代码块解决无限循环问题
public class Service{ public void methodA(){ Object o1 = new Object(); synchronized(o1){ System.out.println("methodA Begin"); boolean tag = true; while(tag){} System.out.println("methodA end"); } } synchronized public void methodB(){ Object o2 = new Object(); synchronized(o2){ System.out.println("methodB Begin"); System.out.println("methodB end"); } } }
九 多线程的死锁#
- 多线程的死锁就是说,已经准备就绪的线程们在等待一个根本不可能被释放的锁,从而导致所有的线程任务都无法继续完成,导致了线程的假死,死锁是必须要避免的问题
如下代码死锁现象
public class demoSiSuo implements Runnable{ public class demoSiSuo implements Runnable{ private String username; private Object lock1 = new Object(); private Object lock2 = new Object(); public void set(String username) { this.username = username; } @Override public void run() { if (username.equals("a")){ synchronized (lock1){ try { System.out.println("a"); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2){ //do something System.out.println("接着lock1 lock2的代码执行了"); } } } if (username.equals("b")){ synchronized (lock2){ System.out.println("b"); //do something synchronized (lock1){ //do something System.out.println("接着lock2 lock1的代码执行了"); } } } } public static void main(String[] args) { demoSiSuo demoSiSuo = new demoSiSuo(); new Thread(()->{ demoSiSuo.set("a"); demoSiSuo.run(); }).start(); new Thread(()->{ demoSiSuo.set("b"); demoSiSuo.run(); }).start(); } }
使用JDK自带的检查死锁的工具
在cmd窗口切换到bin目录,输入指令jps 找到正在运行的实例id, 输入 jstack -l [id] 可查看到
Found 1 deadlock.
示例如下:
C:\Users\acer>jps 335844 ThreadPool 370676 DeadLock 366976 ThreadPool 163348 Jps 297244 256296 RemoteMavenServer 397720 Launcher C:\Users\acer>jstack 370676 ... =================================================== "Thread-1": at com.changwu.MultiThread.lock.HoldLock.run(DeadLock.java:22) - waiting to lock <0x000000076b6f7f58> (a java.lang.String) - locked <0x000000076b6f7f90> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) "Thread-0": at com.changwu.MultiThread.lock.HoldLock.run(DeadLock.java:22) - waiting to lock <0x000000076b6f7f90> (a java.lang.String) - locked <0x000000076b6f7f58> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.