学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • Java基础

  • Java集合

  • MySQL

  • Redis

  • JVM

  • 多线程

    • Java多线程-创建线程
    • Java多线程-启动线程
    • Java多线程-停止中断线程
    • Java多线程-生命周期
    • Java多线程-Thread和Object
    • Java多线程-线程属性
    • Java多线程-线程异常
    • Java多线程-线程安全
      • Java多线程08-线程安全
      • 线程安全问题案例
      • 死锁案例
      • 对象溢出问题
      • 考虑线程安全的情况
    • Java多线程-死锁问题和解决方案
  • 计算机网络

  • Spring

  • Kafka

  • Elasticsearch

  • Python

  • 面试专题

  • 知识库
  • 多线程
旭日
2023-03-31
目录

Java多线程-线程安全

# Java多线程08-线程安全

线程安全的定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。

# 线程安全问题案例

线程安全主要是两个问题导致的:

  • 数据争用:数据读写由于同时写,会造成错误数据
  • 竞争条件:即使不是同时写造成的错误数据,由于顺序原因依然会造成错误,例如写入前就去读。

演示两个线程对一个变量进行相加,结果少加的情况

public class MultiThreadsError implements Runnable {

    private static int index = 0;

    public static void main(String[] args) throws InterruptedException {
        MultiThreadsError multiThreadsError = new MultiThreadsError();
        Thread thread1 = new Thread(multiThreadsError);
        Thread thread2 = new Thread(multiThreadsError);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(index);
        System.out.println("程序执行完毕");
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            index++;
        }
    }
}

假设线程1拿到index,对其进行了++,但是在还没有把++的结果写回,线程2就拿到index,而这时候线程2拿到的index值还是++之前的值。

public class MultiThreadsError implements Runnable {

    private static int index = 0;

    public static void main(String[] args) throws InterruptedException {
        MultiThreadsError multiThreadsError = new MultiThreadsError();
        Thread thread1 = new Thread(multiThreadsError);
        Thread thread2 = new Thread(multiThreadsError);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(index);
        System.out.println("程序执行完毕");
    }

    @Override
    public void run() {
        add();
    }

    private synchronized void add() {
        for (int i = 0; i < 10000; i++) {
            index++;
        }
    }
}

# 死锁案例

现在创建两个线程,两个线程在分别拥有自己锁的情况下,尝试去获取另一个线程的锁,就会造成死锁。

public class DeadLock implements Runnable{

    private int flag = 1;

    private static final Object lock1 = new Object();

    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        DeadLock deadLock1 = new DeadLock();
        DeadLock deadLock2 = new DeadLock();
        deadLock2.flag = 2;
        Thread thread1 = new Thread(deadLock1);
        Thread thread2 = new Thread(deadLock2);
        thread1.start();
        thread2.start();
    }

    @Override
    public void run() {
        if (flag == 1) {
            System.out.println("flag = " + flag);
            synchronized (lock1) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lock2) {
                    System.out.println("获取到锁2");
                }
            }
        }
        if (flag == 2) {
            System.out.println("flag = " + flag);
            synchronized (lock2) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (lock1) {
                    System.out.println("获取到锁1");
                }
            }
        }
    }
}

image-20220906093320720

# 对象溢出问题

1、方法返回了一个private对象导致外部程序可以访问和修改该对象

public class MultiThreadPrivate {

    private Map<String, String> initMap;

    public MultiThreadPrivate () {
        initMap = new HashMap<>();
        initMap.put("1", "元素1");
        initMap.put("2", "元素2");
        initMap.put("3", "元素3");
    }

    public Map<String, String> getInitMap () {
        return initMap;
    }

    public static void main(String[] args) {
        MultiThreadPrivate threadPrivate = new MultiThreadPrivate();
        Map<String, String> outerMap = threadPrivate.getInitMap();
        System.out.println(outerMap.get("1"));
        outerMap.remove("1");
        System.out.println(outerMap.get("1"));
    }

}

