多线程学习(二)线程安全

博客中相关代码的git 地址:

多线程学习(一)基础知识地址:https://blog.csdn.net/qq_40119805/article/details/107399842
多线程学习(二)线程安全地址:https://blog.csdn.net/qq_40119805/article/details/107432575
多线程学习(三)线程通信地址:https://blog.csdn.net/qq_40119805/article/details/107456166

本章主要介绍:
1.什么是线程安全 2.如何实现线程安全 3.synchronized的使用 4.volatile的使用

1. 线程安全基本概念

1.1 什么是非线程安全

多个线程操作同一实例变量时则有可能出现线程安全问题

这里使用第一章的例子,两条线程对同一实例变量执行 i++ 操作各10000次,理论结果为i = 2W,但是控制台打印结果仅为10055;导致该问题出现的原因是线程拿到了其他线程已经修改但是没有保存的数据;

package com.company.并发安全示例;

public class Main{
    public static int i = 0;

    public static void main(String[] args) throws InterruptedException {

        Main main = new Main();
        MyThread myThread1 = main.new MyThread();
        MyThread myThread2 = main.new MyThread();
        myThread1.start();
        myThread2.start();
        Thread.sleep(10000);
        System.out.println("线程执行了:"+i);

    }
    class MyThread extends Thread{

        public void run(){
            for (int j = 0; j <= 10000; j++) {
                i++;
            }

        }

    }

}

控制台:
在这里插入图片描述

1.2 如何避免非线程安全

(1)无状态类避免非线程安全

非线程安全产生的原因时多个线程操作同一变量引起的,那么只要避免操作同一实例变量就可以避免非线程安全问题的产生,或者使用 私有变量;

(2)使用原子类避免非线程安全

atomic包提高原子更新基本类型的工具类,主要有这些:
AtomicBoolean:以原子更新的方式更新boolean;
AtomicInteger:以原子更新的方式更新Integer;
AtomicLong:以原子更新的方式更新Long;
AtomicDouble 以原子更新的方式更新Double;
Atomic 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。

实例代码:两条线程 各执行 i++操作1W次,结果为2W,控制台打印信息为2W符合预期;

package 原子类保证线程安全;

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static AtomicInteger i = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        Main main = new Main();
        MyThread myThread1 = main.new MyThread();
        MyThread myThread2 = main.new MyThread();
        myThread1.start();
        myThread2.start();
        Thread.sleep(1000);
        System.out.println("线程执行了:"+i);

    }
    class MyThread extends Thread{

        public void run(){
            for (int j = 0; j < 10000; j++) {
                i.addAndGet(1);
            }

        }

    }

}

控制台:
在这里插入图片描述

(3)synchronized锁避免非线程安全

依然时上面的例子,这里对for循环使用synchronized代码块包裹,指定Main.class为锁对象

package synchronized保证线程安全;

import java.util.concurrent.atomic.AtomicInteger;

public class Main {
    public static int i = 0;

    public static void main(String[] args) throws InterruptedException {

        Main main = new Main();
        MyThread myThread1 = main.new MyThread();
        MyThread myThread2 = main.new MyThread();
        myThread1.start();
        myThread2.start();
        Thread.sleep(1000);
        System.out.println("线程执行了:"+i);

    }
    class MyThread extends Thread{

        public  void run(){
            synchronized(Main.class){
                for (int j = 0; j < 10000; j++) {
                    i++;
                }
            }
        }

    }

}

控制台:
在这里插入图片描述

1.3 死锁相关

(1)死锁以及如何避免死锁

什么是死锁:

当线程 A 持有独占锁a,并尝试去获取独占锁 b 的同时,线程 B 持有独占锁 b,并尝试获取独占锁 a 的情况下,就会发生 AB 两个线程由于互相持有对方需要的锁,而发生的阻塞现象,我们称为死锁。

如何避免死锁:

1.尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
2.尽量使用 Java. util. concurrent 并发类代替自己手写锁。
3.尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
4.尽量减少同步的代码块。

(2)活锁,锁饥饿

活锁:

活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
死锁是大家都拿不到资源都占用着对方的资源,而活锁是拿到资源却又相互释放不执行

死锁代码示例:两条线程互相持有对方需要的锁,导致两条线程都无法运行;

package 死锁和活锁;

