Java入门12(多线程)

多线程

线程的实现方式

  1. 继承 Thread 类:一旦继承了 Thread 类,就不能再继承其他类了,可拓展性差
  2. 实现 Runnable 接口:仍然可以继承其他类,可拓展性较好
  3. 使用线程池

继承Thread 类

​ 不能通过线程对象调用 run() 方法,需要通过 t1.start() 方法,使线程进入到就绪状态,只要进入到就绪状态的线程才有机会被JVM调度选中

// 这是一个简单的栗子
public class StudentThread extends Thread{
    public StudentThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            System.out.println("This is a test thread!");
            System.out.println(this.getName());
        }
    }
}
// 启动类
public static void main(String[] args) {
    Thread t1 = new StudentThread();
    // 不能通过线程对象调用run()方法
    // 通过 t1.start() 方法,使线程进入到就绪状态,只要进入到就绪状态的线程才有机会被JVM调度选中
    t1.start();
}

实现 Runable 接口

​ 实现方式需要借助 Thread 类的构造函数,才能完成线程对象的实例化

// 介还是一个简单的栗子
public class StudentThreadRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            System.out.println("This is a test thread!");
            System.out.println(Thread.currentThread().getName());
        }
    }
}
// 启动类
public static void main(String[] args) {
    // 实现方式需要借助 Thread 类的构造函数,才能完成线程对象的实例化
    StudentThreadRunnable studentThreadRunnable = new StudentThreadRunnable();
    Thread t01 = new Thread(studentThreadRunnable);
    t01.setName("robot010");
    t01.start();
}

匿名内部类实现

​ 在类中直接书写一个当前类的子类,这个类默认不需要提供名称,类名由JVM临时分配

public static void main(String[] args) {
    Thread t01 = new Thread(){
        @Override
        public void run() {
            for (int i = 0; i < 2; i++) {
                System.out.println("This is a test thread!");
            }
            System.out.println(Thread.currentThread().getName()); // 线程名
            System.out.println(this.getClass().getName()); // 匿名线程类类名
        }
    };
    t01.start();
}

线程的休眠(sleep方法)

​ sleep方法,会使当前线程暂停运行指定时间,单位为毫秒(ms),其他线程可以在sleep时间内,获取JVM的调度资源

// 这是一个计时器
public class TimeCount implements Runnable{
    @Override
    public void run() {
        int count = 0;
        while(true){
            System.out.println(count);
            count++;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
// 测试类
public static void main(String[] args) {
    System.out.println("这是main方法运行的时候,开启的主线程~~~");
    TimeCount timeCount = new TimeCount();
    Thread timeThread = new Thread(timeCount);
    System.out.println("开启计时器");
    timeThread.start();

    System.out.println("主线程即将休眠>>>>>>>>>>>");
    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println(">>>>>>>>>>>主线程休眠结束~~~~~");
}

线程的加入(join方法)

​ 被 join 的线程会等待 join 的线程运行结束之后,才能继续运行自己的代码

public static void main(String[] args) {
    Thread thread01 = new Thread(){
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println("This is thread-01!");
            }
        }
    };
    thread01.start();
    try {
        thread01.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Thread thread02 = new Thread(){
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println("This is thread-02!");
            }
        }
    };
    thread02.start();
}
// thread02 会等待 thread01 完全跑完,才会开始自己的线程

线程的优先级(priority方法)

​ 优先级高的线程会有更大的几率竞争到JVM的调度资源,但是高优先级并不代表绝对,充满玄学✨

public static void main(String[] args) {
    Thread thread01 = new Thread(){
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println("This is thread-01! " + Thread.currentThread().getPriority());
            }
        }
    };
    Thread thread02 = new Thread(){
        @Override
        public void run(){
            for (int i = 0; i < 10; i++) {
                System.out.println("This is thread-02! " + Thread.currentThread().getPriority());
            }
        }
    };
    thread01.setPriority(1);
    thread02.setPriority(10);
    // 尽管thread02优先级高于thread01,但是也有可能
    thread01.start();
    thread02.start();
}

线程的让步(yield方法)

