当前位置: 首页 > news >正文

JUC并发编程系列详解篇五(线程基础理论进阶)

线程安全

什么是线程安全?当多线程运行了同一代码的时候,如果产生了不同的结果会怎么样?就好比如家里养的鸡下的蛋结果却孵出来一个老鹰,这怎么也显得不合适了,所以线程安全说白了就一句话,当多线程运行同一代码,不会产生不一样的结果。即代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。因为共享数据没有,那线程之间就互不影响。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来操作 不然中间过程可能会产生不可预制的结果。

举一个简单的例子,本来你在银行存有1000块钱,你要取1500块钱,你去银行办理两个业务,取钱和存钱,假如你的两个业务是同步进行的,这个时候是不是你的钱就取不出来了?因为你的银行了就只有1000块钱,你取不了1500块钱,但是你的钱却是可以存进去。钱取不了,是不是你去银行办理业务的这个进程就废掉了?因为没有完全运行成功嘛。

确保线程安全

通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。

确保多线程安全的方法:

1、对非安全的代码进行加锁控制
2、使用线程安全的类
3、多线程并发情况下,线程共享的变量改为方法级的局部变量

线程安全在三个方面体现:

原子性: 提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic, synchronized);
可见性: 一个线程对主内存的修改可以及时地被其他线程看到,(synchronized、 volatile);
有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱⽆无序,(happensbefore 原则)

共享数据

第一种:将共享数据封装到一个对象中,把这个共享数据所在的对象传递给不同的Runnable。

第二种:将这些Runnable对象作为某一个类的内部类,共享的数据作为外部类的成员变量,对共享数据的操作分配给外部类的方法来完成,以此实现对操作共享数据的互斥和通信,作为内部类的Runnable来操作外部类的方法,实现对数据的操作。

class shareData {
    private int x = 0;
    public synchronized void addX(){
        x++;
        System.out.println("X++:"+x);
    }
    public synchronized void subX(){
        x--;
        System.out.println("X--:"+x);
    }
}
public class ThreadsVisitData{
    public static shareData share = new shareData();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    share.addX();
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    share.subX();
                }
            }
        }).start();
    }
}

在这里插入图片描述

ThreadLocal

从名字可以看出这叫线程变量,意思是ThreadLocal中填充的的变量输入当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。

作用: 让某个需要用到的对象在线程间隔离(每个线程都有自己独立的对象)在任何方法中都可以轻松获取到该对象。

使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

在这里插入图片描述

场景举例1:每个线程需要一个独享的对象(通常是工具类如:SimpleDateFormat和Random)

/**
 * @description threadLocal用法1:SimpleDateFormat
 *              利用ThreadLocal,给每个线程分配自己的SimpleDateFormat对象,保证线程安全,高效率利用内存
 *
 *              使用场景:
 *              1.在ThreadLocal第一次get的时候把对象给初始化,重写initialValue()方法和返回值, 对象初始化时间可以由我们控制
 *              2.如果需要保存到ThreadLocal里的对象的生成时机不由我们随意控制,用ThreadLocal.set直接放到我们ThreadLocal中,以便后续使用
 *
 *              ThreadLocal好处:
 *              1.达到线程安全
 *              2.不需要加锁,提高执行效率
 *              3.更高效的利用内存、节省创建对象的开销
 *              4.免去传参的繁琐
 */
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.*;

public class ThreadLocal01 {
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            int index = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String result = new ThreadLocal01().data(index);
                    System.out.println(result);
                }
            });
        }
        executorService.shutdown();
    }
    public String data(int index){
        Date date = new Date(1000 * index);
        SimpleDateFormat simpleDateFormat = ThreadSafeSimpleDateFormat.threadLocal2.get();
        return simpleDateFormat.format(date);
    }
}
class ThreadSafeSimpleDateFormat {
    public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
    public static ThreadLocal<SimpleDateFormat> threadLocal2 = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}

场景举例2:每个线程内需要保持全局变量(如:拦截器中获取用户信息)可以让不同方法直接使用,避免参数传递的麻烦

/**
 * @description threadLocal用法2:每个线程内需要保持全局变量,可以让不同方法直接使用,避免参数传递的麻烦
 *                               防止内存泄露,用完里ThreadLocal里面值,要进行remove
 */
