JavaSE-多线程(一)

多线程

基本概念

  1. 什么是程序

    编写的代码就是程序

  2. 什么是进程

    程序一旦运行起来,就要在内存中分配空间,产生一个进程。是系统资源分配的单位。任务管理器中可以查看进程。

  3. 什么是线程

    进程中相对独立的执行单元,线程启动顺序具有不确定性。是CPU 执行调度的单位。

  4. 在java 中有多少个线程

    main(主线程) 、GC (垃圾回收线程)、异常处理线程(抛出异常会影响main 线程的执行)。main() 称为主线程为系统的入口,用于执行整一个程序

  5. 什么是并发

    在一个进程中,如果开辟了多个线程,形成的运行由CPU 安排调度,先后顺序不能人为干预。同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxVTtqMH-1653577273965)(imgclip.png "imgclip.png")]

  1. 进程和线程的区别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6D1ob4C-1653577273972)(imgclip_1.png "imgclip_1.png")]

创建线程

继承Thread 类创建线程

创建线程与启动线程

类之间的关系:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KIzC4rF-1653577324960)(imgclip.png "imgclip.png")]

类构造器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNlvnCqD-1653577324961)(imgclip_1.png "imgclip_1.png")]

继承Thread 类创建线程

继承线程类 Thread,有了多线程的能力就可以在同一进程中共享资源。

所以这个类称为线程类(实例化的对象就是线程对象)。

public class CreateThread_m_1 extends Thread {
    // 执行的任务写到run 方法中

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("TestThread-------------:" + i);
        }
    }
}

执行任务 run()

重写run 方法添加任务。

主线程中启动其他线程 start()

public class Demo {
    public static void main(String[] args) { // 主线程
        
        for (int i = 0; i < 10; i++) {
            System.out.println("main1----------:" + i);
        }
        // 创建其他线程
        CreateThread_m_1 createThread_m_1 = new CreateThread_m_1();
        // 执行到这一步该程序中存在3 个线程: 主线程 其他线程 垃圾回收线程
        // createThread_m_1.run();// 直接执行任务,直接占用主线程

        // 启动子线程
        createThread_m_1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main----------:" + i);
        }
    }
}

注意:不能直接run 线程任务,直接调用run 方法无法启动新线程,如果直接run 体现的是至上而下地执行任务,没有新的线程启动,start()方法才会启动新线程。线程启动后Java虚拟机调用此线程的run方法,同样还是遵照程序的自上而下执行。对于main 方法而言也遵照程序的自上而下执行。

输出结果:

main1----------:0
main1----------:1
main1----------:2
main1----------:3
main1----------:4
main1----------:5
main1----------:6
main1----------:7
main1----------:8
main1----------:9
main----------:0
TestThread-------------:0
TestThread-------------:1
main----------:1
TestThread-------------:2
main----------:2
main----------:3
TestThread-------------:3
main----------:4
TestThread-------------:4
main----------:5
TestThread-------------:5
main----------:6
TestThread-------------:6
main----------:7
TestThread-------------:7
main----------:8
TestThread-------------:8
main----------:9
TestThread-------------:9

线程名称

给线程设定名称

自定义线程:

public class CreateThread_m_1 extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            // 接名字
            System.out.println(this.getName() + "-------------:" + i);
        }
    }
}

主线程:

public class TestThread {
    public static void main(String[] args) { // 主线程
        // 获取当前线程并设置
        Thread.currentThread().setName("this is main thread");
        CreateThread_m_1 createThread_m_1 = new CreateThread_m_1();
        // 设置线程
        createThread_m_1.setName("this is child thread");
        createThread_m_1.start();
        for (int i = 0; i < 10; i++) {
            // 获取当前线程名称
            System.out.println(Thread.currentThread().getName() + "----------:" + i);
        }
    }
}

或者直接调用有参构造器

子线程:

public class CreateThread_m_1 extends Thread {

   public CreateThread_m_1(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "-------------:" + i);
        }
    }
}

主线程:

public class TestThread {
    public static void main(String[] args) {
        Thread.currentThread().setName("this is main thread");
        CreateThread_m_1 createThread_m_1 = new CreateThread_m_1("this is child thread");

        // 启动子线程
        createThread_m_1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "----------:" + i);
        }
    }
}

如果使用Thread创建线程必须使用 static 修饰符修饰的变量参与资源共享

10 张火车票3 个窗口售卖,售完即止。

子线程:

