zoukankan      html  css  js  c++  java
  • 初识指令重排序,Java 中的锁

    本文是作者原创,版权归作者所有.若要转载,请注明出处.本文只贴我觉得比较重要的源码

    指令重排序

    Java语言规范JVM线程内部维持顺序化语义,即只要程序的最终结果与它顺序化情况的结果相等,那么指令的执行顺序可以与代码逻辑顺序不一致,这个过程就叫做指令的重排序。
        指令重排序的意义:使指令更加符合CPU的执行特性,最大限度的发挥机器的性能,提高程序的执行效率。
    看个demo
    public static void main(String[] args) throws InterruptedException {
            int j=0;
            int k=0;
            j++;
            System.out.println(k);
            System.out.println(j);
        }

    上面这段代码可能会被重排序:如下

    public static void main(String[] args) throws InterruptedException {
            int k=0;
            System.out.println(k);
            int j=0;
            j++;
            System.out.println(j);
        }

    此时指令的执行顺序可以与代码逻辑顺序不一致,但不影响程序的最终结果.

    再看个demo

    public class ThreadExample2 {
    
        static int i;
        public  static boolean runing = true;
    
        public static void main(String[] args) throws InterruptedException {
            traditional();
            Thread.sleep(100);
            runing = false;
        }
    
        public static void traditional() {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    while (runing){
                        i++;//没有方法,JVM会做指令重排序,激进优化
                    }
                }
            };
            thread.start();
        }
        
    }

    执行下main方法

     可以看出该程序一直在跑,不会停止.

    此时jvm发现traditional方法内没有其他方法,JVM会做指令重排序,采取激进优化策略,对我们的代码进行了重排序

    如下:

    static int i;
        public  static boolean runing = true;
    
        public static void main(String[] args) throws InterruptedException {
            traditional();
            Thread.sleep(100);
            runing = false;
        }
    
        public static void traditional() {
            Thread thread = new Thread() {
                boolean temp=runing;//注意这里,此时while的条件永远为true
                @Override
                public void run() {
                    while (temp){
                        i++;//没有方法,JVM会做指令重排序,激进优化
                    }
                }
            };
            thread.start();
        }

    因此程序不会停止.

    我们稍微改动下代码,在while 循环里加个方法

    static int i;
        public  static boolean runing = true;
    
        public static void main(String[] args) throws InterruptedException {
            traditional();
            Thread.sleep(100);
            runing = false;
        }
    
        public static void traditional() {
            boolean temp=runing;
            Thread thread = new Thread() {
                @Override
                public void run() {
                    while (runing){//
                        i++;//没有方法,JVM会做指令重排序,激进优化
                        //有方法,JVM认为可能存在方法溢出,不做指令重排序,保守优化策略
                        aa();
                    }
                }
            };
            thread.start();
        }
    
        public static void aa(){
            System.out.println("hello");
        }

    看下结果

     可以看出,程序自行停止了,因为有方法,JVM认为可能存在方法溢出,不做指令重排序,采取保守优化策略

    runing = false;

    全局变量runing 改动值以后,被thread线程识别,while 循环里值变为false,就自动停止了.

    ok,继续,我们把main方法中的sleep()注释掉,如下

    public static void main(String[] args) throws InterruptedException {
            traditional();
            //Thread.sleep(100);
            runing = false;//会优先执行主线程的代码
        }
    
        public static void traditional() {
            boolean temp=runing;
            Thread thread = new Thread() {
                @Override
                public void run() {
                    while (runing){//
                        i++;
                    }
                }
            };
            thread.start();
        }

    看下结果:

     此时,程序停止了,这是为什么呢:

    可能是因为thread 线程和main线程竞争cpu资源的时候,会优先分配给main线程(我不确定,读者们可以自己思考一下)

    Java 中的锁

    synchronized关键字

      在1.6版本之前,synchronized都是重量级锁

      1.6之后,synchronized被优化,因为互斥锁比较笨重,如果线程没有互斥,那就不需要互斥锁

    重量级锁

    1.当一个线程要访问一个共享变量时,先用锁把变量锁住,然后再操作,操作完了之后再释放掉锁,完成

    2.当另一个线程也要访问这个变量时,发现这个变量被锁住了,无法访问,它就会一直等待,直到锁没了,它再给这个变量上个锁,然后使用,使用完了释放锁,以此进行

    3.我们可以这么理解:重量级锁是调用操作系统的函数来实现的锁--mutex--互斥锁

    以linux为例:

    1.互斥变量使用特定的数据类型:pthread_mutex_t结构体,可以认为这是一个函数
    2.可以用pthread_mutex_init进行函数动态的创建 : int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
    3.对锁的操作主要包括加锁 pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个
    3.1  int pthread_mutex_tlock(pthread_mutex_t *mutex) 在寄存器中对变量操作(加/减1)
    3.2  int pthread_mutex_unlock(pthread_mutex_t *mutex) 释放锁,状态恢复
    3.3  int pthread_mutex_trylock(pthread_mutex_t *mutex)
     pthread_mutex_trylock()语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待

    函数pthread_mutex_trylock会尝试对互斥量加锁,如果该互斥量已经被锁住,函数调用失败,返回EBUSY,否则加锁成功返回0,线程不会被阻塞

    偏向锁

    偏向锁是synchronized锁的对象没有资源竞争的情况下存在的,不会一直调用操作系统函数实现(第一次会调用),而重量级锁每次都会调用

    看个demo

    public class SyncDemo2 {
    
        Object o= new Object();
    
        public static void main(String[] args) {
            System.out.println("pppppppppppppppppppppp");
            SyncDemo2 syncDemo = new SyncDemo2();
            syncDemo.start();
        }
    
        public void start() {
            Thread thread = new Thread() {
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(500);
                            sync();
                        } catch (InterruptedException e) {
    
                        }
                    }
                }
            };
    
            Thread thread2 = new Thread() {
                @Override
                public void run() {
                    while (true) {
                        try {
                           Thread.sleep(500);
                            sync();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
    
            thread.setName("t1");
            thread2.setName("t2");
            //两个线程竞争时,synchronized是重量级锁,一个线程时,synchronized是偏向锁
            thread.start();
            thread2.start();
        }
    
        //在1.6版本之前,synchronized都是重量级锁
        //1.6之后,synchronized被优化,因为互斥锁比较笨重,如果线程没有互斥,那就不需要互斥锁
        public void sync() {
            synchronized (o) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }

    代码很简单,就是启动两个线程,并且调用同一个同步方法,看下结果

     可以看到,两个线程都执行了该同步方法,此时两个线程竞争,synchronized是重量级锁

    我们把一个线程注释掉

    //两个线程竞争时,synchronized是重量级锁,一个线程时,synchronized是偏向锁
            thread.start();
            //thread2.start();

    看下结果:

    此时synchronized是偏向锁

     那么怎么证明呢:我目前没那个实力,给个思路.

    1.需要编译并修改linux源码函数pthread_mutex_lock(),在函数中打印当前线程的pid

    2.在同步方法中打印语句"current id"+当前pid(需要自己写c语言实现),java的Thread.currentThread().getId()不能获取操作系统级别的pid

    3.两个线程竞争时,执行一次

     说明是重量级锁,因为每次都调用操作系统的函数pthread_mutex_lock()来实现

    4.注释掉一个线程,再执行一次

     说明是偏向锁,因为第一次会调用pthread_mutex_lock(),后面就不调用系统函数了.

  • 相关阅读:
    naotu.baidu.com 非常棒的脑图在线工具
    编程常用英语词汇大全
    我的总结,编程人生
    排序箭头,升序,降序简单实现
    jquery网页倒计时效果,秒杀,限时抢购!
    echarts入门,5分钟上手写ECharts的第一个图表
    严谨的程序案例Api
    github Travis CI 持续集成
    Cmake使用
    Linxu安装Lamp环境
  • 原文地址:https://www.cnblogs.com/lusaisai/p/12731593.html
Copyright © 2011-2022 走看看