JAVA-Thread-Base-3

Java 线程基础 3

1 线程同步

1.1 线程同步机制简介

线程同步机制是一套用于协调线程之间的数据访问的机制.该机制可以保障线程安全.

Java 平台提供的线程同步机制包括: 锁, volatile 关键字, final 关键字,static 关键字,以及相关的 API,如 Object.wait()/Object.notify()等

1.2 锁概述

线程安全问题的产生前提是多个线程并发访问共享数据.

将多个线程对共享数据的并发访问转换为串行访问,即一个共享数据一次只能被一个线程访问.锁就是复用这种思路来保障线程安全的

锁(Lock)可以理解为对共享数据进行保护的一个许可证.

对于同一个许可证保护的共享数据来说,任何线程想要访问这些共享数据必须先持有该许可证. 一个线程只有在持有许可证的情况下才能对这些共享数据进行访问; 并且一个许可证一次只能被一个线程持有; 许可证线程在结束对共享数据的访问后必须释放其持有的许可证.

一线程在访问共享数据前必须先获得锁; 获得锁的线程称为锁的持有线程; 一个锁一次只能被一个线程持有. 锁的持有线程在获得锁之后 和释放锁之前这段时间所执行的代码称为临界区(CriticalSection).

锁具有排他性(Exclusive), 即一个锁一次只能被一个线程持有.这种锁称为排它锁或互斥锁(Mutex).

JVM 把锁分为内部锁和显示锁两种. 内部锁通过 synchronized 键字实现; 显示锁通过 java.concurrent.locks.Lock 接口的实现类实现的

1.2.1 锁的作用

锁可以实现对共享数据的安全访问. 保障线程的原子性,可见性与有序性.

锁是通过互斥保障原子性. 一个锁只能被一个线程持有, 这就保证临界区的代码一次只能被一个线程执行.使得临界区代码所执行的操作自然而然的具有不可分割的特性,即具备了原子性.

可见性的保障是通过写线程冲刷处理器的缓存和读线程刷新处理器缓存这两个 动作实现的.
在 java 平台中,锁的获得隐含着刷新处理器缓存的动作, 锁的释放隐含着冲刷处理器缓存的动作.

锁能够保障有序性.写线程在临界区所执行的在读线程所执行的临界区看来像是完全按照源码顺序执行的.

注意:使用锁保障线程的安全性,必须满足以下条件:

这些线程在访问共享数据时必须使用同一个锁
即使是读取共享数据的线程也需要使用同步锁

1.2.2 锁相关的概念

1)可重入性

可重入性(Reentrancy)描述这样一个问题: 一个线程持有该锁的时候能再次(多次)申请该锁

void methodA(){
    申请 a 锁
    methodB();
    释放 a 锁
}

void methodB(){
    申请 a 锁
    .... 
    释放 a 锁
}

如果一个线程持有一个锁的时候还能够继续成功申请该锁,称该锁是可重入的, 否则就称该锁为不可重入的

2)锁的争用与调度

Java 平台中内部锁属于非公平锁, 显示 Lock 锁既支持公平锁又支持非公平锁

3)锁的粒度

一个锁可以保护的共享数据的数量大小称为锁的粒度.
锁保护共享数据量大,称该锁的粒度粗, 否则就称该锁的粒度细.
锁的粒度过粗会导致线程在申请锁时会进行不必要的等待.锁的粒度过细会增加锁调度的开销

1.3 内部锁:synchronized 关键字

Java 中的每个对象都有一个与之关联的内部锁(Intrinsic lock). 这种锁也称为监视器(Monitor), 这种内部锁是一种排他锁,可以保障原子性,可见性与有序性.
内部锁是通过 synchronized 关键字实现的.synchronized 关键字修饰代码块,修饰该方法.

修饰代码块的语法:

synchronized( 对象锁 ) {
    同步代码块,可以在同步代码块中访问共享数据
}

修饰实例方法就称为同步实例方法
修饰静态方法称称为同步静态方法

1.3.1 synchronized 同步代码块

