synchronize关键字

java线程相关的3个关键字synchronize、ThreadLocal、volatile也许在我们平常保持数据一致性场景中用到不多,因为它们都是基于JVM的特征,而我们的应用一般都是集群部署,分布式环境下线程的一致性几乎用不到它们,但在一些底层的源码中,还会看到他们的身影,比如ConcurrentHashMap、StringBuffer中,synchronize为线程安全保驾护航,那么接下来看下它们都有哪些特性

synchronize

用synchronize关键字来修饰一个方法、一个代码块的时候,能保证同一个时刻只能有一个线程可以执行,具体的特性:

1. 当2个并发线程访问同1个对象object中的这个synchronized方法时,一个时间内只能有1个线程得到执行。另一个线程必须等待当前线程执行完这个方法以后才能执行该方法 (潜台词是如果2个线程并发访问2个对象的synchronize方法时,是异步执行的,可见注释掉的代码)。

看下代码:

package com.syj.test.thread;

/**
* Created by syj on 2019/1/7.
*
* @DESC 2个并发线程访问1个对象的synchronized方法时,一个时间内只能有一个线程得到执行
*/
public class SynchronizedDemo1 {

   public synchronized void synMeth() {
       for (int i = 0; i < 500; i++) {
           System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
       }
   }

   public static void main(String[] args) {
       //2个线程同时访问1个对象的synchronize方法,一个时间只能有一个线程得到执行
       final SynchronizedDemo1 t1 = new SynchronizedDemo1();

       Thread ta = new Thread(new Runnable() {
           public void run() {
               t1.synMeth();
           }
       }, "A");
       Thread tb = new Thread(new Runnable() {
           public void run() {
               t1.synMeth();
           }
       }, "B");

       ta.start();
       tb.start();

       //2个线程同时访问2个对象的synchronize方法,互不影响(即一个时间可能有2个线程在执行)
       /*final SynchronizedDemo1 t2 = new SynchronizedDemo1();
       final SynchronizedDemo1 t3 = new SynchronizedDemo1();
       Thread tc = new Thread(new Runnable() {
           public void run() {
               t2.synMeth();
           }
       }, "C");
       Thread td = new Thread(new Runnable() {
           public void run() {
               t3.synMeth();
           }
       }, "D");
       tc.start();
       td.start();*/
   }
}

执行结果如下,线程A、B是串行执行的,线程C、D是交叉执行的,因为C、D对应的是2个对象嘛,锁的不是同一个对象,当然互不影响了(每台机器CPU的执行结果的顺序会不同,为了得到真实的结果,建议多运行几次来观察)
线程A 、B执行结果
线程C D交叉执行

2. 当1个线程访问object的一个synchronized方法时,另1个线程仍然可以访问该object中的非synchronized方法。

看下代码:

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 * @DESC 1个线程访问某个object的synchronized方法、synchronize代码块时,其他并发线程仍然可以访问该object的非synchronize方法
 */
public class SynchronizedDemo2 {

    public synchronized void synMethod() {
            for (int i=0; i<500; i++) {
                System.out.println(Thread.currentThread().getName() + " syn loop " + i);
            }
    }

    public void noSynMethod() {
        for (int i=0; i<500; i++) {
            System.out.println(Thread.currentThread().getName() + " nosyn loop " + i);
        }
    }

