java多线程4:synchronized关键字

概述

  java有各类各样的锁,而且每种锁的特性不一样,合理场景下利用锁能够展示出很是高的效率。synchronized内置锁就是Java的一种重量级锁,它可以解决并发编程中出现多个线程同时访问一个共享,可变的临界资源时出现的线程安全问题。让多个线程序列化访问临界资源,同一时刻,只能有一个线程访问临界资源,同步互斥,这样就保证了操做的原子性。html

synchronized使用

同步方法块java

public class ThreadDemo5 implements Runnable{

    private int count = 0;

    @Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 10; ++i){
                count++;
                System.out.println("执行的线程是=>" + Thread.currentThread().getName() + "执行结果为->" + count);
            }
        }

    }

    public static void main(String[] args) {
        ThreadDemo5 threadDemo5 = new ThreadDemo5();
        Thread thread1 = new Thread(threadDemo5,"thread1");
        Thread thread2 = new Thread(threadDemo5,"thread2");
        thread1.start();
        thread2.start();
    }
}

执行结果编程

执行的线程是=>thread1执行结果为->1
执行的线程是=>thread1执行结果为->2
执行的线程是=>thread1执行结果为->3
执行的线程是=>thread1执行结果为->4
执行的线程是=>thread1执行结果为->5
执行的线程是=>thread1执行结果为->6
执行的线程是=>thread1执行结果为->7
执行的线程是=>thread1执行结果为->8
执行的线程是=>thread1执行结果为->9
执行的线程是=>thread1执行结果为->10
执行的线程是=>thread2执行结果为->11
执行的线程是=>thread2执行结果为->12
执行的线程是=>thread2执行结果为->13
执行的线程是=>thread2执行结果为->14
执行的线程是=>thread2执行结果为->15
执行的线程是=>thread2执行结果为->16
执行的线程是=>thread2执行结果为->17
执行的线程是=>thread2执行结果为->18
执行的线程是=>thread2执行结果为->19
执行的线程是=>thread2执行结果为->20

 同步方法块,synchronized锁的是括号里的对象,每一个线程要进入代码块前必须先获取对象的的锁,才可执行。synchronized是一个隐式锁,也是jvm内置的锁,它会自动加锁和解锁,同时java的每一个对象均可以做为锁。数组

普通同步方法安全

public class ThreadDemo6 implements Runnable {

    private int count = 0;

    @Override
    public void run() {
        say();
    }

    private synchronized void say(){
        for (int i = 0; i < 10; ++i){
            count++;
            System.out.println("如今执行的线程执行=>" + Thread.currentThread().getName() + "结果为->" + count);
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        ThreadDemo6 threadDemo6 = new ThreadDemo6();
        Thread thread1 = new Thread(threadDemo6,"Thread-1");
        Thread thread2 = new Thread(threadDemo6,"Thread-2");
        thread1.start();
        thread2.start();
    }
}

执行结果bash

如今执行的线程执行=>Thread-1结果为->1
如今执行的线程执行=>Thread-1结果为->2
如今执行的线程执行=>Thread-1结果为->3
如今执行的线程执行=>Thread-1结果为->4
如今执行的线程执行=>Thread-1结果为->5
如今执行的线程执行=>Thread-1结果为->6
如今执行的线程执行=>Thread-1结果为->7
如今执行的线程执行=>Thread-1结果为->8
如今执行的线程执行=>Thread-1结果为->9
如今执行的线程执行=>Thread-1结果为->10
/*停顿5秒*/ 如今执行的线程执行=>Thread-2结果为->11 如今执行的线程执行=>Thread-2结果为->12 如今执行的线程执行=>Thread-2结果为->13 如今执行的线程执行=>Thread-2结果为->14 如今执行的线程执行=>Thread-2结果为->15 如今执行的线程执行=>Thread-2结果为->16 如今执行的线程执行=>Thread-2结果为->17 如今执行的线程执行=>Thread-2结果为->18 如今执行的线程执行=>Thread-2结果为->19 如今执行的线程执行=>Thread-2结果为->20

普通同步方法,经过例子能够知道他是一个对象锁,线程1未释放锁,线程2只能被动等待,改下代码多线程