public class TrainTestObj extends Thread {
    // 资源共享
    private static int ticketNum = 10;

    public TrainTestObj(String name) {
        super(name);
    }

    @Override
    public void run() {
        while (ticketNum > 0) {
            System.out.println(this.getName() + "窗口卖了1张票,剩余" + (--ticketNum));
        }
    }
}

主线程:

public class TrainTest {
    public static void main(String[] args) {
        TrainTestObj trainTestObj = new TrainTestObj("3");
        TrainTestObj trainTestObj2 = new TrainTestObj("2");
        TrainTestObj trainTestObj3 = new TrainTestObj("1");
        // 启动线程
        trainTestObj.start();
        trainTestObj2.start();
        trainTestObj3.start();
    }
}

输出结果发现了问题:这就和多线程的调度与资源共享有关。如何一步步执行线程任务?这就需要线程加锁。

CPU执行哪个线程的代码具有不确定性

2窗口卖了1张票,剩余8
1窗口卖了1张票,剩余7
1窗口卖了1张票,剩余5
3窗口卖了1张票,剩余9
3窗口卖了1张票,剩余3
3窗口卖了1张票,剩余2
3窗口卖了1张票,剩余1
3窗口卖了1张票,剩余0
1窗口卖了1张票,剩余4
2窗口卖了1张票,剩余6

实现Runnable接口创建线程

创建线程与启动线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kE3yjlaF-1653577385991)(imgclip.png "imgclip.png")]

继承Runnable 类创建线程

注意这个类是一个接口类,无法重载父类构造器

子线程:

public class CreateThread_m_2 implements Runnable {
    // 接口类无法定义构造器,所以无法通过this 获取线程名称
    // 所以使用Thread.currentThread().getName() 获取

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---------:" + i);
        }
    }
}

主线程:

需要与Thread 进行关联

public class TestThread2 {
    public static void main(String[] args) {
        Thread.currentThread().setName("this is main thread");
        // 创建子线程
        CreateThread_m_2 createThread_m_2 = new CreateThread_m_2();
        // 关联线程
        Thread thread = new Thread(createThread_m_2,"this is child thread");
        thread.start();
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "---------:" + i);
        }
    }
}

购买火车票

class Thread_Runnable implements Runnable {
    private int num = 10;

    @Override
    public void run() {
        while (num > 0) {
            System.out.println(Thread.currentThread().getName() + "买了一张票,剩余:" + (--num));
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread_Runnable thread_runnable = new Thread_Runnable();
        new Thread(thread_runnable, "窗口1").start();
        new Thread(thread_runnable, "窗口2").start();
        new Thread(thread_runnable, "窗口3").start();
    }
}

使用内部类:

public class Test {
    private int num = 10;
    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了一张票,剩余:" + (--num));
                }
            }
        }, "窗口1");
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了一张票,剩余:" + (--num));
                }
            }
        }, "窗口2");
        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                while (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "买了一张票,剩余:" + (--num));
                }
            }
        }, "窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

输出结果:

窗口2买了一张票,剩余:9
窗口1买了一张票,剩余:8
窗口1买了一张票,剩余:6
窗口1买了一张票,剩余:5
窗口1买了一张票,剩余:4
窗口1买了一张票,剩余:3
窗口3买了一张票,剩余:2
窗口2买了一张票,剩余:7
窗口3买了一张票,剩余:0
窗口1买了一张票,剩余:1
使用lambda表达式

为什么可以使用lambda 表达式实现Runnable 接口类

原因就是Runnable 接口类是一个函数型接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-deQFes1q-1653577469557)(imgclip.png "imgclip.png")]

public class Test {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        // ()->{...} lambda 表达式
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTick();
            }
        }, "窗口1").start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTick();
            }
        }, "窗口2").start();
        new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                ticket.saleTick();
            }
        }, "窗口3").start();

    }
}

class Ticket {
    private int num = 50;
    // synchronized 本质就是锁+ 队列
    public /*synchronized*/ void saleTick() {
        if (num > 0)
            System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余:" + (--num));
    }
}

实现Callable接口创建线程

第一种和第二种实现run 方法的缺点

  1. 没有返回值。

  2. 不能抛出异常。

所以经过上述分析的缺点,引入了方式三。

Runnable 和Callable 有什么不同?

主要区别是Callable的 call() 方法可以返回值和抛出异常,而Runnable的run()方法没有这些功能。Callable可以返回装载有计算结果的Future对象。