import 线程的停止.退出标志停止.MyThread;

public class Main {
    public volatile Object lock1="lock1";
    public volatile Object lock2="lock2";
    public static void main(String[] args) throws InterruptedException {
        Main main = new Main();
        MyThread1 myThread1 =main.new MyThread1();
        MyThread2 myThread2 = main.new MyThread2();
        myThread1.start();
        Thread.sleep(500);
        myThread2.start();
    }
    class MyThread1 extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"尝试获取lock2");
            synchronized (lock2){
                System.out.println(Thread.currentThread().getName()+"获取lock2成功");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"尝试获取lock1");
                synchronized (lock1){
                    System.out.println(Thread.currentThread().getName()+"获取lock1成功");
                }
            }
        }
    }
    class MyThread2 extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"尝试获取lock1");
            synchronized (lock1){
                System.out.println(Thread.currentThread().getName()+"获取lock1成功");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"尝试获取lock2");
                synchronized (lock2){
                    System.out.println(Thread.currentThread().getName()+"获取lock2成功");
                }
            }
        }
    }
}

运行结果:
在这里插入图片描述
活锁示例:
太难了。写不出来;

锁饥饿:

锁饥饿是指线程优先级低迟迟无法获取到资源;

(3)乐观锁和悲观锁

悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。

1.4 Java. util. concurrent 包下的常用并发类

JDK5中添加了新的java.util.concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能。因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全性,所以这种方法的代价就是严重降低了并发性,当多个线程竞争容器时,吞吐量严重降低。因此JDK5开始针对多线程并发访问设计,提供了并发性能较好的并发容器,引入了java.util.concurrent包。与Vector和Hashtable、Collections.synchronizedXxx()同步容器等相比,util.concurrent中引入的并发容器主要解决了两个问题:
1)根据具体场景进行设计,尽量避免synchronized,提供并发性。
2)定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。

2. syncchronized对象锁

2.1 Synchronized的基本使用

(1)修饰普通方法

1.基本使用

直接把sunchronized 添加到方法前面:

代码示例:

package Synchronized的基本使用;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Mythread mythread1 = new Main().new Mythread();
        Mythread mythread2 = new Main().new Mythread();
        mythread1.start();
        mythread2.start();
        Thread.sleep(100);
    }
    class Mythread extends Thread{
        @Override
        public synchronized void run() {
            for (int j = 0; j < 10000; j++) {
                i++;
            }
        }
    }
}

控制台:
在这里插入图片描述
2.修饰方法是锁定的当前对象

两个方法同时使用synchronized进行修饰,两者不能异步执行,因为它锁定的是当前对象;

代码示例:当method1()执行结束后才会执行method2()

package Synchronized的基本使用;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test = new Main().new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test);
        Mythread2 mythread2 = new Main().new Mythread2(test);
        mythread1.start();
        Thread.sleep(100);
        mythread2.start();
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }
    class Test{
        public synchronized void method1() {
            System.out.println("method1开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1执行结束");
        }
        public synchronized void method2() {
            System.out.println("method2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2执行结束");
        }
    }
}

运行结果:在这里插入图片描述

3.多个对象多个锁;

synchronized是锁的实例对象而不是 代码

代码示例:两个对象 test1 和 test2

package Synchronized的基本使用.多个对象多个锁;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test1 = new Main().new Test();
        Test test2 = new Main().new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test1);
        Mythread2 mythread2 = new Main().new Mythread2(test2);
        mythread1.start();
        Thread.sleep(100);
        mythread2.start();
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }
    class Test{
        public synchronized void method1() {
            System.out.println("method1开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1执行结束");
        }
        public synchronized void method2() {
            System.out.println("method2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2执行结束");
        }
    }
}

运行结果:方法交替执行,没有同步
在这里插入图片描述

(2)修饰静态方法

对静态方法的同步本质上是对类的同步(静态方法本质上是属于类的方法,而不是对象上的方法),所以不存在多个对象多个锁的问题;相当于 synchronized(xxx.class){};

Main:

package Synchronized的基本使用.修饰静态方法;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test1 = new Test();
        Test test2 = new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test1);
        Mythread2 mythread2 = new Main().new Mythread2(test2);
        mythread1.start();
        Thread.sleep(100);
        mythread2.start();
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }

}

