二、Java内存模型与多线程安全

一、线程安全问题

  • 1.什么是线程安全问题:
    • 当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题;但是做读操作是不会发生数据冲突问题
  • 2.线程安全解决办法:
    • a)内置锁(synchronized)、b)显示锁(lock锁)
  • 3.为什么使用线程同步或使用锁能解决线程安全问题:
    • 将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。代码执行完成后释放锁,然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
  • 4.什么是多线程同步:
    • 当多个线程共享同一个资源,不会受到其他线程的干扰
  • 5.买票案例演示线程安全问题
    • 共享的变量count代表总共有100张票
    • 线程t1和t2同时对count执行–操作
    • 从打印结果我们可以看出,售票出现了异常(也就是多线程安全问题)
class SaleTicket implements Runnable {
    private int count = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            sale();
        }
    }

    private void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() +  " -> 当前售票:" + (100 - count + 1));
        }
        --count;
    }
}

public class App {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread(saleTicket,"t1线程");
        Thread t2 = new Thread(saleTicket,"t2线程");
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

二、内置锁(synchronized)

  • 1.Java的内置锁机制
    • Java提供了一种内置的锁机制来支持原子性;每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
  • 2.什么是互斥锁
    • 即线程A获取到锁后,线程B阻塞直到线程A释放锁,线程B才能获取到同一个锁;
    • 内置锁也是互斥锁,但是它会降低程序的效率
  • 3.synchronized的两种实现方法
    • a)同步方法
    • b)同步代码块

三、同步代码块

  • 1.相同Runnable对象的同步实现
    • 同步代码块的锁要使用同一个对象,如代码中的private Object oj = new Object();
class SaleTicket implements Runnable {
    private int count = 100;
    private Object oj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            sale();
        }
    }

    private void sale() {
        synchronized (oj) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + " -> 当前售票:" + (100 - count + 1));
            }
            --count;
        }
    }
}

public class App {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread(saleTicket, "t1线程");
        Thread t2 = new Thread(saleTicket, "t2线程");
        t1.start();
        t2.start();
    }
}
  • 2.不同Runnable对象的同步实现
    • t1和t2线程使用的是不同的Runnable对象
    • private static int count = 100;:为了保证操作是同一个count,加上了static关键字
    • private static Object oj = new Object();:为了保证使用的是同一个锁对象,加上了static关键字
class SaleTicket implements Runnable {
    private static int count = 100;
    private static Object oj = new Object();

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            sale();
        }
    }

    private void sale() {
        synchronized (oj) {
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + " -> 当前售票:" + (100 - count + 1));
            }
            --count;
        }
    }
}

public class App {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        SaleTicket saleTicket2 = new SaleTicket();
        Thread t1 = new Thread(saleTicket, "t1线程");
        Thread t2 = new Thread(saleTicket2, "t2线程");
        t1.start();
        t2.start();
    }
}

四、同步方法

  • 1.非静态同步方法
    • 非静态同步方法使用的是this锁,相当于synchronized (this)
class SaleTicket implements Runnable {
    private int count = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            sale();
        }
    }

    private synchronized void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + " -> 当前售票:" + (100 - count + 1));
        }
        --count;
    }
}

public class App {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread(saleTicket, "t1线程");
        Thread t2 = new Thread(saleTicket, "t2线程");
        t1.start();
        t2.start();
    }
}
  • 2.静态同步方法
    • 因为使用的是静态方法,所以count也必须修改为静态对象private static int count = 100;
    • 使用当前class字节码文件,相当于synchronized (ThreadDemo01.class)
class SaleTicket implements Runnable {
    private static int count = 100;

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(10);
            } catch (Exception e) {

            }
            sale();
        }
    }

    private static synchronized void sale() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + " -> 当前售票:" + (100 - count + 1));
        }
        --count;
    }
}

public class App {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread(saleTicket, "t1线程");
        Thread t2 = new Thread(saleTicket, "t2线程");
        t1.start();
        t2.start();
    }
}

五、多线程死锁

  • 1.什么是多线程死锁
    • 同步中嵌套同步,导致锁无法释放
  • 2.死锁的实例分析
    • 增加了一个flag,这个主要是用来控制t1和t2分别执行不同的逻辑
    • 对t1来说,flag=true,先获取oj锁,再获取this锁
    • 对t2来说,flag=false,先获取this锁,再获取oj锁
    • 在某一时刻t1获得了oj锁,没有获取到this锁;而t2获取了this锁,没有获取到oj锁,从而导致了死锁
class SaleTicket implements Runnable {
    private int count = 100;
    private Object oj = new Object();
    public boolean flag = true;

    @Override
    public void run() {
        if(flag){
            while (count > 0){
                synchronized (oj){
                    sale();
                }
            }
        }else {
            while (count > 0){
                sale();
            }
        }
    }

    private synchronized void sale() {
        synchronized (oj) {
            try {
                Thread.sleep(10);
            }catch (Exception e){

            }
            if (count > 0) {
                System.out.println(Thread.currentThread().getName() + " -> 当前售票:" + (100 - count + 1));
            }
            --count;
        }
    }
}