​ 立刻让出JVM的调度资源,并且重新参与到竞争中

public static void main(String[] args) {
    Thread thread01 = new Thread(){
        @Override
        public void run(){
            for (int i = 1; i <= 10; i++) {
                System.out.println("This is thread-01! " + i);
            }
        }
    };
    Thread thread02 = new Thread(){
        @Override
        public void run(){
            for (int i = 1; i <= 10; i++) {
                System.out.println("This is thread-02! " + i);
                Thread.yield();
            }
        }
    };
    thread01.start();
    thread02.start();
}

守护线程(Deamon)

​ 会在其他非守护线程都运行结束之后,自身停止运行,(GC垃圾回收机制就是一个典型的守护线程)

public static void main(String[] args) {
    Thread thread01 = new Thread(){
        @Override
        public void run(){
            int times = 0;
            while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("time pass " + ++times + "second");
            }
        }
    };
    Thread thread02 = new Thread(){
        @Override
        public void run(){
            for (int i = 1; i <= 10; i++) {
                System.out.println("This is thread-02! " + i);
            }
        }
    };
    // 将t1设置为守护线程
    thread01.setDaemon(true);
    thread01.start();
    thread02.start();
    // 延长主线程运行,便于观察结果
    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println("main thread end \\(-_-)/");
}

线程同步

数据操作的原子性

​ 具有原子性的操作,不会被其他线程打断,类似(a++)的操作是不具备原子性的,因此很容易在多线程场景中出现误差

synchronized 悲观锁(互斥性)

优缺点:保证了数据在多线程场景下的安全(保证线程安全),牺牲的是效率,锁的获取和释放,其他线程被阻塞都会额外消耗性能

同步对象:被多个线程所竞争的资源对象叫做同步对象

核心作用: 确保线程在持有锁的期间内,其他线程无法操作和修改指定数据(同步对象)

​ 每一个同步对象都会持有一把线程锁,当线程运行到synchronized 修饰的方法或代码时,线程会自动获取当前同步对象的线程锁,在synchronized 修饰的方法或代码块运行结束后,该线程会自动释放此线程锁,在持有线程锁的这段时间里,其他线程是无法执行synchronized 所修饰的代码块的,其他线程会被阻塞在synchronized 代码块之外,直到这把锁被释放。。。

// synchronized 的两种写法:
// 1. 写在方法之前,修饰整个方法
public synchronized Ticket getTicket(){
    // 取票
    Ticket ticketTmp = null;
    if(!tickets.isEmpty()){
        ticketTmp = tickets.removeLast();
    }
    return ticketTmp;
}
// 2. 代码块,修饰代码块所包含的部分
public Ticket getTicket(){
    // 取票
    synchronized(this){
        Ticket ticketTmp = null;
        if(!tickets.isEmpty()){
            ticketTmp = tickets.removeLast();
        }
        return ticketTmp;
    }
}

线程死锁

💥一个线程可以持有多个不同的同步对象的锁!!!当两个线程同时想要持有相同一把锁时,就会产生死锁现象

wait notify notifyAll

​ 这三个方法并不是通过线程调用,而是通过同步对象第哦啊用,所有对象都可以当作是同步对象,这三个方法是在Object类中定义的

方法 作用
wait 让占用当前同步对象锁的线程进行等待,并且释放锁,直到被唤醒时才会被恢复
notify 通知一个在当前同步对象上等待的线程 进行唤醒,让其重新竞争锁,并运行代码
notifyAll 通知在当前同步对象上等待的所有线程,进行唤醒

wait和 sleep 的区别

wait sleep
对象 同步对象 Thread类
是否加锁 加锁 不加锁
是否释放资源 释放资源 不释放资源
唤醒条件 notify唤醒 时间到,或者interrupt唤醒

生产者消费者模型

  1. 生产者线程负责提供用户请求
  2. 消费者线程负责处理用户请求