Test:

package Synchronized的基本使用.修饰静态方法;

class Test{
        public static synchronized void method1() {
            System.out.println("method1开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method1执行结束");
        }
        public static synchronized void method2() {
            System.out.println("method2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2执行结束");
        }
    }

运行结果:顺序执行
在这里插入图片描述

(3)修饰代码块

把需要同步的代码用synchronized(){}包裹

代码示例:

package Synchronized的基本使用.同步代码块;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test = new Main().new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test);
        Mythread2 mythread2 = new Main().new Mythread2(test);
        mythread1.start();
        Thread.sleep(100);
        mythread2.start();
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }
    class Test{
        public  void method1() {
            synchronized (this){
                System.out.println("method1开始执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("method1执行结束");
            }
        }
        public synchronized void method2() {
            synchronized (this){
                System.out.println("method2开始执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("method2执行结束");
            }
        }
    }
}

运行结果:
在这里插入图片描述

(4)Synchronized锁重入

synchronized方法/代码块内部是可以再次获取该锁的其他方法是可以再次获取到的;这就叫锁重入;

示例代码:在synchronized方法调用另一个 synchronized方法 ,可以正常获取锁;

package Synchronized的基本使用.锁重入;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test = new Main().new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test);
        mythread1.start();
        Thread.sleep(100);
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }
    class Test{
        public synchronized void method1() {
            System.out.println("method1开始执行");
            System.out.println("method1调用method2");
            method2();
            System.out.println("method1执行结束");
        }
        public synchronized void method2() {
            System.out.println("method2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2执行结束");
        }
    }
}

运行结果:
在这里插入图片描述

(5)出现异常自动释放锁

当一个线程执行的代码出现异常时,其持有的锁会自动释放;

示例代码:在 method1()中主动制造异常,控制台可见 method1()没有结束但是 method2()获取到了锁;

package Synchronized的基本使用.出现异常自动释放锁;

public class Main {
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Test test = new Main().new Test();
        Mythread1 mythread1 = new Main().new Mythread1(test);
        Mythread2 mythread2 = new Main().new Mythread2(test);
        mythread1.start();
        Thread.sleep(100);
        mythread2.start();
    }
    class Mythread1 extends Thread{
        private Test test = null;
        Mythread1(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method1();
        }
    }
    class Mythread2 extends Thread{
        private Test test = null;
        Mythread2(Test test){
            this.test = test;
        }
        @Override
        public void run() {
            test.method2();
        }
    }
    class Test{
        public synchronized void method1() {
            int[] a = {1};
            System.out.println("method1开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(a[2]);
            System.out.println("method1执行结束");
        }
        public synchronized void method2() {
            System.out.println("method2开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("method2执行结束");
        }
    }
}

运行结果:
在这里插入图片描述

2.2 Synchronized 原理

转自https://www.cnblogs.com/paddix/p/5367116.html
我们先通过反编译下面的代码来看看Synchronized是如何实现对代码块进行同步的:

package com.paddx.test.concurrent;

public class SynchronizedDemo {
    public void method() {
        synchronized (this) {
            System.out.println("Method 1 start");
        }
    }
}

反编译结果:在这里插入图片描述
关于这两条指令的作用,我们直接参考JVM规范中描述:

monitorenter :

Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.

这段话的大概意思为:

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit:

The thread that executes monitorexit must be the owner of the monitor
associated with the instance referenced by objectref. The thread
decrements the entry count of the monitor associated with objectref.
If as a result the value of the entry count is zero, the thread exits
the monitor and is no longer its owner. Other threads that are
blocking to enter the monitor are allowed to attempt to do so.

这段话的大概意思为:

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

2.3 高效并发–锁优化

高效并发时jdk1.5到1.6的一个重要改进;主要通过适应性自旋,锁消除,锁粗化轻量级锁和偏向锁等来提高程序执行效率;

(1)自旋锁和自适应自旋

jdk1.4就引入了自旋锁,但是默认是关闭的,在jdk1.6默认开启且引入了自适应自旋锁

自旋锁:假设有A , B两条线程,他们同时抢占C资源,其中A线程抢到C并开始执行,那么B线程没有抢到资源该怎么办呢,有两种选择,一种时将线程挂起,等待一段时间后再尝试获取资源C,但是A线程可能执行的非常快,为了这一小段时间将线程挂起是非常不值得的,因为挂起操作需要操作系统的支持,所以B线程使用 while循环再次尝试获取锁,这就是锁自旋;

自适应自旋:while循环在A执行的非常快的情况下是具有很高的效率的,但是如果A线程迟迟不结束执行,那么B将会一直自旋,这会给cpu代来很大压力,所以出现了适应性自旋,即根据上一次获取锁的自旋次数来决定这一次的自旋次数,例如100次或者不进行自旋直接挂起;

(2)锁消除

锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。

代码示例:虽然StringBuffer的append是一个同步方法,但是这段程序中的StringBuffer属于一个局部变量,并且不会从该方法中逃逸出去,所以其实这过程是线程安全的,可以将锁消除。

package com.paddx.test.concurrent;

public class SynchronizedTest02 {

    public static void main(String[] args) {
        SynchronizedTest02 test02 = new SynchronizedTest02();
        //启动预热
        for (int i = 0; i < 10000; i++) {
            i++;
        }
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000000; i++) {
            test02.append("abc", "def");
        }
        System.out.println("Time=" + (System.currentTimeMillis() - start));
    }

    public void append(String str1, String str2) {
        StringBuffer sb = new StringBuffer();
        sb.append(str1).append(str2);
    }
}

