初识指令重排序,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(),后面就不调用系统函数了.

原文地址https://www.cnblogs.com/lusaisai/p/12731593.html

相关文章
|
5天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
3天前
|
XML 前端开发 Oracle
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
16:JSP简介、注释与Scriptlet、Page指令元素、Include操作、内置对象、四种属性-Java Web
9 2
|
6天前
|
安全 Java 编译器
【Java EE】总结12种锁策略以及synchronized的实现原理
【Java EE】总结12种锁策略以及synchronized的实现原理
|
7天前
|
Java
【专栏】Java 中的锁是什么意思,有哪些分类?
【4月更文挑战第28天】Java多线程中,锁用于控制共享资源访问,确保数据一致性和正确性。本文探讨锁的概念、作用及分类:乐观锁与悲观锁、自旋锁与适应性自旋锁、公平锁与非公平锁、可重入锁和读写锁。使用锁需注意避免死锁、合理选择锁粒度及性能优化。理解锁有助于提升多线程编程的效率和稳定性。
|
7天前
|
Java 编译器
Java并发编程中的锁优化策略
【4月更文挑战第28天】在Java并发编程中,锁是一种常用的同步机制,用于保护共享资源的访问。然而,不当的使用锁可能导致性能问题和死锁。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。
|
8天前
|
安全 Java
【JAVA】Java并发编程中的锁升级机制
【JAVA】Java并发编程中的锁升级机制
|
8天前
|
安全 Java
【JAVA】Java8开始ConcurrentHashMap,为什么舍弃分段锁
【JAVA】Java8开始ConcurrentHashMap,为什么舍弃分段锁
|
8天前
|
安全 Java
【JAVA面试题】什么是对象锁?什么是类锁?
【JAVA面试题】什么是对象锁?什么是类锁?
|
11天前
|
Java 调度
Java中的并发编程:从线程到锁的探索
在Java的并发编程中,线程与锁是构建高效、稳定多线程应用程序的关键要素。本文将带你走进Java并发编程的世界,从线程的基础知识讲起,逐步深入到锁的实现与应用,旨在让读者对Java并发编程有更深入的理解,并能在实际开发中灵活运用。
|
11天前
|
算法 安全 Java
您真的了解Java中的锁吗?这7种不同维度下的锁知道吗?
您真的了解Java中的锁吗?这7种不同维度下的锁知道吗?
21 2