实现Callable 接口类创建线程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCk2TDaC-1653577511590)(imgclip.png "imgclip.png")]

JDK1.5 为了解决上述缺点而引入…

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ADruIV1h-1653577511591)(imgclip_1.png "imgclip_1.png")]

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Thread.sleep(3000);  // 异步测试,睡眠3 秒
        return new Random().nextInt(10);  // 生成一个10 以内的随机数
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Test test = new Test();
        FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(test);
        // 中间转换器
        Thread thread = new Thread(integerFutureTask);
        thread.start();

        // 获取线程返回结果,以及线程情况
        System.out.println("线程是否执行完成" + integerFutureTask.isDone());
        System.out.println(integerFutureTask.get());  //等待了3 秒再获取,说明get 方法是一个阻塞方法
        System.out.println("线程是否执行完成" + integerFutureTask.isDone());
    }
}

缺点:线程创建比较麻烦

购买火车票

class Thread_Callable implements Callable<Boolean> {
    private int num = 10;


    @Override
    public Boolean call() throws Exception {
        while (num > 0) System.out.println(Thread.currentThread().getName() + "买了一张票,剩余:" + (--num));
        return true;
    }
}

public class Demo {
    public static void main(String[] args) {
        Thread_Callable thread_callable = new Thread_Callable();
        FutureTask<Boolean> booleanFutureTask = new FutureTask<Boolean>(thread_callable);
        new Thread(booleanFutureTask, "窗口1").start();
        new Thread(booleanFutureTask, "窗口2").start();
        new Thread(booleanFutureTask, "窗口3").start();
    }
}

输出结果:

窗口2买了一张票,剩余:9
窗口2买了一张票,剩余:8
窗口2买了一张票,剩余:7
窗口2买了一张票,剩余:6
窗口2买了一张票,剩余:5
窗口2买了一张票,剩余:4
窗口2买了一张票,剩余:3
窗口2买了一张票,剩余:2
窗口2买了一张票,剩余:1
窗口2买了一张票,剩余:0

多线程不安全

购买或称票不安全

//----------------------------------------------------------------------- Thread 版本
class Ticket extends Thread {
    private static int tickNumber = 10;

    public Ticket(String name) {
        super(name);
    }

    @Override
    public void run() {
        // 人抢票直至抢完为止
        while (tickNumber > 0) {
            System.out.println(this.getName() + "抢了一张票,剩余:" + (--tickNumber));
        }
    }
}

public class Demo{
    public static void main(String[] args) {
        new Ticket("窗口1").start();
        new Ticket("窗口2").start();
        new Ticket("窗口3").start();
    }
}

//----------------------------------------------------------------------- Runnable 版本
class Ticket implements Runnable {
    private static int tickNumber = 10;

    @Override
    public void run() {
        // 人抢票直至抢完为止
        while (tickNumber > 0) {
            System.out.println(Thread.currentThread().getName() + "抢了一张票,剩余:" + (--tickNumber));
        }
    }
}

public class Demo{
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket,"窗口1").start();
        new Thread(ticket,"窗口2").start();
        new Thread(ticket,"窗口3").start();        
//        new Thread(()->{},"窗口1").start();
    }
}
//----------------------------------------------------------------------- Callable 版本
class Ticket implements Callable {
    private static int tickNumber = 10;

    @Override
    public String call() throws Exception {
        while (tickNumber > 0) {
            System.out.println(Thread.currentThread().getName() + "抢了一张票,剩余:" + (--tickNumber));
        }
        return Thread.currentThread().getName();
    }
}

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Ticket ticket = new Ticket();
        FutureTask<String> stringFutureTask = new FutureTask<String>(ticket);
        new Thread(stringFutureTask, "窗口1").start();
        new Thread(stringFutureTask, "窗口2").start();
        new Thread(stringFutureTask, "窗口3").start();
    }
}

银行取钱业务不安全

class Account {
    String name;
    int money;

    public Account(String name, int money) {
        this.name = name;
        this.money = money;
    }
}

class TakeMoney extends Thread {
    Account account;  // 账户
    int takeMoney;  // 取多少钱
    int money;  // 手里多少钱

    public TakeMoney(Account account, int takeMoney, String name) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    @Override
    public void run() {
        if (account.money - takeMoney < 0) {
            System.out.println(this.getName() + "取不了钱!");
            return;
        }
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money = account.money - takeMoney;
        money = money + takeMoney;
        System.out.println(account.name + "余额为:" + account.money);
        System.out.println(this.getName() + "手里有:" + money + "钱");
    }
}