(3)锁粗化

锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。

代码示例:这里每次调用stringBuffer.append方法都需要加锁和解锁,如果虚拟机检测到有一系列连串的对同一个对象加锁和解锁操作,就会将其合并成一次范围更大的加锁和解锁操作,即在第一次append方法时进行加锁,最后一次append方法结束后进行解锁。

package com.paddx.test.string;

public class StringBufferTest {
    StringBuffer stringBuffer = new StringBuffer();

    public void append(){
        stringBuffer.append("a");
        stringBuffer.append("b");
        stringBuffer.append("c");
    }
}

(4)重量级锁

Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。JDK中对Synchronized做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。

(5)锁升级

简单的说下锁升级有助于理解轻量级锁和偏向锁;
首先每个对象都有一个对象头,里面记录了是否可以偏向,如果可以偏向,我们尝试(CAS)将线程id写入对象头,如果成功则说明没有人和我竞争,否则则说明有其他线程尝试获取偏向锁,这时会触发锁升级,升级为轻量级锁(自旋锁),自旋锁反复尝试获取锁,多次获取不到锁会再次膨胀升级为重量级锁;

在这里插入图片描述

三种锁的对比:
在这里插入图片描述

(5)轻量级锁

获取轻量锁的过程与偏向锁不同,竞争锁的线程首先需要拷贝对象头中的Mark Word到帧栈的锁记录中。拷贝成功后使用CAS操作尝试将对象的Mark Word更新为指向当前线程的指针。如果这个更新动作成功了,那么这个线程就拥有了该对象的锁。如果更新失败,那么意味着有多个线程在竞争。
当竞争线程尝试占用轻量级锁失败多次之后(使用自旋)轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。

(6)偏向锁

引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能。

偏向锁的枷锁过程:

(1)访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态。

(2)如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)。

(3)如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)。
(4)如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码。
(5)执行同步代码。

偏向锁的释放:

偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

3. volatile

3.1 Volatile的使用

Volatile可以保证变量的可见性但不会保证原子性;每条线程都有一块自己的私有内存所以变量发生修改首先是在私有内存中进行的,随后在更新到主存,使用volatile后强制从主存中读取变量;

示例代码:这段代码如果不加 volatile在–server服务器模式中的64bit的jvm中是无法停止的;

package Volatile的使用;

import 线程的停止.退出标志停止.MyThread;

public class Main {
    private volatile Boolean stopMark = true;
    public static void main(String[] args) {
        Main main = new Main();
        Mythread mythread = main.new Mythread(main);
        mythread.start();
        main.setStopMark(false);
    }
    class Mythread extends Thread{
        Main main = null;
        Mythread(Main main){
            this.main= main;
        }
        @Override
        public void run() {
            main.sout();
        }
    }
    public void setStopMark(Boolean stopMark) {
        this.stopMark = stopMark;
    }
    public void sout(){
        while (stopMark){
            System.out.println("执行循环");
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值