前言
搞清楚synchronized同步锁.了解三种方式的区别,
- 非静态方法的同步; - 非静态代码块的同步 - 静态方法的同步 - 静态代码块的同步
public synchronized void setIds(){}
很明显就是直接将synchronized加到一个非静态的方法上面,那么它默认就是的锁就是this,即当前对象,当持有同一Runnable对象的多个线程同时访问此方法时,先抢到锁的那个线程会首先执行,而其他线程则处于等待状态,当方法体内代码执行完成后释放锁,让给其他等待的线程继续竞争,–ok,这里要注意,此时是同一对象的多个线程,这个要搞清楚.synchronized关键字直接加到方法上,锁对象默认是this对象,所以如果你在多个线程中使用一个对象引用来调用此方法,就会进行排队.
void test(){ Object o = new Object(); synchronized(o){ //或者 synchronized(this) //代码块... } }
代码块同步也就是写一个synchronized(Object o), 参数可以是当前对象this,也可以是object对象,将需要同步的代码用其包裹,看起来似乎比第一个要多加东西,还是愿意用第一个是吗,那就错了,来看如下代码:
public static void main(String[] args) { MRunnable mRunnable = new MRunnable(); new Thread(mRunnable).start(); new Thread(mRunnable).start(); new Thread(mRunnable).start(); new Thread(mRunnable).start(); } int s = 1; synchronized void test(){ System.out.println("展示"+s); for(int i = 0;i<100000;i++){ //执行延时操作 } System.out.println("后续工作"); }
我们可以想象一个场景, 有100个持有相同Runnable对象的线程同时访问test(),而你只需要同步s的数值,后续代码完全不需要同步,那么此时如果使用方法级别的同步,那么第一个线程抢到锁以后要将所有的代码全部执行完毕才会解开锁,与此同时其他99个线程需要一直等待,以此类推,严重影响了程序性能,这时静态代码块就发挥了它的优点了:
void test(){ Object o = new Object(); synchronized(o){ System.out.println("展示"+s); } for(int i = 0;i<100000;i++){ //执行延时操作 } System.out.println("后续工作"); }
可以看到,只需要在执行system.out.p…时候其他线程是无法访问的(上锁了),当执行完这行代码以后此线程会将锁释放掉,且继续执行耗时操作,而其他持有相同对象的线程就可以抢锁来进入方法进行操作了.所以在仅需要某一小处需要同步时,同步代码块是不二之选.
上文多次强调不同线程持有同一Runnable对象来获取锁资源,可能读者就会发问了,那么不同线程持有不同Runnable对象来获取锁资源呢? 来看如下代码
public static void main(String[] args) { MRunnable mRunnable = new MRunnable(); MRunnable mRunnable1 = new MRunnable(); MRunnable mRunnable2 = new MRunnable(); MRunnable mRunnable3 = new MRunnable(); new Thread(mRunnable).start(); new Thread(mRunnable1).start(); new Thread(mRunnable2).start(); new Thread(mRunnable3).start(); } class MRunnable implements Runnable { @Override public void run() { test(); } public synchronized void test(){ System.out.println("让所有实例化我的线程都来访问我把"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }
你能猜到结果吗?你觉得他是3秒打印一次呢?还是一下都打印呢?自己亲手来试试吧,结果是一次性全部打印,为什么呢?这就是我前面一直要强调不同线程持有相同Runnable对象的原因,你可以仔细想想,synchronized如果加到方法上,那么他的锁对象是this,也就是当前对象,那么不论是在哪个线程,传递的参数都是同一个runnable对象,所以即使有多个线程最终都会阻塞,举个例子就是不同的人上所在的都是同一个地方,所以抢的是同一个厕所.
那么持有不同runnable对象的线程访问为什么不会阻塞呢,因为创建的不同的Runnable对象,对象不同进入方法后的this也就不同,因为创建两个对象时候,栈内存里是两个不同的引用地址,不在访问同一堆内存的对象了,两个人去了两个不同的厕所,他们抢什么,都能进去
synchronized加在static方法上
public static synchronized void test(){}
前面已经讲了不同线程持有相同的Runnable对象和不同的Runnable对象的区别.到了类锁这里,就不在有什么相同不相同了,因为用static关键字修饰以后,方法就到了静态区存放了,不再放到堆内存中,这样的话,该方法就属于类的方法,不再属于对象,对象可以有多个,但类只有一个,从程序运行开始到结束,只有一个,所以即便是不同的线程不同的对象,调用到test(),都是在抢这一个锁
举个例子的就是全世界只有一个厕所,从人类诞生开始,这个厕所就已经存在了,一直到人类灭亡,都仅仅只有这一个厕所,有点可怕哈,不过目的是为了让读者了解,那么不管你是哪个地方的人,都没用只有一个,抢吧
synchronized加在代码块中
public void test(){ synchronized (Runnable.class){ System.out.println("让所有实例化我的线程都来访问我把"); } }
很好理解,直接将代码块中的参数设置为runnable.class 全局只有一个
总结 :到此synchronized的同步方式就介绍完了,线程这一块还是比较绕的,这里仅仅简单介绍了对象锁和类锁,然而在使用过程中还是会有很多的问题,比如不同锁之间对资源读写的情况等等,都是比较复杂的,不过多踩踩坑对自己也是一种历练