Java入门12(多线程)

这篇具有很好参考价值的文章主要介绍了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 两个局部变量会随着栈帧出栈而销毁
    }
}

Java入门12(多线程)

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. 不同线程之间也不能直接访问对方工作内存中的变量,线程之间变量的值需要借助主内存中转来完成
    Java入门12(多线程)

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

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

CAS 机制(compare and swap)

Java入门12(多线程)文章来源地址https://www.toymoban.com/news/detail-549382.html

public final int getAndIncrement(){
    while(true){
        // 每一次从内存中读取数据,然后将此数据+1之后的结果
        // 进行 CAS 操作 如果成功则返回,否则就重试到成功为止
        int current = get();
        int next = current + 1;
        if(compareAndSet(current,next)){
            retrun current;
        }
    }
}

到了这里,关于Java入门12(多线程)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • java基础入门-12-【API(算法,lambda表达式,练习)】

    ​ 数据结构是数据存储的方式,算法是数据计算的方式。 ​ 也叫做顺序查找 ​ 说明:顺序查找适合于存储结构为数组或者链表。 基本思想 基本思想 :顺序查找也称为线形查找,属于无序查找算法。从数据结构线的一端开始,顺序扫描,依次将遍历到的结点与要查找的值

    2024年02月05日
    浏览(56)
  • Java并发编程学习笔记(一)线程的入门与创建

    认识 程序由指令和数据组成,简单来说,进程可以视为程序的一个实例 大部分程序可以同时运行多个实例进程,例如记事本、画图、浏览器等 少部分程序只能同时运行一个实例进程,例如QQ音乐、网易云音乐等 一个进程可以分为多个线程,线程为最小调度单位,进程则是作

    2024年02月16日
    浏览(54)
  • java筑基--基础不牢,地动山摇,线程基础入门到进阶

    1.1 线程相关概念 1.1.1 程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合简单的说:就是我们写的代码. 1.1.2 进程 进程是指运行中的程序,比如我们使用QQ,就启动了一个进程,操作系统就会为该进程分配内存空间。当我们使用迅雷,又启动了一个进程,操作系

    2024年02月16日
    浏览(40)
  • 从零开始学习 Java:简单易懂的入门指南之线程池(三十六)

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。线程对象在不同的时期有不同的状态。那么Java中的线程存在哪几种状态呢?Java中的线程 状态被定义在了java.lang.Thread.State枚举类中,State枚举类的源码如下: 通过源码我们可以看到Ja

    2024年02月08日
    浏览(53)
  • 从零开始学习 Java:简单易懂的入门指南之线程同步(三十五)

    1.1卖票【应用】 案例需求 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票 实现步骤 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100; 在SellTicket类中重写run()方法实现卖票,代码步骤如下

    2024年02月08日
    浏览(49)
  • 从零开始学习 Java:简单易懂的入门指南之多线程(三十四)

    1.1简单了解多线程 是指从软件或者硬件上实现多个线程并发执行的技术。 具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。 1.2并发和并行 并行:在同一时刻,有多个指令在多个CPU上同时执行。 并发:在同一时刻,有多个指令在单个CPU上交

    2024年02月08日
    浏览(68)
  • 解决安卓12限制32个线程

    Android 12及以上用户在使用Termux时,有时会显示 [Process completed (signal 9) - press Enter] ,这是因为Android 12的PhantomProcesskiller限制了应用的子进程,最大允许应用有32个子进程。 这里以ColorOS 12.1为例(其他系统操作略有出入) 开启开发者模式 打开设置 打开“关于手机” 打开“版本

    2024年02月12日
    浏览(32)
  • 12.网络爬虫—线程队列详讲(实战演示)

    前言 : 🏘️🏘️个人简介:以山河作礼。 🎖️🎖️:Python领域新星创作者,CSDN实力新星认证 📝​📝第一篇文章《1.认识网络爬虫》获得 全站热榜第一,python领域热榜第一 。 🧾 🧾第四篇文章《4.网络爬虫—Post请求(实战演示)》 全站热榜第八 。 🧾 🧾第八篇文章《8.网

    2023年04月18日
    浏览(42)
  • QT基础篇(12)QT5多线程

    在任何一门语言中,多线程都是一个相对其他方面比较重要的点,这里面的知识体系很庞大,同步和异步之间的处理方式,以及IO多路复用等等各种进行性能优化的方面,在往上层一点我们不可能一直进行系统层次的调用,这样太费时间也太麻烦,就到设计模式这里,比如反

    2024年01月22日
    浏览(41)
  • 12分钟从Executor自顶向下彻底搞懂线程池

    上篇文章 13分钟聊聊并发包中常用同步组件并手写一个自定义同步组件 聊到并发包中常用的同步组件,并且还手把手实现了自定义的同步组件 本篇文章来聊聊并发包下的另一个核心-线程池 阅读本文大概12分钟 通读本篇文章前先来看看几个问题,看看你是否以及理解线程池

    2024年02月09日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包