 public static void main(String[] args) {
        Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1");
        Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2");
        thread1.start();
        thread2.start();
    }

执行结果并发

如今执行的线程执行=>Thread-2结果为->1
如今执行的线程执行=>Thread-2结果为->2
如今执行的线程执行=>Thread-2结果为->3
如今执行的线程执行=>Thread-2结果为->4
如今执行的线程执行=>Thread-2结果为->5
如今执行的线程执行=>Thread-2结果为->6
如今执行的线程执行=>Thread-2结果为->7
如今执行的线程执行=>Thread-2结果为->8
如今执行的线程执行=>Thread-2结果为->9
如今执行的线程执行=>Thread-2结果为->10
如今执行的线程执行=>Thread-1结果为->1
如今执行的线程执行=>Thread-1结果为->2
如今执行的线程执行=>Thread-1结果为->3
如今执行的线程执行=>Thread-1结果为->4
如今执行的线程执行=>Thread-1结果为->5
如今执行的线程执行=>Thread-1结果为->6
如今执行的线程执行=>Thread-1结果为->7
如今执行的线程执行=>Thread-1结果为->8
如今执行的线程执行=>Thread-1结果为->9
如今执行的线程执行=>Thread-1结果为->10
停顿。。

不是同一个对象锁,因此线程1和线程2不存在锁的互斥,而且不存在共享资源count变量,因此多个线程访问的必须是同一个对象,锁才会变得有意义。jvm

静态同步方法ide

public class ThreadDemo6 implements Runnable {

    private static int count = 0;

    @Override
    public void run() {
        say();
    }