public class ThreadLocal02 {
    public static void main(String[] args) {
        Server1 server1 = new Server1();
        server1.process();
    }
}
class Server1{
    public void process(){
        User user = new User("张三");
        UserContextHolder.userThreadLocal.set(user);
        Server2 server2 = new Server2();
        server2.process();
    }
}
class Server2{
    public void process(){
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server2 获取用户名字:" + user.getName());
        UserContextHolder.userThreadLocal.remove();
        User newUser = new User("李四");
        UserContextHolder.userThreadLocal.set(newUser);
        Server3 server3 = new Server3();
        server3.process();
    }
}
class Server3{
    public void process(){
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server3 获取新用户名字:" + user.getName());
        // 用完后一定要remove
        UserContextHolder.userThreadLocal.remove();
    }
}
class User{
    private String name;
    public String getName() {
        return name;
    }
    public User(String name) {
        this.name = name;
    }
}
class UserContextHolder{
    public static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
}

结果:
在这里插入图片描述

相关文章:

  • 设计模式 人类父母和猫孩子的关系理解观察者模式(发布订阅模式)
  • 【java核心技术】Java知识总结 -- 语法篇
  • Neo4j图数据库和GDS图算法应用
  • Hello, World
  • 蒋鑫鸿:9.10国际黄金原油最新外盘行情趋势点评附解一套技术指导
  • gem5 GPGPU-Sim 安装踩坑笔记
  • 【Linux私房菜】—— 远程登录与数据传输、Vim与Vi的基础用法、关机与重启、登录与注销、运行级别、root密码找回
  • JSR-133: JavaTM Memory Model and Thread Specification原文解析
  • html网页如何获取后台数据库的数据(html + ajax + php + mysql)
  • Spring之事务实现原理及其注解@Transactional底层和传播机制原理
  • 第14章: 集合
  • Java后端开发工程师学习笔记【狂神说Java笔记】
  • Linux上的中文输入法安装(Ubuntu + Kali五笔拼音)
  • java+php+python的公文审批系统-办公系统
  • 使用C语言+USRP B210从零开始实现无线通信(4) 接收检测与解调
  • JavaScript-如何实现克隆(clone)函数
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • 【跃迁之路】【477天】刻意练习系列236(2018.05.28)
  • ES6 ...操作符
  • Facebook AccountKit 接入的坑点
  • iOS帅气加载动画、通知视图、红包助手、引导页、导航栏、朋友圈、小游戏等效果源码...
  • java8 Stream Pipelines 浅析
  • JavaSE小实践1:Java爬取斗图网站的所有表情包
  • Spring-boot 启动时碰到的错误
  • Vim 折腾记
  • 产品三维模型在线预览
  • 构建二叉树进行数值数组的去重及优化
  • 关于extract.autodesk.io的一些说明
  • 基于Javascript, Springboot的管理系统报表查询页面代码设计
  • 记一次删除Git记录中的大文件的过程
  • 开源SQL-on-Hadoop系统一览
  • 猫头鹰的深夜翻译:Java 2D Graphics, 简单的仿射变换
  • 王永庆:技术创新改变教育未来
  • 在weex里面使用chart图表
  • 微龛半导体获数千万Pre-A轮融资,投资方为国中创投 ...
  • ​学习一下,什么是预包装食品?​
  • #每天一道面试题# 什么是MySQL的回表查询
  • (13)Hive调优——动态分区导致的小文件问题
  • (4)logging(日志模块)
  • (9)STL算法之逆转旋转
  • (C语言)fgets与fputs函数详解
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (数据结构)顺序表的定义
  • (算法)Travel Information Center
  • (一)Dubbo快速入门、介绍、使用
  • (转)Linq学习笔记
  • (转)Spring4.2.5+Hibernate4.3.11+Struts1.3.8集成方案一
  • (转)程序员技术练级攻略
  • (转)我也是一只IT小小鸟
  • .net core MVC 通过 Filters 过滤器拦截请求及响应内容
  • .NET delegate 委托 、 Event 事件
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .Net程序猿乐Android发展---(10)框架布局FrameLayout