IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java并发编程(四) -> 正文阅读

[Java知识库]Java并发编程(四)

Java并发编程(四)

成员变量与静态变量是否线程安全

  • 如果他们没有共享,则线程安全
  • 如果他们共享了,根据他们的状态是否能够改变,有分为两种情况
    • 如果只有读操作,则线程安全
    • 如果有读写操作,则这段代码处于临界区,则需要考虑线程的安全,如下代码
class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            { //临界区, 会产生竞态条件
                method2();
                method3();
            } //临界区
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }
}

当多个线程同时执行时,如下模拟

static final int THREAD_NUMBER = 5;
    static final int LOOP_NUMBER = 2000;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
  • 情况一:size和add的数量不匹配。
    • 分析如下:
      • 加入线程1首先进入list.add(“1”),把1加入0的位置,此时刚准备要加入的时候CPU时间片用完了,也就是说list数组里面还是没有东西,线程2分配到了CPU时间片,这时候线程而也执行了添加操作, 此时的线程2还是加入到list的0的位置,因为线程1还没有加进入。此时线程2把size设置为了1.
      • 线程1的时间片到了,开始执行add,由于CPU是在刚准备加入的时候停止的,此时线程1要加入的位置还是index = 0,那么线程1执行add操作加入到index = 0的位置,然后把size设置为2,实际上list里面才只有1个数据。
  • 情况二:
    • 在上述情况的基础上,由于两个线程都添加了元素,所以当两个线程都执行remove()方法时,会发生如下错误,只有一个数据,但却remove()两次
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at com.wjl.code01.ThreadUnsafe.method3(Test09.java:39)
	at com.wjl.code01.ThreadUnsafe.method1(Test09.java:29)
	at com.wjl.code01.Test09.lambda$main$0(Test09.java:16)
	at java.lang.Thread.run(Thread.java:750)
  • 将list修改为局部变量,对方法进行了改进,就不会出现线程的安全问题了
class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    private void method2(ArrayList<String> list) {
        list.add("1");
    }
    private void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

上述代码分析
list 是局部变量,每个线程调用时会创建其不同实例,没有共享
而 method2 的参数是从 method1 中传递过来的,与 method1 中引用同一个对象,method3 的参数分析与 method2 相同

局部变量是否线程安全

  • 局部变量是线程安全的,但局部变量引用的对象则未必
    • 如果该对象没有逃离方法的作用访问,它是线程安全的,如果该对象逃离方法的作用范围,需要考虑线程安全
  • 再将上述的method2 和 method3 的方法修改为 public 会不会代理线程安全问题?
    • 情况1:有其他线程调用method2()与method()3,线程不安全
    • 情况2:在 情况1 的基础上,为 ThreadSafe 类添加子类,子类覆盖 method2 或 method3 方法
    class ThreadSafe {
        public final void method1(int loopNumber) {
            ArrayList<String> list = new ArrayList<>();
            for (int i = 0; i < loopNumber; i++) {
                method2(list);
                method3(list);
            }
        }
        public void method2(ArrayList<String> list) {
            list.add("1");
        }
        public void method3(ArrayList<String> list) {
            list.remove(0);
        }
    }
    
    class ThreadSafeSubClass extends ThreadSafe{
        @Override
        public void method3(ArrayList<String> list) {
            new Thread(() -> {
                list.remove(0);
            }).start();
        }
    }
    

由于此时子类继承了ThreadSafe,导致list也被共享了,作为参数传递到子类的方法中可以被不同的线程调用,所以线程不安全

常见的线程安全类

  • 常见线程安全类String,Integer,StringBuffer,Random,Vector,Hashtable,java.util.concurrent 包下的类
  • 这里说它们是线程安全的是指,多个线程调用它们同一个实例的某个方法时,是线程安全的。也可以理解为,他们的每个方法是原子的,但是注意他们多个方法的组合不是原子的
	Hashtable table = new Hashtable();
	new Thread(()->{
 		table.put("key", "value1");
	}).start();
	new Thread(()->{
 		table.put("key", "value2");
	}).start();

线程安全类的方法组合

	Hashtable table = new Hashtable();
	// 两个线程同时调用
	if( table.get("key") == null) {
 		table.put("key", value);
	}
  • 上述多个线程调用是否安全? 不安全,因为线程1和线程2有机会同时进入if条件中,此时就有一个线程的值会被另一个线程put的值覆盖
    ****

不可变线程的安全性

  • String、Integer 等都是不可变类,因为其内部的状态不可以改变,因此它们的方法都是线程安全的。但是String 有 replace,substring 等方法【可以】改变值啊,那么这些方法又是如何保证线程安全的呢?从String的源码我们可以看到,到最后是**new String(value, beginIndex, subLen);**来生成了新的对象,所以操作的不是同一个对象,所以也就不存在线程不安全一说了。
 public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

线程安全实例分析

  1. 普通成员变量
public class MyServlet extends HttpServlet {
	 // 是否安全?
	 //不安全,因为doGet方法可能操作修改map
	 Map<String,Object> map = new HashMap<>();
	 // 是否安全?
	 //是安全的,对于String的操作是安全的,因为内部其实是new出来的
	 String S1 = "...";
	 // 是否安全?同上
	 final String S2 = "...";
	 // 是否安全?不安全,因为同一个D1有可能被不同线程修改
	 Date D1 = new Date();
	 // 是否安全?
	 // 不安全,final只能保证D2只能执行一处地方,但是不能保证里面的属性是不可变的
	 final Date D2 = new Date();
 
	 public void doGet(HttpServletRequest request, HttpServletResponse 			response) {
	 	// 使用上述变量
	 }
}
  1. 方法中的成员变量
public class MyServlet extends HttpServlet {
	 // 是否安全?不安全,因为是成员变量,有可能被共享
	 private UserService userService = new UserServiceImpl();
 
	 public void doGet(HttpServletRequest request, 	HttpServletResponse response) {
		 userService.update(...);
  	 }
}
	public class UserServiceImpl implements UserService {
	 // 记录调用次数
	 //线程安全,也是成员变量,那么多个线程就有可能共享到
	 private int count = 0;
 
	 public void update() {
	 // ...
	 count++;
	 }
}

黑马程序员多线程课程:JUC并发编程全套教程

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-04-24 09:15:04  更:2022-04-24 09:16:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:04:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码