这里就是get方法把private对象暴露给外部了,外部的程序就可以访问和修改该私有对象。

修正方案

通过返回副本的方式来解决。

public class MultiThreadPrivate {

    private Map<String, String> initMap;

    public MultiThreadPrivate () {
        initMap = new HashMap<>();
        initMap.put("1", "元素1");
        initMap.put("2", "元素2");
        initMap.put("3", "元素3");
    }

    public Map<String, String> getInitMap () {
        //return initMap;
        // 修正方案 使用副本代替原对象
        return new HashMap<>(initMap);
    }

    public static void main(String[] args) {
        MultiThreadPrivate threadPrivate = new MultiThreadPrivate();
        Map<String, String> outerMap = threadPrivate.getInitMap();
        System.out.println(outerMap.get("1"));
        outerMap.remove("1");
        System.out.println(threadPrivate.getInitMap().get("1"));
    }

}

2、还未完成初始化就把对象提供给外界

public class MultiThreadThis {

    static Point myPoint;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new PointMaker());
        thread.start();
        Thread.sleep(105);
        if (myPoint != null) {
            System.out.println(myPoint);
        }
    }
}

class Point {

    private final int x, y;

    public Point(int x, int y) throws InterruptedException {
        this.x = x;
        MultiThreadThis.myPoint = this;
        Thread.sleep(1000);
        this.y = y;
    }

    @Override
    public String toString() {
        return x + "," + y;
    }
}

class PointMaker implements Runnable {

    @Override
    public void run() {
        try {
            new Point(1, 1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

修正方案

避免在未初始化完毕就this赋值

3、隐式逸出 - 注册监听事件

public class MultiThreadObserve {
    int count;

    public MultiThreadObserve(MySource source) {
        source.registerListener(new EventListener() {
            @Override
            public void onEvent(Event e) {
                System.out.println("\n我得到的数字是" + count);
            }
        });
        for (int i = 0; i < 10000; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.print(i);
        }
        count = 100;
    }

    public static void main(String[] args) {
        MySource source = new MySource();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                source.eventCome(new Event() {
                });
            }
        }).start();
        MultiThreadObserve multiThreadObserve = new MultiThreadObserve(source);
    }

    static class MySource {

        private EventListener listener;

        void registerListener(EventListener eventListener) {
            this.listener = eventListener;
        }

        void eventCome(Event e) {
            if (listener != null) {
                listener.onEvent(e);
            } else {
                System.out.println("还未初始化完毕呢。。");
            }
        }

    }

    interface EventListener {

        void onEvent(Event e);
    }

    interface Event {

    }
}

4、构造函数中运行线程

public class MultiThreadConstruct {
    private Map<String, String> initMap;

    public MultiThreadConstruct() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                initMap = new HashMap<>();
                initMap.put("1", "哈哈");
                initMap.put("2", "嘿嘿");
                initMap.put("3", "嘻嘻");
            }
        }).start();
    }

    public Map<String, String> getInitMap() {
        return initMap;
    }

    public static void main(String[] args) {
        MultiThreadConstruct threadPrivate = new MultiThreadConstruct();
        Map<String, String> outerMap = threadPrivate.getInitMap();
        System.out.println(outerMap.get("1"));
    }

}

由于是子线程去创建对象,而主线程以为对象已经创建完毕,使用对象就会因为子线程对象没有创建完毕,导致空指针。

修正方案

避免在构造函数中运行线程。

# 考虑线程安全的情况

  • 访问共享的变量或资源,会有并发风险,比如对象的属性、静态变量、共享缓存、数据库等
  • 所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题:
  • 不同的数据之间存在捆绑关系的时候
  • 我们使用其他类的时候,如果对方没有声明自己是线程安全的,那么大概率会存在并发问题
#Java
上次更新: 2024/06/29, 15:13:44
Java多线程-线程异常
Java多线程-死锁问题和解决方案

← Java多线程-线程异常 Java多线程-死锁问题和解决方案→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式