public class Demo {
    public static void main(String[] args) {
        Account account = new Account("基金", 100);
        new TakeMoney(account, 50, "你").start();
        new TakeMoney(account, 50, "女朋友").start();
    }
}

集合不安全

public class Demo extends Thread {
    static List<String> list = new ArrayList<String>();

    public Demo(String name) {
        super(name);
    }

    @Override
    public void run() {
        list.add(this.getName());
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Demo("线程1").start();
        }
        System.out.println(list.size());  // 605
    }
}
public class Demo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            },"线程1").start();
        }
        System.out.println(list.size());  // 566
    }
}

CopyOnWriteArrayList 线程安全。

对高并发的理解

并发编程

  1. 并发

CPU 一核,模拟出多条线程,天下武功唯快不破,快速交替。

单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了

  1. 并行

CPU 多核,多线程同时执行,线程池。

public class Test{
	public static void main(String[] args){
		System.out.println(Runtime.getRuntime().availableProcessors());  // 4
	}
}

本质就是充分利用CPU 资源。

线程的调度与生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vpRRSPX5-1653577880287)(imgclip.png "imgclip.png")]

什么是时间片、单核 / 多核CPU如何执行任务?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3iga2lFm-1653577880289)(imgclip_1.png "imgclip_1.png")]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5zIvH8qy-1653577880291)(imgclip_2.png "imgclip_2.png")]

CPU分配给各个程序的时间,使各个程序从表面上看是同时进行的,而不会造成CPU资源浪费。

对于单核cpu来说,同一时间片只能执行一个线程。因为时间片时间特别短,感受到的就是“同时”执行多个线程实际上这个多线程只是一种假象。

对于多核cpu来说此时才是真正意义上的同一时间片执行多个线程。

什么是线程的上下文调度

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

线程生命周期

  1. 新生状态
  • 使用new 关键字建立线程对象后,该线程对象就处于新生状态

  • 处于新生状态的线程有自己的内存空间,通过start 进入就绪状态

  1. 就绪状态
  • 处于就绪状态的线程具备了运行条件,但还没有分配到CPU 中,处于线程就绪队列,等待系统为其分配CPU

  • 当系统选定一个执行的线程后,他就后从就绪状态进入运行状态,这个动作叫做CPU 的调度

  1. 运行状态
  • 在运行状态的线程执行自己run 方法中的代码,直到等待某资源阻塞或完成任务而死亡

  • 如果在给定的时间片内没有执行结束,就会被系统给换下来,回到等待执行状态

  1. 阻塞状态
  • 处于运行状态的线程在某种情况下,如执行了sleep(睡眠)方法,或者等待IO设备等资源,将让CPU 暂停对自己的运行,进入阻塞状态

  • 在阻塞状态的线程不能进入就绪状态。只有当引起阻塞的原因被消除时,如睡眠时间已到,或等待IO 设备空闲下来,线程才会重新进入就绪状态,重新到就绪队列中等待,被系统选中后从原来的位置开始继续运行

  1. 死亡状态
  • 死亡状态是线程最后的一个阶段,死亡原因存在3 个。一、线程执行玩所有的任务。二、线程强制被终止(调用了stop方法)。三、线程抛出未捕获的异常

并发编程三大核心问题?

并发编程三大核心基础理论:原子性、可见性、有序性

原子性

什么是原子性

原子(atomic)本意是“不能被进一步分割的最小粒子”,而原子操作(atomic operation)意 为“不可被中断的一个或一系列操作”。

原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行。(提供了互斥访问,在同一时刻只有一个线程进行访问)

原子性问题的产生的原因

当某个线程获得CPU的时间片之后就获取了CPU的执行权,就可以执行任务,当时间片耗尽之后还没执行完任务,就会失去CPU使用权。
进而本任务会暂时停止执行。多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。

public class Demo {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(100000);
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                add();
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(num);  // 99996
    }

    private static void add() {
        num++;
    }
}

产生这个问题的原因:

i++ 的执行实际上这个操作不是原子性的,因为 i++ 会被拆分成以下三个步骤执行(这样的步骤不是虚拟的,而是真实情况就是这么执行的)

  1. 读取i的值

  2. 计算+1 的结果

  3. 将+1 的结果赋值给i 变量

在多线程中会发生资源争抢问题,例如:

线程A 从主内存中读取i 的值并计算+1 结果,此时计算结果还未写回主内存中就被线程B 抢去了资源从主内存中读取i (0),线程B开始对 i 值进行+1操作; A线程将累加后的结果赋值给 count 结果为 1,B 线程将累加后的结果赋值给 count 结果为 1;A 线程将结果 count =1 刷回到主内存,B 线程将结果 count =1 刷回到主内存。这样就造成了最终i 不为10000 的原因。

解决方法:

  1. 加锁
// synchronized 

public class Demo {
    private static int num = 0;

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(100000);
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                add();
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(num);  // 100000
    }

    private static synchronized void add() {
        num++;
    }
//    private static void add() {
//        synchronized (Demo.class) {
//            num++;
//        }
//    }
}
// ReenterantLock()

public class Demo {
    private static int num = 0;
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(100000);
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                add();
                countDownLatch.countDown();
            }).start();
        }
        countDownLatch.await();
        System.out.println(num);  // 100000
    }

    private static void add() {
        try {
            lock.lock();
            num++;
        } finally {
            lock.unlock();
        }

    }
}
  1. CAS 操作
public class Demo {
    private static AtomicInteger aint = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(100000);
        for (int i = 0; i < 100000; i++) {
            new Thread(() -> {
                add();
                countDownLatch.countDown();
            }).start();
            /*countDownLatch.countDown(); 注意此操作不能写在此处*/
        }
        countDownLatch.await();  
        System.out.println(aint);  // 100000
    }

    private static void add() {
        aint.getAndIncrement();
    }
}

注意为什么不需要加volatile? 因为synchronized 保证可见性、原子性。由于i++ 不是原子操作(i=i+1)。由于依赖性不会进行指令重排,所以不需要volatile 修饰。

可见性

什么是可见性

可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到,我们称为可见性。

可见性问题的产生的原因

JMM 中的约定:

  1. 所有的共享变量都存储于主内存,这里所说的变量指的是实例变量和类变量,不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

  2. 在多线程情况下,线程上锁之前将共享资源从主内存中加载到线程工作内存中。

  3. 在多线程情况下,线程解锁前必须更新主内存中的数据

  4. 线程对变量的所有的操作(读,写【写缓冲区】)都必须在工作内存中完成,而不能直接读写主内存中的变量。

public class Demo {
    private static boolean bol = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            bol = true;
        }).start();
        while (!bol) {
        }
        System.out.println("跳出循环了");
    }
}

是否会输出”跳出循环了“?不会,缓存不能及时刷新到主内存就是导致可见性问题产生的根本原因。

解决方法:

  1. 加锁
public class Demo {
    private static boolean bol = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            bol = true;
        }).start();
        while (!bol) {
            synchronized (Demo.class){}
        }
        System.out.println("跳出循环了");
    }
}
  1. volatile
public class Demo {
    private volatile static boolean bol = false;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            bol = true;
        }).start();
        while (!bol) {
        }
        System.out.println("跳出循环了");
    }
}

有序性

什么是可见性

有序性:程序执行的顺序按照代码的先后顺序执行。

有序性问题产生的原因

  1. 编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。考虑依赖性

  2. 指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism,ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应 机器指令的执行顺序。

  3. 内存系统的重排序。由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上 去可能是在乱序执行。

有序性的案例最常见的就是 DCL了(double check lock)就是单例模式中的双重检查锁功能。

public class Demo {
    private int num;
    private static Demo demo;

    public Demo() {
        num = 1;
    }

    public static Demo getInstance() {
        if (demo == null) {
            synchronized (Demo.class) {
                if (demo == null) demo = new Demo();
                // 1. 为Demo实例分配空间、初始化实例属性默认值
                // 2. 调用构造器,初始化实例、实例属性
                // 3. 返回地址给引用
                // 多线程过程中会可能发生指令重排...还分配空间就返回地址给引用导致空指针异常。
            }
        }
        return demo;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                getInstance();
            }).start();
        }
    }
}

解决方法:

  1. volatile
public class Demo {
    private int num;
    private volatile static Demo demo;

    public Demo() {
        num = 1;
    }

    public static Demo getInstance() {
        if (demo == null) {
            synchronized (Demo.class) {
                if (demo == null) demo = new Demo();
            }
        }
        return demo;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                getInstance();
            }).start();
        }
    }
}

**为什么加上volatile 就可以避免指令重排呢?**见下篇。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值