// 模拟生产者消费者
// 球(ball) => 数据
public class Ball {
    private int ballNum;
}
// 篮子(basket) => 数据容器
public class Basket {
    // 指针,指向最新放入的球
    private int index = 0;
    // 容器
    private Ball[] balls = new Ball[5];
    // 存操作
    public synchronized void ballPut(Ball ballTmp){
        // 不断的判断容器是否放满,如果线程被唤醒,需要再此判断,如果此时容器还是满的,应该继续等待
        while(index == balls.length){
            System.out.println("The basket is full~~");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 进程能推进到这里,说明容器并没有满,可以进行存操作
        balls[index++] = ballTmp;
        System.out.println("ballPut : " + index);
        // 此时可以唤醒等待的取操作线程
        this.notifyAll();
    }
    
    // 取操作
    public synchronized void ballGet(){
        // 不断的判断容器是否为空,如果线程被唤醒,需要再此判断,如果此时容器还是空的,应该继续等待
        while(index == 0){
            System.out.println("The basket is empty~~");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 进程能推进到这里,说明容器没有被取空,可以进行取操作
        System.out.println("ballGet : " + (index - 1));
        balls[--index] = null;
        // 此时可以唤醒等待的存操作线程
        this.notifyAll();
    }
}
// 放球(ballPut) => 生产者
public class Producer implements Runnable{
    // 为了保证生产者和消费者绑定的是同一个容器
    Basket basket;
    // 构造方法,方便传参
    public Producer(Basket basket){
        this.basket = basket;
    }
    // 存操作
    @Override
    public void run() {
        // 模拟一共放10个球
        for (int i = 0; i < 20; i++) {
            Ball ballTmp = new Ball(i);
            basket.ballPut(ballTmp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
// 拿球(ballOut) => 消费者
public class Consumer implements Runnable{
    // 为了保证生产者和消费者绑定的是同一个容器
    Basket basket;
    // 构造方法,方便传参
    public Consumer(Basket basket){
        this.basket = basket;
    }
    // 取操作
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            basket.ballGet();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
// 测试启动类
public static void main(String[] args) {
    // 实例化容器
    Basket basket = new Basket();
    // 实例化生产者,消费者
    Producer producer = new Producer(basket);
    Consumer consumer = new Consumer(basket);
    // 生产者线程
    new Thread(producer).start();
    // 等待,容器被装满
    try {
        Thread.sleep(8000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    // 消费者线程
    new Thread(consumer).start();
}

线程池和自定义线程池

优点:节约线程资源,让线程池中的消费者线程不断的去执行任务

  1. 需要准备一个存放业务的容器
  2. 只启动固定数量的消费线程
  3. 生产者线程负责向任务容器中提交任务
  4. 程序开始时,任务容器为空,所有消费者线程都处于 wait 状态
  5. 直到有一个生产者向任务容器中投入了一个任务,那么就会有一个消费者线程被 notify 唤醒,并执行此任务
  6. 任务执行完毕之后,该消费者线程会重新处于 wait 状态,等待下一次任务到来
  7. ⭐(线程复用)整个任务流程,都不需要创建新的线程,而是对已经存在的线程循环使用

模拟线程池基础功能实现(没有实现自动扩容)

public class ThreadPool {
    // 线程池的大小
    int threadPoolSize;
    // 任务容器中的任务 --> 线程
    LinkedList<Runnable> tasks = new LinkedList<>();
    // 提供一个 add 方法,用于向任务容器中添加任务
    public void add(Runnable r){
        synchronized (tasks){
            tasks.add(r);
            // 唤醒消费者线程,取走任务容器中的任务
            tasks.notifyAll();
        }
    }
    //构造函数--初始化线程池
    public ThreadPool() {
        // 初始化线程池大小
        threadPoolSize = 10;
        // 定义10个消费者线程,并且将其实例化后start
        synchronized (tasks){
            for (int i = 0; i < threadPoolSize; i++) {
                new TaskConsumeThread("ThreadRobot-0" + i+1).start();
            }
        }
    }

    // 内部类--消费者线程
    class TaskConsumeThread extends Thread{
        // 重写 Thread 类构造方法,方便给线程定义 name 属性
        public TaskConsumeThread(String name) {
            super(name);
        }
        // 需要被执行的任务
        Runnable task;
        // 重写 run 方法
        @Override
        public void run() {
            System.out.println(this.getName() + " running!");
            while (true){
                // 获取任务容器 tasks 的锁,避免多个消费者线程同时操作任务容器,保证任务容器的线程安全
                synchronized (tasks){
                    // 只要任务容器为空,需要 wait 方法,让所有消费者线程等待
                    while (tasks.isEmpty()){
                        try {
                            tasks.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 脱离循环能运行到这里,说明任务容器不为空!
                    // 当前消费者从任务容器中取出任务,并存再自己的 task 引用中
                    task = tasks.removeLast();
                    // 唤醒添加任务的线程
                    tasks.notifyAll();
                    System.out.println(this.getName() + " get task!");
                    // 启动任务线程
                    new Thread(task).start();
                }
            }
        }
    }
}
// 启动测试类01
public static void main(String[] args) {
    ThreadPool threadPool = new ThreadPool();
    Scanner sc = new Scanner(System.in);
    String tmp;
    while (true) {
        tmp = sc.nextLine();
        Thread task = new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        threadPool.add(task);
    }
}

// 启动测试类02
public static void main(String[] args) {
    ThreadPool threadPool = new ThreadPool();
    int sleepTime = 1000;
    // 任务计数
    int[] count = {0};
    while (true) {
        Runnable runnableTmp = new Runnable() {
            @Override
            public void run() {
                // 通过访问外部数组地址,来实现控制
                System.out.println("执行任务" + (++count[0]));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        threadPool.add(runnableTmp);
        try {
            Thread.sleep(sleepTime);
            sleepTime = sleepTime > 100 ? sleepTime - 100 : sleepTime;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Java自带的线程池使用

public static void main(String[] args) {
    // 参数含义:
    //  corePoolSize : 线程池中初始线程数量
    //  maximumPoolSize : 线程池中最大线程数量
    //  keepAliveTime : 临时线程的最大存活时间
    //  TimeUnit.SECONDS : 存活时间的单位
    //  new LinkedBlockingDeque<Runnable>() : 存放任务的任务容器
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 15, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    // 创建测试任务线程
    Runnable runnableTmp = new Runnable() {
        @Override
        public void run() {
            System.out.println("This is a testRunnable!");
        }
    };
    // 将测试任务抛入线程池即可
    threadPoolExecutor.execute(runnableTmp);
}

数据库连接池

​ 如果每有一个用户使用连接,就新建一个连接的话,创建连接和关闭连接的过程是非常消耗性能的,且单一数据库支持的连接总数是有上限的,如果短时间内并发量过大,数据库的连接总数就会被消耗光,后续线程发起的数据库连接就会失败,那么连接池的作用就是:它会在使用之前,就创建好一定数量的连接,如果有线程需要使用连接,就从连接池中借用,而不是重新创建连接,使用完此连接之后,再将此连接归还给连接池,整个过程中,连接池中的连接都不会被关闭,而是被重复使用。

模拟数据库连接池实现

public class ConnectionPool {
    List<Connection> connections = new ArrayList<>();
    int size;
    // 准备构造函数
    public ConnectionPool(int size) {
        this.size = size;
        init();
    }
    // 初始化连接池
    public void init(){
        try {
            Class.forName("com.mysql.jdbc.Driver");
            for (int i = 0; i < size; i++) {
                Connection c = DriverManager.getConnection("jdbc:mysql://localhost:3306/iweb?characterEncoding=utf8","root","123456");
                connections.add(c);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 获取一个数据库连接
    public synchronized Connection getConnection(){
        while (connections.isEmpty()){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return connections.remove(0);
    }
    // 归还一个数据库连接
    public synchronized void returnConnection(Connection connectionTmp){
        connections.add(connectionTmp);
        this.notifyAll();
    }
}
// 测试启动类
public class Application {
    public static void main(String[] args) {
        ConnectionPool connectionPool = new ConnectionPool(3);
        // 创建100个线程用于测试
        for (int i = 0; i < 100; i++) {
            new WorkingThread("Thread0" + i,connectionPool).start();
        }
    }

    static class WorkingThread extends Thread{
        private ConnectionPool connectionPool;

        public WorkingThread(String name,ConnectionPool cp){
            super(name);
            this.connectionPool = cp;
        }

        @Override
        public void run() {
            Connection connection = connectionPool.getConnection();
            System.out.println(this.getName() + " get the mysql connection!");
            try(Statement statement = connection.createStatement()){
                Thread.sleep(1000);
                statement.execute("select * from user");
            }catch (Exception e){
                e.printStackTrace();
            }
            connectionPool.returnConnection(connection);
        }
    }
}

Java内存模型

内存分配(💩令人头大的理论知识)

数据区域 概括 线程共享
程序计数器 当前线程所执行的字节码的行号显示器,(每一个线程私有,不共享程序计数器) ✖️
虚拟机栈 为Java的方法执行创建栈帧存储局部变量,操作数栈,运行时常量池的引用,方法返回地址等信息(存放的是方法本身所对应的栈帧,线程调用方法的时候会生成一个栈帧) ✖️
本地方法栈 与虚拟机栈类似,为NATIVE方法 ✖️
对象本身(堆是被所有线程共享) ✔️
方法区 存放虚拟机已经加载的类信息,常量,静态变量,即使编译后的代码等数据(大多数类信息都在方法区中) ✔️
运行时常量池 方法区的一部分,存放编译器生成的字面量和符号引用(在类或者接口被加载到 JVM 之后,接口或者类所对应的常量池就被创建出来了) ✔️
直接内存 被分配在堆外的内存,性能高,不受到java堆的大小限制 ✔️

堆和栈的区别

  1. 堆内存负责存放对象,线程共享,栈内存是线程所私有的,生命周期和线程相同
  2. 无论在何时何地创建对象,对象总是存储在堆内存中,并且占内存空间包含着对这个对象引用栈内存空间值包含方法的原始数据,局部变量以及堆空间中对象的内存地址
  3. 堆中的对象可以全局访问,栈内存中的空间被线程所私有
  4. JVM 中 栈内存的结构管理相对简单(先进后出),堆内存的管理相对负责,需要考虑对象状态,分为新生代和老年代
  5. 栈内存生命周期短暂,堆内存伴随整个应用程序的生命周期
  6. 抛出异常的方式,如果线程请求的栈深度大于虚拟机所允许的深度,则抛出 StackOverFlow 异常,而堆内存会在特定场景下抛出OutOfMemoryException
public class TestMemory {
    // 当我们执行程序时,JVM 会默认将所有的运行时类加载到内存空间中,并且从方法区中读取 TestMemory 的类的内容
    // 找到 main 方法,执行生成栈帧,压入栈空间中
    public static void main(String[] args) {
        // 创建了一个基本数据类型的局部变量
        // 该变量 i 被存放在 main 方法所对应的栈内存空间中(局部变量表中)
        int i = 1;
        // 该对象被存放在堆内存中,并且栈空间中存储了 obj 这个引用
        Object obj = new Object();
        // 与上一行类似
        TestMemory mem = new TestMemory();
        // 主线程调用了 foo 方法,会生成一个 foo 方法的栈帧,压入到栈中
        mem.foo(obj);
        // main方法运行结束, main 方法对应的栈帧出战,所有的局部变量伴随着栈帧的出栈也一并销毁
    }
    // 通过方法的参数传递,param 这个局部变量接受到了 main 方法传递的对象,对 obj 的引用
    private void foo(Object param){
        // 调用了 Object 类的 toString 方法,由于最底层是 new String
        // 在堆中生成了这个字符串对象,引用由 str 保存
        String str = param.toString();
        // 利用系统自带打印流,向控制台输出了这个字符串
        System.out.println(str);
        // 方法运行结束 foo 方法的栈帧出栈 param 和 str 两个局部变量会随着栈帧出栈而销毁
    }
}

volatile 乐观锁

⭐volatile 保证不同线程对共享变量操作的可见性,当一个线程修改了 volatile 所修饰的变量,当修改写回到主内存的时候,其他线程会立刻看到最新的值

💩滥用volatile也会导致一系列问题:volatile 的 mesi 缓存一致性协议,需要不断的从主内存嗅探和 cas 自旋(cas算法)会导致大量的无效交互,会导致总线风暴出现

单例模式(单实例)

三大特征

  1. 私有的当前类的成员变量
  2. 私有构造方法
  3. 公有的 Get 方法用于获取实例
// 懒汉模式 => 延迟加载;如果使用者不调用getInstance方法,就不会实例化对象
// 模式下无法保证线程安全(要加锁)
public class SingleTon {
    private static SingleTon singleTon;
    // 确保在其他类不能通过构造方法实例化对象
    private SingleTon(){ }
    public synchronized static SingleTon getInstance(){
        if(singleTon == null){
            singleTon = new SingleTon();
        }
        return singleTon;
    }
}

// 饿汉模式 => 在类加载的时候,就完成对象的创建
// 优点:线程安全    缺点:不具备延迟加载的特性
public class SingleTon {
    private static SingleTon singleTon = new SingleTon();
    // 确保在其他类不能通过构造方法实例化对象
    private SingleTon(){ }
    public synchronized static SingleTon getInstance(){
        return singleTon;
    }
}


双重检查锁

public class SingleTon {
    // 代码在执行的过程中,真实的执行顺序,可能回合预期的不一致
    // 以创建对象为例:分配内存空间 => 调用构造方法 => 将对象的地址返回给引用
    // JVM 会因为指令优化,在开发者不知情的情况下,调整指令的执行顺序
    // 所以可能会变成这样:分配内存空间 => 将对象的地址返回给引用 => 调用构造方法
    // 这个时候,就需要通过 volatile 去禁止指令重排序
    private volatile static SingleTon singleTon = null;
    private SingleTon(){ }
    public static SingleTon getInstance(){
        // 外层的判断可以避免重复获得synchronized锁,提高效率
        if(singleTon == null){
            synchronized (SingleTon.class){
                // 内层的判断,通过锁的机制,保证对象只被实例化一次
                if(singleTon == null){
                    singleTon = new SingleTon();
                }
            }
        }
        return singleTon;
    }
}

一致性协议

JMM(java memory model)模型

Java虚拟机规范中所定义的一种内存模型,设计JMM是为了屏蔽掉计算底层内存架构不同的区别,JMM有如下规定:

  1. 所有的共享变量(成员变量 => 堆,静态变量 => 方法区),都存储于主内存
  2. 所有的线程都存在自己的工作内存,线程的工作内存保留了被线程使用的变量的工作副本
  3. 线程对变量的所有操作(读写操作),都必须在工作内存中完成,而不能直接在主内存中完成
  4. 不同线程之间也不能直接访问对方工作内存中的变量,线程之间变量的值需要借助主内存中转来完成

乐观锁(volatile)和悲观锁(synchronized , ReentrantLock)的区别

  1. 乐观锁,乐观的认为所有线程都只会对竞争数据做读,不做写入修改
  2. 悲观锁,悲观的认为所有线程都会对竞争数据做写入修改
  3. 使用范围区别:乐观锁只能修饰成员变量和类变量,而悲观锁可以修饰方法和代码块
  4. 乐观锁保证数据的可见性,但是无法保证其原子性(多线程并发写入的时候,无法保证线程安全)
  5. 悲观锁具有互斥机制,因此可以保证其原子性
  6. 乐观锁具有禁止指令重排序的特性,可以解决单例模式下双重检查对象初始化代码执行乱序的问题
  7. 乐观锁虽然不能保证其原子性,但是如果是对一个共享变量进行多个线程的值读取,而没有写入操作,就可以代替悲观锁,在这种情况下,乐观锁性能更加

CAS 机制(compare and swap)

public final int getAndIncrement(){
    while(true){
        // 每一次从内存中读取数据,然后将此数据+1之后的结果
        // 进行 CAS 操作 如果成功则返回,否则就重试到成功为止
        int current = get();
        int next = current + 1;
        if(compareAndSet(current,next)){
            retrun current;
        }
    }
}
posted @ 2023-07-12 12:04  te9uila  阅读(130)  评论(0)    收藏  举报