package com.sevattal.intrinsiclock;
/**
* synchronized 同步代码块
* this 锁对象
* Author: sevattal
*/
public class Test01 {
    public static void main(String[] args) {
        //创建两个线程,分别调用 mm()方法
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test01 obj = new Test01();
        new Thread(new Runnable() {
            @Override
            public void run() {
            obj.mm(); //使用的锁对象this就是obj对象
        }}).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
            obj.mm(); //使用的锁对象this也是obj对象
            }
        }).start();
    }
    //定义方法,打印 100 行字符串
    public void mm(){
        synchronized ( this ) { //经常使用this当前对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
}

package com.sevattal.intrinsiclock;
/**
* synchronized 同步代码块
* 如果线程的锁不同, 不能实现同步
* 想要同步必须使用同一个锁对象
* Author: sevattal
*/
public class Test02 {
    public static void main(String[] args) {
        //创建两个线程,分别调用 mm()方法
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test02 obj = new Test02();
        Test02 obj2 = new Test02();
        new Thread(new Runnable() {
            @Override
            public void run() {
            obj.mm(); //使用的锁对象this就是obj对象
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj2.mm(); //使用的锁对象 this 也是 obj2对象
            }
        }).start();
    }
    //定义方法,打印 100 行字符串
    public void mm(){
        synchronized ( this ) { //经常使用this当前对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
}

package com.sevattal.intrinsiclock;
/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象
* Author: sevattal
*/
public class Test03 {
    public static void main(String[] args) {
    //创建两个线程,分别调用 mm()方法
    //先创建 Test01 对象,通过对象名调用 mm()方法
    Test03 obj = new Test03();
    Test03 obj2 = new Test03();
    new Thread(new Runnable() {
        @Override
        public void run() {
            obj.mm(); //使用的锁对象 OBJ 常量
        }
    }).start();
    new Thread(new Runnable() {
        @Override
        public void run() {
            obj2.mm(); //使用的锁对象 OBJ 常量
        }
    }).start();
    }
    public static final Object OBJ = new Object(); //定义一个常量, 
    //定义方法,打印 100 行字符串
    public void mm(){
        synchronized ( OBJ ) { //使用一个常量对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
}

package com.sevattal.intrinsiclock;
/**
* synchronized 同步代码块
* 使用一个常量对象作为锁对象,不同方法中 的同步代码块也可以
同步
* Author: sevattal
*/
public class Test04 {
    public static void main(String[] args) {
        //创建两个线程,分别调用 mm()方法
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test04 obj = new Test04();
        Test04 obj2 = new Test04();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.mm(); //使用的锁对象 OBJ 常量
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj2.mm(); //使用的锁对象 OBJ 常量
            }
        }).start();
        //第三个线程调用静态方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                sm(); //使用的锁对象 OBJ 常量
            }
        }).start();
    }
    public static final Object OBJ = new Object(); //定义一个常量, 
    //定义方法,打印 100 行字符串
    public void mm(){
        synchronized ( OBJ ) { //使用一个常量对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
    //定义方法,打印 100 行字符串
    public static void sm(){
        synchronized ( OBJ ) { //使用一个常量对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
}

1.3.2 同步方法

package com.sevattal.intrinsiclock;
/**
 * synchronized 同步实例方法
 * 把整个方法体作为同步代码块
 * 默认的锁对象是 this 对象
 * Author: sevattal
 */
public class Test05 {
    public static void main(String[] args) {
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test05 obj = new Test05();
        //一个线程调用 mm()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.mm(); //使用的锁对象this就是obj对象
            }
        }).start();
        //另一个线程调用 mm22()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.mm22(); //使用的锁对象 this 也是 obj对象, 可以同步
                // new Test05().mm22(); //使用的锁对象 this是刚刚 new 创建的一个新对象,不是同一个锁对象不能同步
            }
        }).start();
    }
    //定义方法,打印 100 行字符串
    public void mm(){
        synchronized ( this ) { //经常使用this当前对象作为锁对象
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
    //使用 synchronized 修饰实例方法,同步实例方法, 默认 this 作为锁对象
    public synchronized void mm22(){
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + " --> " + i);
        }
    }
}

package com.sevattal.intrinsiclock;
/**
 * synchronized 同步静态方法
 * 把整个方法体作为同步代码块
 * 默认的锁对象是当前类的运行时类对象, Test06.class, 有人称它为类锁
 * Author: sevattal
 */