    public static void main(String[] args) {
        final SynchronizedDemo2 obj = new SynchronizedDemo2();

        //1个线程访问1个object的synchronize方法,其他并发线程可以访问该object的非synchronize方法
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                obj.synMethod();
            }
        }, "t1");

        Thread t2 = new Thread(new Runnable() {
            public void run() {
                obj.noSynMethod();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

执行结果如下:
在这里插入图片描述
synchronize方法和非synchronize方法交叉执行,互不影响。但这在某些场景下,会发生脏读,就是使用了synchronize方法,但得到的并不是我想要的结果,看下面这个脏读的case:

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 * @DESC 多线程访问中的脏读
 */
public class SynchronizedDemo6 {

    private int num = 10;

    public synchronized void synMethod() {
        try {
            //模拟业务处理
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num += 1;
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod, num=" + num);
    }

    public void noSynMethod() {
        System.out.println("thread name : " + Thread.currentThread().getName() + ", noSynMethod, num=" + num);
    }

    public static void main(String[] args) {
        final SynchronizedDemo6 obj = new SynchronizedDemo6();

        new Thread(new Runnable() {
            public void run() {
                obj.synMethod();
            }
        }, "t1").start();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //发生脏读
        new Thread(new Runnable() {
            public void run() {
                obj.noSynMethod();
            }
        }, "t2").start();
    }
}

执行结果如下:
脏读
线程t1执行了synMethod后,num应该变为11,但是线程t2通过noSynMethod去打印,发现还是之前的值,那该怎么处理呢?那就接下来看看下一个case吧

3. 当1个线程访问object的一个synchronized方法时,其他线程对object中所有其它synchronized方法的访问将被阻塞。

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 *
 * @DESC 1个线程访问某个object的synchronized方法时,其他并发线程对该object的所有其他
 * synchronize方法的访问都将被阻塞
 */
public class SynchronizedDemo3 {

    private int num = 10;

    public synchronized void synMethod1() {
        try {
            //模拟业务处理
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num += 1;
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1, num=" + num);
    }

    public synchronized void synMethod2() {
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2, num=" + num);
    }

    public static void main(String[] args) throws Exception{
        final SynchronizedDemo3 obj = new SynchronizedDemo3();

        new Thread(new Runnable() {
            public void run() {
                obj.synMethod1();
            }
        }, "t1").start();

        Thread.sleep(1000);

        new Thread(new Runnable() {
            public void run() {
                obj.synMethod2();
            }
        }, "t2").start();
    }
}

看下结果,发现脏读消失了,两个线程是同步执行的,因为线程对方法的调用是串行执行的,随之而来的就是性能的消耗,那能不能有更细粒度的锁,比如对某个变量加锁,而不是对整个对象加锁?当然是有的,看下个例子
在这里插入图片描述

4. 不同的锁之间是异步的,比如变量锁和对象锁,互不影响,但每一种锁都有以上1、2、3种特性。 (锁就是一种对象监视器,不同的对象监视器之间是异步的)

看下代码:

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 *
 * @DESC 1个线程访问1个object的synchronized变量时,另1个线程可以访问该object的synchronize方法
 * 这是因为1个是变量锁,一个是对象锁,它们之间不冲突
 */
public class SynchronizedDemo4 {
    private String lock = new String();
	
	//object对象锁
    public synchronized void synMeth() {
        for (int i = 0; i < 500; i++) {
            System.out.println(Thread.currentThread().getName() + " synMeth loop " + i);
        }
    }

    public void noSynMeth() {
		//变量锁
        synchronized (lock) {
            for (int i = 0; i < 500; i++) {
                System.out.println(Thread.currentThread().getName() + " synPara loop " + i);
            }
        }
    }

    public static void main(String[] args) {
        //1个线程访问synchronize的变量锁时,另一个并发线程可以访问synchronize方法
        final SynchronizedDemo4 t = new SynchronizedDemo4();

        Thread ta = new Thread(new Runnable() {
            public void run() {
                t.synMeth();
            }
        }, "A");

        Thread tb = new Thread(new Runnable() {
            public void run() {
                t.noSynMeth();
            }
        }, "B");

        ta.start();
        tb.start();
    }
}

执行结果:
在这里插入图片描述
synchronize(lock)是一种变量锁,非对象锁,那这种方式有什么好处呢?

如果在一个类中有很多synchronized方法,这时虽然能实现同步,但会受到阻塞,从而影响效率。但如果同步代码块锁的是非this对象,则synchronized(非this对象x)代码块中的程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,大大提高了运行效率。

说明:synchronize修饰方法跟synchronize(this){}是一样的,它们都是针对object对象加锁。当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

看下代码:

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 *
 * @DESC synchronize修饰方法时,是this对象锁,跟synchronize(this)一样,都是this对象锁
 */
public class SynchronizedDemo5 {
	//object对象锁
    public synchronized void synMeth1() {
        for (int i = 0; i < 500; i++) {
            System.out.println(Thread.currentThread().getName() + " synMeth1 loop " + i);
        }
    }

    public void synMeth2() {
        //object对象锁
        synchronized (this) {
            for (int i = 0; i < 500; i++) {
                System.out.println(Thread.currentThread().getName() + " synMeth2 loop " + i);
            }
        }
    }

    public static void main(String[] args) {
        final SynchronizedDemo5 t = new SynchronizedDemo5();

        Thread ta = new Thread(new Runnable() {
            public void run() {
                t.synMeth1();
            }
        }, "A");

        Thread tb = new Thread(new Runnable() {
            public void run() {
                t.synMeth2();
            }
        }, "B");

        ta.start();
        tb.start();
    }
}

执行结果如下:
在这里插入图片描述

5. 锁重入:当一个线程得到一个对象锁之后,再次请求此对象锁时是可以再次得到该对象的锁的。这也说明在一个synchronize方法/块的内部调用本类的其他synchronize方法/块时,是永远可以得到锁的

package com.syj.test.thread;

/**
 * Created by syj on 2019/1/7.
 * @DESC 锁重入
 */
public class SynchronizedDemo7 {

    public synchronized void synMethod1() {
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1 bengin");
        synMethod2();
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod1 end");
    }

    public synchronized void synMethod2() {
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2 bengin");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread name : " + Thread.currentThread().getName() + ", synMethod2 end");    }

    public static void main(String[] args) {
        final SynchronizedDemo7 obj = new SynchronizedDemo7();

        //锁重入
        new Thread(new Runnable() {
            public void run() {
                obj.synMethod1();
            }
        }, "t1").start();


        new Thread(new Runnable() {
            public void run() {
                obj.synMethod1();
            }
        }, "t2").start();
    }
}

执行结果如下,线程t1拿到对象锁后,可以直接调用其他synchronize方法,其他线程等待t1释放锁后,才可以执行:
锁重入

volatile

待完善

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值