public class App {
    public static void main(String[] args) throws InterruptedException {
        SaleTicket saleTicket = new SaleTicket();
        Thread t1 = new Thread(saleTicket, "t1线程");
        Thread t2 = new Thread(saleTicket, "t2线程");
        t1.start();
        Thread.sleep(40);
        saleTicket.flag = false;
        t2.start();
    }
}

在这里插入图片描述

六、Threadlocal

  • 1.什么是Threadlocal
    • ThreadLocal提高一个线程的局部变量,访问某个线程拥有自己局部变量
    • 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
  • 2.ThreadLocal接口
    • a)void set(Object value):设置当前线程的线程局部变量的值
    • b)public Object get():返回当前线程所对应的线程局部变量
    • c)public void remove():将当前线程局部变量的值删除,目的是为了减少内存的占用(这个不是必要的,因为线程结束后会自动回收,这个只是加快内存回收的速度)
    • d)protected Object initialValue():回该线程局部变量的初始值,这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次
  • 3.Threadlocal使用
class Res {
    ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public Integer getNumber() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }
}

class MyThread extends Thread {
    private Res res;

    public MyThread(Res res) {
        this.res = res;
    }

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

public class App {
    public static void main(String[] args) {
        Res res = new Res();
        MyThread t1 = new MyThread(res);
        MyThread t2 = new MyThread(res);
        t1.start();
        t2.start();
    }
}

在这里插入图片描述

七、多线程三大特性

  • 1.原子性
    • 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
      • 一个很经典的例子就是银行账户转账问题:
      • 比如从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。这2个操作必须要具备原子性才能保证不出现一些意外的问题。
  • 2.可见性
    • 当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
      • 若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题
  • 3.有序性
    • 程序执行的顺序按照代码的先后顺序执行
      • 一般来说处理器为了提高程序运行效率,可能会对输入代码进行优化
      • 它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的
int a = 10;    //语句1
int r = 2;    //语句2
a = a + 3;    //语句3
r = a*a;     //语句4
//因为重排序,可能执行顺序为 2-1-3-4,1-3-2-4
//但绝不可能 2-1-4-3,因为这打破了依赖关系
//显然重排序对单线程运行是不会有任何问题
//而多线程就不一定了,所以我们在多线程编程时就得考虑这个问题了

八、Java内存模型

  • 1.什么是java内存模型
    • 共享内存模型指的就是Java内存模型(简称JMM),JMM决定一个线程对共享变量的写入时,能对另一个线程可见
  • 2.“内存模型”和“内存结构”的区别
    • 内存模型:jmm,多线程相关的
    • 内存结构:jvm,内存结构,如堆、栈、方法区等
  • 3.主内存与本地内存
    • JMM定义了线程和主内存之间的抽象关系
      • 主内存:共享变量,线程之间的共享变量存储在主内存(main memory)中
      • 本地内存:共享变量的副本,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本
  • 如下图
    • 多线程对count实现++的时候,并不是直接对共享变量count进行++
    • 而是先对自己的本地内存count进行++,然后再刷新到主内存中
      • a)起始的时候主内存count=0,这时候t1和t2线程同时执行
      • b)此时t1的本地内存count=1,t2的本地内存count=1
      • c)这时候t1和t2同时刷新到主内存中
      • d)此时的主内存count=1
        在这里插入图片描述

九、Volatile关键字

  • 1.什么是Volatile
    • 可见性也就是说一旦某个线程修改了该被volatile修饰的变量,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,可以立即获取修改之后的值
    • 在Java中为了加快程序的运行效率,对一些变量的操作通常是在该线程的寄存器或是CPU缓存上进行的,之后才会同步到主存中,而加了volatile修饰符的变量则是直接读写主内存
  • 2.实际开发项目何时使用Volatile
    • 只要是全局共享变量,全部都加上Volatile关键字
  • 3.验证案例
    • flag不加Volatile关键字,线程会一直运行,不会暂停
      • 因为flag是在主线程中修改为false的,这时候并不会同步到主内存中,子线程中本地内存的flag还是true
    • flag加Volatile关键字,3秒之后线程就停止了
class ThreadDemo004 extends Thread {
    public volatile boolean flag = true;

    @Override
    public void run() {
        System.out.println("线程开始...");
        while (flag) {

        }
        System.out.println("线程結束...");
    }

    public void setRuning(boolean flag) {
        this.flag = flag;
    }
}

public class App {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo004 threadDemo004 = new ThreadDemo004();
        threadDemo004.start();
        Thread.sleep(3000);
        threadDemo004.setRuning(false);
        System.out.println("flag已經改為false");
        Thread.sleep(1000);
        System.out.println("flag:" + threadDemo004.flag);
    }
}
  • 4.Volatile与Synchronized区别
    • volatile
      • 可以保证可见性,不能保证原子性(线程安全问题)
      • 禁止重排序
    • synchronized
      • 既可以保证可见性,还可以保证原子性
      • 没有禁止重排序
      • synchronized是阻塞式的,同一时刻保证只能有一个线程在访问

要注意volatile关键字是无法替代synchronized关键字的,因为volatile关键字无法保证操作的原子性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

无休止符

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值