public class Test06 {
    public static void main(String[] args) {
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test06 obj = new Test06();
        //一个线程调用 m1()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.m1(); //使用的锁对象是 Test06.class
            }
        }).start();
        //另一个线程调用 sm2()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                Test06.sm2(); //使用的锁对象是 Test06.class
            }
        }).start();
    }
    //定义方法,打印 100 行字符串
    public void m1(){
        //使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象
        synchronized ( Test06.class ) {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
            }
        }
    }
    //使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类Test06.class 作为锁对象
    public synchronized static void sm2(){
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + " --> " + i);
        }
    }
}

package com.sevattal.intrinsiclock;
/**
 * 同步方法与同步代码块如何选择
 * 同步方法锁的粒度粗, 执行效率低, 同步代码块执行效率高
 *
 * Author: sevattal
 */
public class Test07 {
    public static void main(String[] args) {
        Test07 obj = new Test07();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.doLongTimeTask();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.doLongTimeTask();
            }
        }).start();
    }
    //同步方法, 执行效率低
    public synchronized void doLongTimeTask(){
        try {
            System.out.println("Task Begin");
            Thread.sleep(3000); //模拟任务需要准备 3 秒钟
            System.out.println("开始同步");
            for(int i = 1; i <= 100; i++){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
            System.out.println("Task end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //同步代码块,锁的粒度细, 执行效率高
    public void doLongTimeTask2(){
        try {
            System.out.println("Task Begin");
            Thread.sleep(3000); //模拟任务需要准备 3 秒钟
            synchronized (this){
                System.out.println("开始同步");
                for(int i = 1; i <= 100; i++){
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
            System.out.println("Task end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.3.3 脏读

package com.sevattal.intrinsiclock;
/**
 * 脏读
 * 出现读取属性值出现了一些意外, 读取的是中间值,而不是修改之后 的值
 * 出现脏读的原因是 对共享数据的修改 与对共享数据的读取不同步
 * 解决方法:
 * 不仅对修改数据的代码块进行同步,还要对读取数据的代码块同步
 * Author: sevattal
 */
public class Test08 {
    public static void main(String[] args) throws InterruptedException {
        //开启子线程设置用户名和密码
        PublicValue publicValue = new PublicValue();
        SubThread t1 = new SubThread(publicValue);
        t1.start();
        //为了确定设置成功
        Thread.sleep(100);
        //在 main 线程中读取用户名,密码
        publicValue.getValue();
    }
    //定义线程,设置用户名和密码
    static class SubThread extends Thread{
        private PublicValue publicValue;
        public SubThread( PublicValue publicValue){
            this.publicValue = publicValue;
        }
        @Override
        public void run() {
            publicValue.setValue("bjpowernode", "123");
        }
    }
    static class PublicValue{
        private String name = "wkcto";
        private String pwd = "666";
        public synchronized void getValue(){
            System.out.println(Thread.currentThread().getName() + ", getter -- name: " + name + ",--pwd: " + pwd);
        }
        public synchronized void setValue(String name, String pwd){
            this.name = name;
            try {
                Thread.sleep(1000); //模拟操 name 属性需要一定时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.pwd = pwd;
            System.out.println(Thread.currentThread().getName() + ", setter --name:" + name + ", --pwd: " + pwd );
        }
    }
}

1.3.4 线程出现异常会自动释放锁

package com.sevattal.intrinsiclock;
/**
 * 同步过程中线程出现异常, 会自动释放锁对象
 *
 * Author: sevattal
 */
public class Test09 {
    public static void main(String[] args) {
        //先创建 Test01 对象,通过对象名调用 mm()方法
        Test09 obj = new Test09();
        //一个线程调用 m1()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                obj.m1(); //使用的锁对象是 Test06.class
            }
        }).start();
        //另一个线程调用 sm2()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                Test09.sm2(); //使用的锁对象是 Test06.class
            }
        }).start();
    }
    //定义方法,打印 100 行字符串
    public void m1(){
        //使用当前类的运行时类对象作为锁对象,可以简单的理解为把 Test06 类的字节码文件作为锁对象
        synchronized ( Test09.class ) {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + " --> " + i);
                if ( i == 50){
                    Integer.parseInt("abc"); //把字符串转换为int 类型时,如果字符串不符合 数字格式会产生异常
                }
            }
        }
    }
    //使用 synchronized 修饰静态方法,同步静态方法, 默认运行时类Test06.class 作为锁对象
    public synchronized static void sm2(){
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + " --> " + i);
        }
    }
}