    private static synchronized void say(){
        for (int i = 0; i < 10; ++i){
            count++;
            System.out.println("如今执行的线程执行=>" + Thread.currentThread().getName() + "结果为->" + count);
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


    public static void main(String[] args) {
        Thread thread1 = new Thread(new ThreadDemo6(),"Thread-1");
        Thread thread2 = new Thread(new ThreadDemo6(),"Thread-2");
        thread1.start();
        thread2.start();
    }
}

执行结果

如今执行的线程执行=>Thread-1结果为->1
如今执行的线程执行=>Thread-1结果为->2
如今执行的线程执行=>Thread-1结果为->3
如今执行的线程执行=>Thread-1结果为->4
如今执行的线程执行=>Thread-1结果为->5
如今执行的线程执行=>Thread-1结果为->6
如今执行的线程执行=>Thread-1结果为->7
如今执行的线程执行=>Thread-1结果为->8
如今执行的线程执行=>Thread-1结果为->9
如今执行的线程执行=>Thread-1结果为->10
/*停顿*/
如今执行的线程执行=>Thread-2结果为->11
如今执行的线程执行=>Thread-2结果为->12
如今执行的线程执行=>Thread-2结果为->13
如今执行的线程执行=>Thread-2结果为->14
如今执行的线程执行=>Thread-2结果为->15
如今执行的线程执行=>Thread-2结果为->16
如今执行的线程执行=>Thread-2结果为->17
如今执行的线程执行=>Thread-2结果为->18
如今执行的线程执行=>Thread-2结果为->19
如今执行的线程执行=>Thread-2结果为->20

即便他们是不一样的对象,但执行的都是一个类的方法,在执行同步静态方法时,争抢的是类锁,这也是和非静态同步方法所区别开来。由于他们是两个不一样的锁,一个是对象锁,一个是类锁。因此,在代码中,一个线程能够同时抢有对象锁,类锁。

 monitor和monitorexit

Java的互斥锁是如何的实现的,javap -verbose ThreadDemo3.class 看下字节码子令。

同步代码块

   

 非静态方法同步

 

 

 静态方法同步

 

 同步块中monitor被占用就处于锁定状态,其余本次抢锁失败的线程将会放入Wait Set等待同步队列中进行等待,占用锁的线程执行完同步块而且释放锁后将会通知放入同步队列的的其余线程,通知他们,我释放锁了赶忙来抢吧!而相对于普通的静态同步方法和非静态同步方法,常量池汇中多了ACC_SYNCHRONIZED标记,方法调用就会去检查是否是有这个标记若是有,jvm就会要求线程在调用前先请求锁,但不管哪一种实现,在实质上仍是经过对象相关联的的monitor获取的。

而monitor是什么哪?它是每一个对象建立以后都会在jvm内部维护一个与之对应Monitor(监视器锁)也有人叫管程反正都是一个东西,能够理解为每一个对象天生都有一把看不见的锁咱们叫他monitor锁,而每一个线程会有一个可用的MR(Monitor Record)列表,还有一个全局可用列表,每个被锁住的对象都会和一个MR相关联,而且对象monitor中会有一个owner字段存放占用该锁线程惟一标识,表示这个锁已经被哪一个线程占用,synchronized就是基于进入与退出Monitor对象实现方法与代码块同步,而监视器锁的实现哪是依赖底层操做系统Mutex lock(互斥锁),它是一个重量级锁,每次从用户态切换到内的态的资源消耗是比较大的,也所以从jdk1.6后,java对synchronized进行了优化,从一开始的无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态,而且这个状态是不可逆的。

jvm加锁过程

 

 

 对象内存结构

 上文说过每一个Java对象都是天生的锁,存放在Java的对象头中,对象头包含三个区域,对象头,实例数据,补齐填充

第一部分是存储对象自身运行时的数据,哈希码,GC,偏向时间戳,保存对象的分代年龄,锁状态标志,偏向锁线程id,线程持有的锁,若是是数组还须要一块区域存放数组大小,class的对象指针是虚拟机经过他肯定这个对象是哪一个类的实例,咱们平时getClass直接获取类就跟这个有关,官方称这部分为Mark Word,第二部分略过,第三部分规定对象的大小必须是8字节的整数倍,至于为何,lz没去深究暂时不知道。咱们重点关注是Mark Word的锁标志位,因此锁的状态是保存在对象头中的,至于偏向状态,篇幅有限,下节在谈。

锁的粗化和消除

锁的粗化

锁带来性能开销是很大的,为了保证多线程的并发操做,一般会要求每一个线程持有锁的时间越短越好,但若是遇到一连串对同一把锁进行请求和释放的操做,jvm会进行优化智能的把锁操做的整合成一个较大同步块,从而减小了对锁的频繁申请和释放提升性能。

public class ThreadDemo7 implements Runnable {
    public void test(){
        synchronized (this){
            System.out.println(1111);
        }
        synchronized (this){
            System.out.println(222);
        }
        synchronized (this){
            System.out.println(333);
        }
    }
    public static void main(String[] args) {
        ThreadDemo7 threadDemo7 = new ThreadDemo7();
        Thread thread = new Thread(threadDemo7);
        thread.start();
    }

    @Override
    public void run() {
        test();
    }
}

锁的消除

咱们设置了同步块,在字节码中也发现了monitorenter和monitorexit,至少看上去有锁的获取和释放过程,但执行的结果与咱们预测的风马牛不相及。

public class ThreadDemo8 implements Runnable {
    private static int count = 0;
    @Override
    public void run() {
        synchronized (new Object()){
            count++;
            System.out.println("锁的消除...=>"  + Thread.currentThread().getName() + "值=>" + count);
        }
    }

    public static void main(String[] args) {
        ThreadDemo8 threadDemo8 = new ThreadDemo8();
        for (int i = 0; i < 10; ++i){
            Thread thread = new Thread(threadDemo8);
            thread.start();
        }
    }

}

执行结果

锁的消除...=>Thread-6值=>4
锁的消除...=>Thread-4值=>2
锁的消除...=>Thread-5值=>4
锁的消除...=>Thread-0值=>4
锁的消除...=>Thread-1值=>6
锁的消除...=>Thread-2值=>6
锁的消除...=>Thread-3值=>7
锁的消除...=>Thread-9值=>8
锁的消除...=>Thread-7值=>9
锁的消除...=>Thread-8值=>10

这是由于jit在编译代码时,使用了逃逸分析的技术,判断程序中的使用锁的对象是否被其余线程使用,若是只被一个线程使用,这个同步代码就不会生成synchronized锁标识的锁申请和释放的机器码,消除了锁的使用流程。因此,并非全部的实例对象都存放在堆区,若是发生线程逃逸行为,将会存储在线程栈上。

总结

锁的重入和锁膨胀升级,在后期在慢慢整理。

参考

https://blog.csdn.net/axiaoboge/article/details/84335452

https://www.cnblogs.com/xrq730/p/4853578.html

相关文章
相关标签/搜索