1.3.5 死锁

package com.sevattal.intrinsiclock;
/**
 * 死锁
 * 在多线程程序中,同步时可能需要使用多个锁,如果获得锁的顺序
 不一致,可能会导致死锁
 * 如何避免死锁?
 * 当需要获得多个锁时,所有线程获得锁的顺序保持一致即可
 * Author: sevattal
 */
public class Test10 {
    public static void main(String[] args) {
        SubThread t1 = new SubThread();
        t1.setName("a");
        t1.start();
        SubThread t2 = new SubThread();
        t2.setName("b");
        t2.start();
    }
    static class SubThread extends Thread{
        private static final Object lock1 = new Object();
        private static final Object lock2 = new Object();
        @Override
        public void run() {
            if ("a".equals(Thread.currentThread().getName())){
                synchronized (lock1){
                    System.out.println("a 线程获得了 lock1 锁,还需 要获得 lock2 锁");
                    synchronized (lock2){
                        System.out.println("a 线程获得 lock1 后又 获得了 lock2,可以想干任何想干的事");
                    }
                }
            }
            if ("b".equals(Thread.currentThread().getName())){
                synchronized (lock2){
                    System.out.println("b 线程获得了 lock2 锁,还需要获得 lock1 锁");
                    synchronized (lock1){
                        System.out.println("b 线程获得lock2后又 获得了 lock1,可以想干任何想干的事");
                    }
                }
            }
        }
    }
}

1.4 轻量级同步机制:volative 关键字

1.4.1 volatile 的作用

volatile 关键的作用使变量在多个线程之间可见

package com.sevattal.volatilekw;
/**
 * volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取
 * Author: sevattal
 */
public class Test02 {
    public static void main(String[] args) {
        //创建 PrintString 对象
        PrintString printString = new PrintString();
        //开启子线程,让子线程执行 printString 对象的 printStringMethod()方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                printString.printStringMethod();
            }
        }).start();
        //main 线程睡眠 1000 毫秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("在 main 线程中修改打印标志");
        printString.setContinuePrint(false);
        //程序运行,查看在 main 线程中修改了打印标志之后 ,子线程打印是否可以结束打印
        //程序运行后, 可能会出现死循环情况
        //分析原因: main 线程修改了 printString 对象的打印标志后, 子线程读不到
        //解决办法: 使用 volatile 关键字修饰 printString 对象的打印标志. 
        //volatile 的作用可以强制线程从公共内存中读取变量的值,而不是从工作内存中读取
    }
    //定义类打印字符串
    static class PrintString{
        private volatile boolean continuePrint = true;
        public PrintString setContinuePrint(boolean continuePrint) {
            this.continuePrint = continuePrint;
            return this;
        }
        public void printStringMethod(){
            System.out.println(Thread.currentThread().getName() + "开始....");
            while ( continuePrint ){
            }
            System.out.println(Thread.currentThread().getName() + "结束++++++++++++++");
        }
    }
}

volatile 与 synchronized 比较

  1. volatile 关键字是线程同步的轻量级实现,所以volatile性能肯定比 synchronized 要好; volatile 只能修饰变量,而 synchronized 可以修饰方法,代码块. 随着 JDK 新版本的发布,synchronized 的执行效率也有较大的提升,在开发中使用 sychronized 的比率还是很大的.

  2. 多线程访问 volatile 变量不会发生阻塞,而 synchronized 可能会阻塞

  3. volatile 能保证数据的可见性,但是不能保证原子性; 而 synchronized 可以保证原子性,也可以保证可见性

  4. 关键字 volatile 解决的是变量在多个线程之间的可见性;synchronized 关键字解决多个线程之间访问公共资源的同步性.

1.4.2 volatile 非原子特性

volatile 关键字增加了实例变量在多个 线程之间的可见性,但是不具备原子性.

package com.sevattal.volatilekw;
/**
 * volatile 不是具备原子性
 * Author: sevattal
 */
public class Test03 {
    public static void main(String[] args) {
        //在 main 线程中创建 10 个子线程
        for (int i = 0; i < 100; i++) {
            new MyThread().start();
        }
    }
    static class MyThread extends Thread{
        //volatile 关键仅仅是表示所有线程从主内存读取 count 变量的值
        public static int count;
        /* //这段代码运行后不是线程安全的,想要线程安全,需要使用 synchronized 进行同
        步,如果使用 synchronized 同时,也就不需要 volatile 关键了*/
        /*public static void addCount(){
        for (int i = 0; i < 1000; i++) {
        //count++不是原子操作
        count++;
        }
        System.out.println(Thread.currentThread().getName() + " count=" + count);
        }*/
        public synchronized static void addCount(){
            for (int i = 0; i < 1000; i++) {
                //count++不是原子操作
                count++;
            }
            System.out.println(Thread.currentThread().getName() + " count=" + count);
        }
        @Override
        public void run() {
            addCount();
        }
    }
}

1.4.3 常用原子类进行自增自减操作

我们知道 i++操作不是原子操作, 除了使用 Synchronized 进行同步外,也可以使用 AtomicInteger/AtomicLong 原子类进行实现

package com.sevattal.volatilekw;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * 使用原子类进行自增
 * Author: sevattal
 */
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        //在 main 线程中创建 10 个子线程
        for (int i = 0; i < 1000; i++) {
            new MyThread().start();
        }
        Thread.sleep(1000);
        System.out.println( MyThread.count.get());
    }
    static class MyThread extends Thread{
        //使用 AtomicInteger 对象
        private static AtomicInteger count = new AtomicInteger();
        public static void addCount(){
            for (int i = 0; i < 10000; i++) {
                //自增的后缀形式
                count.getAndIncrement();
            }
            System.out.println(Thread.currentThread().getName() + " count=" + count.get());
        }
        @Override
        public void run() {
            addCount();
        }
    }
}

1.5 CAS

CAS(Compare And Swap)是由硬件实现的.
CAS 可以将 read- modify - write 这类的操作转换为原子操作.

i++自增操作包括三个子操作:

从主内存读取 i 变量值
对 i 的值加 1
再把加 1 之后 的值保存到主内存

CAS 原理: 在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新.

使用 CAS 实现线程安全的计数器

package com.sevattal.cas;
/**
 * 使用 CAS 实现一个线程安全的计数器
 * Author: sevattal
 */
public class CASTest {
    public static void main(String[] args) {
        CASCounter casCounter = new CASCounter();
        for (int i = 0; i < 100000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(casCounter.incrementAndGet());
                }
            }).start();
        }
    }
}
class CASCounter{
    //使用 volatile 修饰 value 值,使线程可见
    volatile private long value;
    public long getValue() {
        return value;
    }
    //定义 comare and swap 方法
    private boolean compareAndSwap(long expectedValue, long newValue){
        //如果当前 value 的值与期望的 expectedVAlue 值一样,就把当前的 Value 字段替换为newValue 值
        synchronized (this){
            if ( value == expectedValue){
                value = newValue;
                return true;
            }else {
                return false;
            }
        }
    }
    //定义自增的方法
    public long incrementAndGet(){
        long oldvalue ;
        long newValue;
        do {
            oldvalue = value;
            newValue = oldvalue+1;
        }while ( !compareAndSwap(oldvalue, newValue) );
        return newValue;
    }
}

CAS 实现原子操作背后有一个假设: 共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过.

实际上这种假设不一定总是成立.如有共享变量 count = 0

A 线程对 count 值修改为 10
B 线程对 count 值修改为 20
C 线程对 count 值修改为 0

当前线程看到 count 变量的值现在是 0,现在是否认为 count 变量的值没有被其他线程更新呢? 这种结果是否能够接受??
这就是 CAS 中的 ABA 问题,即共享变量经历了 A->B->A 的更新.

是否能够接收 ABA 问题跟实现的算法有关.
如果想要规避 ABA 问题,可以为共享变量引入一个修订号(时间戳), 每次修改共享变量时,相应的修订号就会增加 1.
ABA 变量更新过程变量: [A,0] ->[B,1]->[A,2], 每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过.
AtomicStampedReference 类就是基于这种思想产生的.

1.6 原子变量类

原子变量类基于CAS实现的, 当对共享变量进行 read-modify-write 更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量的 read-modify-write 更新操作是指当前操作不是一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++. 由于volatile只能保证可见性,无法保障原子性, 原子变量类内部就是借助一个 Volatile 变量, 并且保障了该变量的 read-modify-write 操作的原子性, 有时把原子变量类看作增强的 volatile 变量.

原子变量类有 12 个,如:

分组 原子变量类
基础数据型 AtomicInteger, AtomicLong, AtomicBoolean
数组型 AtomicIntegerArray, AtomicLongArray,AtomicReferenceArray
字段更新器 AtomicIntegerFieldUpdater,
AtomicLongFieldUpdater, AtomicReferenceFieldUpdater
引用型 AtomicReference, AtomicStampedReference, AtomicMarkableReference

1.6.1 AtomicLong

package com.sevattal.atomics.atomiclong;
import java.util.concurrent.atomic.AtomicLong;
/**
 * 使用原子变量类定义一个计数器
 * 该计数器,在整个程序中都能使用,并且所有的地方都使用这一个计数器,这个计数器可以设计为单例
 * sevattal
 */
public class Indicator {
    //构造方法私有化
    private Indicator(){}
    //定义一个私有的本类静态的对象
    private static final Indicator INSTANCE = new Indicator();
    //3)提供一个公共静态方法返回该类唯一实例
    public static Indicator getInstance(){
        return INSTANCE;
    }
    //使用原子变量类保存请求总数,成功数,失败数
    private final AtomicLong requestCount = new AtomicLong(0); //记录请求总数
    private final AtomicLong successCount = new AtomicLong(0); //处理成功总数
    private final AtomicLong failureCount = new AtomicLong(0); //处理失败总数
    //有新的请求
    public void newRequestReceive(){
        requestCount.incrementAndGet();
    }
    //处理成功
    public void requestProcessSuccess(){
        successCount.incrementAndGet();
    }
    //处理失败
    public void requestProcessFailure(){
        failureCount.incrementAndGet();
    }
    //查看总数,成功数,失败数
    public long getRequestCount(){
        return requestCount.get();
    }
    public long getSuccessCount(){
        return successCount.get();
    }
    public long getFailureCount(){
        return failureCount.get();
    }
}

package com.sevattal.atomics.atomiclong;
import java.util.Random;
/**
 * 模拟服务器的请求总数, 处理成功数,处理失败数
 * sevattal
 */
public class Test {
    public static void main(String[] args) {
        //通过线程模拟请求,在实际应用中可以在 ServletFilter 中调用 Indicator 计数器的相关方法
        for (int i = 0; i < 10000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //每个线程就是一个请求,请求总数要加 1
                    Indicator.getInstance().newRequestReceive();
                    int num = new Random().nextInt();
                    if ( num % 2 == 0 ){ // 偶数模拟成功
                        Indicator.getInstance().requestProcessSuccess();
                    }else { //处理失败
                        Indicator.getInstance().requestProcessFailure();
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印结果
        System.out.println( Indicator.getInstance().getRequestCount()); //总的请求数
        System.out.println( Indicator.getInstance().getSuccessCount()); //成功数
        System.out.println( Indicator.getInstance().getFailureCount()); //失败数
    }
}

1.6.2 AtomicIntegerArray

原子更新数组

package com.sevattal.atomics.atomicarray;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
 * AtomicIntegerArray 的基本操作
 * 原子更新数组
 * sevattal
 */
public class Test {
    public static void main(String[] args) {
        //1)创建一个指定长度的原子数组
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
        System.out.println( atomicIntegerArray ); //[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
        //2)返回指定位置的元素
        System.out.println( atomicIntegerArray.get(0)); //0
        System.out.println( atomicIntegerArray.get(1)); //0
        //3)设置指定位置的元素
        atomicIntegerArray.set(0, 10);
        //在设置数组元素的新值时, 同时返回数组元素的旧值
        System.out.println( atomicIntegerArray.getAndSet(1, 11) ); //0
        System.out.println( atomicIntegerArray ); //[10, 11, 0, 0, 0, 0, 0, 0, 0, 0]
        //4)修改数组元素的值,把数组元素加上某个值
        System.out.println( atomicIntegerArray.addAndGet(0, 22) ); //32
        System.out.println( atomicIntegerArray.getAndAdd(1, 33)); //11
        System.out.println( atomicIntegerArray ); //[32, 44, 0, 0, 0, 0, 0, 0, 0, 0]
        //5)CAS 操作
        //如果数组中索引值为 0 的元素的值是 32 , 就修改为 222
        System.out.println( atomicIntegerArray.compareAndSet(0, 32, 222)); //true
        System.out.println( atomicIntegerArray ); //[222, 44, 0, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.compareAndSet(1, 11, 333)); //false
        System.out.println(atomicIntegerArray);
        //6)自增/自减
        System.out.println( atomicIntegerArray.incrementAndGet(0) ); //223, 相当于前缀
        System.out.println( atomicIntegerArray.getAndIncrement(1)); //44, 相当于后缀
        System.out.println( atomicIntegerArray ); //[223, 45, 0, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.decrementAndGet(2)); //-1
        System.out.println( atomicIntegerArray); //[223, 45, -1, 0, 0, 0, 0, 0, 0, 0]
        System.out.println( atomicIntegerArray.getAndDecrement(3)); //0
        System.out.println( atomicIntegerArray ); //[223, 45, -1, -1, 0, 0, 0, 0, 0, 0]
    }
}

package com.sevattal.atomics.atomicarray;
import java.util.concurrent.atomic.AtomicIntegerArray;
/**
 * 在多线程中使用 AtomicIntegerArray 原子数组
 * sevattal
 */
public class Test02 {
    //定义原子数组
    static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
    public static void main(String[] args) {
        //定义线程数组
        Thread[] threads = new Thread[10];
        //给线程数组元素赋值
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new AddThread();
        }
        //开启子线程
        for (Thread thread : threads) {
            thread.start();
        }
        //在主线程中查看自增完以后原子数组中的各个元素的值,在主线程中需要在所有子线程都执行完后再查看
        //把所有的子线程合并到当前主线程中
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println( atomicIntegerArray );
    }
    //定义一个线程类,在线程类中修改原子数组
    static class AddThread extends Thread{
        @Override
        public void run() {
            //把原子数组的每个元素自增 1000 次
            for (int j = 0; j < 100000; j++) {
                for (int i = 0; i < atomicIntegerArray.length(); i++) {
                    atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
                }
            }
    /* for (int i = 0; i < 10000; i++) {
    atomicIntegerArray.getAndIncrement(i % atomicIntegerArray.length());
    }*/
        }
    }
}

1.6.2 AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater 可以对原子整数字段进行更新,要求:

  1. 字符必须使用 volatile 修饰,使线程之间可见

  2. 只能是实例变量,不能是静态变量,也不能使用 final 修饰

    package com.sevattal.atomics.atominintegerfiled;
    /**

    • 使用 AtomicIntegerFieldUpdater 更新的字段必须使用 volatile 修饰
    • sevattal
      */
      public class User {
      int id;
      volatile int age;
      public User(int id, int age) {
      this.id = id;
      this.age = age;
      }
      @Override
      public String toString() {
      return “User{“ +
      “id=” + id +
      “, age=” + age +
      ‘}’;
      }
      }

    package com.sevattal.atomics.atominintegerfiled;
    import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
    /**

    • 线程类, *
    • sevattal
      */
      public class SubThread extends Thread {
      private User user; //要更新的 User 对象
      //创建 AtomicIntegerFieldUpdater 更新器
      private AtomicIntegerFieldUpdater updater = AtomicIntegerFieldUpdater.newUpdater(User.class, “age”);
      public SubThread(User user) {
      this.user = user;
      }
      @Override
      public void run() {
      //在子线程中对 user 对象的 age 字段自增 10 次
      for (int i = 0; i < 10; i++) {
      System.out.println( updater.getAndIncrement(user));
      }
      }
      }

    package com.sevattal.atomics.atominintegerfiled;
    /**

    • sevattal
      */
      public class Test {
      public static void main(String[] args) {
      User user = new User(1234, 10);
      //开启 10 个线程
      for (int i = 0; i < 10; i++) {
      new SubThread(user).start();
      }
      try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.println( user );
      }
      }

1.6.3 AtomicReference

可以原子读写一个对象

package com.sevattal.atomics.atomicreference;
import java.util.Random;
import java.util.concurrent.atomic.AtomicReference;
/**
 * 使用 AtomicReference 原子读写一个对象
 * sevattal
 */
public class Test01 {
    //创建一个 AtomicReference 对象
    static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
    public static void main(String[] args) throws InterruptedException {
        //创建 100 个线程修改字符串
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(20));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (atomicReference.compareAndSet("abc","def")){
                        System.out.println(Thread.currentThread().getName() + "把字符串 abc 更改为 def");
                    }
                }
            }).start();
        }
        //再创建 100 个线程
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(20));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (atomicReference.compareAndSet("def","abc")){
                        System.out.println(Thread.currentThread().getName() + "把字符串 还原为 abc");
                    }
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(atomicReference.get());
    }
}

package com.sevattal.atomics.atomicreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
 * 演示 AtomicReference 可能会出现 CAS 的 ABA 问题
 * sevattal
 */
public class Test02 {
    private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
    public static void main(String[] args) throws InterruptedException {
        //创建第一个线程,先把 abc 字符串改为"def",再把字符串还原为 abc
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicReference.compareAndSet("abc", "def");
                System.out.println(Thread.currentThread().getName() + "--" +
                        atomicReference.get());
                atomicReference.compareAndSet("def", "abc");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println( atomicReference.compareAndSet("abc", "ghg"));
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println( atomicReference.get());
    }
}

package com.sevattal.atomics.atomicreference;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
 * AtomicStampedReference 原子类可以解决 CAS 中的 ABA 问题
 * 在 AtomicStampedReference 原子类中有一个整数标记值 stamp, 每次执行 CAS 操作时,需要对比它的版本,即比较 stamp 的值
 * sevattal
 */
public class Test03 {
    // private static AtomicReference<String> atomicReference = new AtomicReference<>("abc");
    //定义 AtomicStampedReference 引用操作"abc"字符串,指定初始化版本号为 0
    private static AtomicStampedReference<String> stampedReference = new
            AtomicStampedReference<>("abc", 0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                stampedReference.compareAndSet("abc", "def", stampedReference.getStamp(), stampedReference.getStamp()+1);
                System.out.println(Thread.currentThread().getName() + "--" +stampedReference.getReference());
                stampedReference.compareAndSet("def", "abc", stampedReference.getStamp(), stampedReference.getStamp()+1);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = stampedReference.getStamp(); //获得版本号
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println( stampedReference.compareAndSet("abc", "ggg", stamp, stamp+1));
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println( stampedReference.getReference() );
    }
}
Contents
  1. 1. Java 线程基础 3
    1. 1.1. 1 线程同步
      1. 1.1.1. 1.1 线程同步机制简介
      2. 1.1.2. 1.2 锁概述
        1. 1.1.2.1. 1.2.1 锁的作用
        2. 1.1.2.2. 1.2.2 锁相关的概念
          1. 1.1.2.2.1. 1)可重入性
          2. 1.1.2.2.2. 2)锁的争用与调度
          3. 1.1.2.2.3. 3)锁的粒度
      3. 1.1.3. 1.3 内部锁:synchronized 关键字
        1. 1.1.3.1. 1.3.1 synchronized 同步代码块
        2. 1.1.3.2. 1.3.2 同步方法
        3. 1.1.3.3. 1.3.3 脏读
        4. 1.1.3.4. 1.3.4 线程出现异常会自动释放锁
        5. 1.1.3.5. 1.3.5 死锁
      4. 1.1.4. 1.4 轻量级同步机制:volative 关键字
        1. 1.1.4.1. 1.4.1 volatile 的作用
        2. 1.1.4.2. volatile 与 synchronized 比较
        3. 1.1.4.3. 1.4.2 volatile 非原子特性
        4. 1.1.4.4. 1.4.3 常用原子类进行自增自减操作
      5. 1.1.5. 1.5 CAS
      6. 1.1.6. 1.6 原子变量类
        1. 1.1.6.1. 1.6.1 AtomicLong
        2. 1.1.6.2. 1.6.2 AtomicIntegerArray
        3. 1.1.6.3. 1.6.2 AtomicIntegerFieldUpdater
        4. 1.1.6.4. 1.6.3 AtomicReference
|