一、多线程带来的的风险---线程安全(重点) 1、观察线程不安全 家观察下是否适用多线程的现象是否⼀致? 同时尝试思考下为什么会有这样的现象发生呢? ,则说这个程序是线程安全的。 3、线程不安全的原因 3.1线程调度是随机的(抢占式执行) 这是线程安全问题的罪魁祸首 :线程的调度 随机调度使⼀个程序在多线程环境下,执行顺序存在很多的变数 既保障了count++的线程安全,又利用了循环的并发潜力,因此执行速度更快。 3. 外部加锁 为什么count++是串行的? 对应的,CPU的价格最贵,内存次之,硬盘最便宜 3.5指令重排序引起的线程不安全问题 指令重排序 什么是代码重排序 ⼀段代码是这样的: 1. 去前台取下U盘 2. 去教室写10分钟作业 3.
sqlite3 sqlite3.c文件中有sqlite_threadsafe的定义 /* ** The SQLITE_THREADSAFE macro must be defined as 0, 1, THREADSAFE #else # define SQLITE_THREADSAFE 1 /* IMP: R-07272-22309 */ #endif #endif 使用c++调用sqlite多线程调用异常 ,可以尝试重新编译sqlite3,或者修改配置,sqlite3_threadsafe函数可以查看lib是否是线程安全的编译库 使用sqlite3_config函数,配置如下参数 #define SQLITE_CONFIG_SINGLETHREAD 1 /* nil */ #define SQLITE_CONFIG_MULTITHREAD 2 /* nil */ #define SQLITE_CONFIG_SERIALIZED 3 5 /* sqlite3_mem_methods* */ #define SQLITE_CONFIG_SCRATCH 6 /* void*, int sz, int N */
线程安全问题 指的是多个线程操作同一个共享资源的时候可能会出现的线程安全问题 银行取钱问题 多个用户同时操作一个银行账户进行取钱操作,账户内余额为1000元,每个账户同时取出1000元,由于线程执行的随机性就会导致线程安全问题的产生 user1用户结束操作,余额0.0元 user2用户结束操作,余额-1000.0元 线程同步 也称同步代码块 作用 为了更好的解决线程安全问题的方案 线程同步解决线程安全问题的核心思想 让多个线程实现先后依次访问共享资源 ,这样就解决了安全问题 做法 将共享资源上锁,每次只能一个线程进入访问完毕之后,其他线程才能进来 三种同步(上锁)方式 同步代码块 同步方法 Lock显式锁 同步代码块 作用 将出现线程安全问题的核心代码上锁 ,性能差 线程不安全的程序,性能较好。 在开发中假如并不会存在多线程安全问题,一般采用线程不安全的设计
一个进程的多个线程,共享同一份内存资源,如果两个线程,都尝试修改某个变量,就可能出现冲突; 某个逻辑单个线程执行是可以的,但是多个线程执行出现问题,这就是线程不安全,反之则线程安全 线程安全问题的原因 [ 根本原因 ] 操作系统对于线程的调度是随机的(没有办法应对) 两个线程针对同一个变量进行修改操作 修改操作不是原子的 内存可见性 指令重排序 eg:线程不安全例子 public class Demo14 5w次 ++ 操作,其结果应该为10w,但运行结果确像是一个<10w的随机值,这就是两个线程对同一变量修改的不安全。 count++操作实际是三次指令,将内存值加载到cpu寄存器中,在cpu寄存器中对值进行计算,将寄存器再写入到内存中,由于是三次指令,可能在某一条指令时调度到别的线程,这样的调度穿插过程就可能出现线程安全问题 虽然和synchronized都是解决线程安全问题,但和synchronized解决的是两种不同的问题。
线程安全是开发者在开发多线程任务时最关心的问题,那么线程安全需要注意哪些呢? 一、思考:线程安全产生的原因是什么? 二、final,volatile关键字的作用? 四、如何编写线程安全的程序? 五、ThreadLocal使用的注意事项有哪些? 一、思考:线程安全产生的原因是什么? 看下面这个过程: 例如:对于 int a = 3; 线程A、B、C 都要操作变量a++; 当线程A操作a++后:主内存 a=3,A的工作内存 a = 4; B工作内存中 a=3; C 中 a=3; 然后 二、如何实现线程安全呢? 根据线程安全原因:可变资源(内存)线程间共享可得出: 不共享资源 共享不可变资源 共享可变资源(可见性、操作原子性、禁止重排序) 1、不共享资源 ThreadLocal: 如何使用ThreadLocal
在了解完这个问题后,我们又需要去了解一个使用多线程不得不考虑的问题——线程安全。今天我们不说如何保证一个线程的安全,我们聊聊什么是线程安全? 因为我之前面试被问到了,说真的,我之前真的不是特别了解这个问题,我们好像只学了如何确保一个线程安全,却不知道所谓的安全到底是什么!3、什么是线程安全? ,我开启的3条线程,每个线程循环10次,得到以下结果:? 毫无疑问,它绝对是线程安全的,我们来分析一下,为什么它是线程安全的? t1在获取锁对象之后,执行任务缺花费了3秒,那么这个时候线程t2是不在等待的。?
类型存储介质数据特征共享内存主内存存放变量多线程共享本地内存CPU 高速缓存、缓冲区、寄存器以及其它硬件优化临时存放线程使用的变量副本使用期间其它线程无法访问优势:由于 CPU 执行速度明先快于内存读写速度 可见性可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。Java 语言会尽可能保证主内存数据和本地内存同步,但仍可能出现不可见问题。 线程锁互斥锁和自旋锁互斥锁阻塞锁。当线程需要获取的锁已经被其他线程占用时,该线程会被直接挂起。直到其他线程释放锁,由操作系统激活线程。 适用于锁使用者保持锁时间比较长的情况,线程挂起后不再消耗 CPU 资源。自旋锁非阻塞锁。当线程需要获取的锁已经被其他线程占用时,该线程会不断地消耗 CPU 的时间去试图获取锁。 可重入锁允许一个线程对同一对象多次上锁。由 JVM 记录对象被线程加锁次数,只有当线程释放掉所有锁(加锁次数为0)时,其他线程才获准进入。
第二章 线程安全 2.1 线程安全 2.2 线程同步 2.3 同步代码块 2.4 同步方法 2.5 Lock锁 第三章 线程状态 3.1 线程状态概述 3.2 Timed Waiting(计时等待) 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对 象才是真正 的线程对象。 3. 适合多个相同的程序代码的线程去共享同一个资源。 2. 可以避免 java 中的单继承的局限性。 3. 这种问题,几个窗口 ( 线程 ) 票数不同步了,这种问题称为线程不安全。 线程安全问题都是由全局变量及静态变量引起的。 这样才能保证该线 程执行过程 中会睡眠 3. sleep 与锁无关,线程睡眠到期自动苏醒,并返回到 Runnable (可运行)状态。
一、什么是线程安全? 二、java语言中的线程安全 我们将java语言中各种操作共享的数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。 绝对线程安全 在Java API中标注自己是线程安全的类,大多数都不是绝对的线程安全。我们可以通过Java API中一个不是“绝对线程安全”的线程安全类来看看这里的“绝对”是什么意思。 3. 无同步方案 同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性,因此会有一些代码天生就是线程安全的。 3. 锁粗化 如果虚拟机探测到有一系列连续操作都对同一个对象反复加锁和解锁,将会把加锁同步的范围扩展(粗化)到整个操作序列的外部。 4.
AQS 核心思想是通过以下方式,建立一套线程阻塞等待以及被唤醒时锁分配的机制。如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。 如果被请求的共享资源被占用,就将暂时获取不到锁的线程封装成一个结点,加入到一个虚拟的双向队列 CLH 中。CLH 不存在真实的队列,仅存在结点之间的关联关系。 线程抢占资源时会通过 CAS 操作去尝试修改 state ,成功则获取锁成功,失败则进入等待队列等待被唤醒。 Share(共享)多个线程可同时执行,如 Semaphore/CountDownLatch。 isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。 tryAcquire(int):独占方式。
线程安全: 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。 如何保证呢: 1、使用线程安全的类; 2、使用synchronized同步代码块,或者用Lock锁; > 由于线程安全问题,使用synchronized同步代码块 原理:当两个并发线程访问同一个对象 object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。 另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。 3、多线程并发情况下,线程共享的变量改为方法局部级变量; 参考学习:线程安全和线程同步Synchronized
5 变量的线程安全分析 成员变量和静态变量是否线程安全? 如果它们没有共享,则线程安全 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况 如果只有读操作,则线程安全 如果有读写操作,则这段代码是临界区,需要考虑线程安全 局部变量是否线程安全? 相同 示例图 方法访问修饰符带来的思考, 如果把 method2 和 method3 的方法修改为 public 会不会代理线程安全问题? 给个提示,这些线程安全类的方法,单个是线程安全的,那么多个组合起立还是不是呢。 所以不存在线程安全。
移动端开发过程中,会出现 进程 和 线程的概念,以及多线程中 线程安全 的问题。 ThreadPoolExecutor.DiscardOldestPolicy()); private ExecutorService grayThreadPool = Executors.newSingleThreadExecutor(); 线程安全 所以在多个线程同时访问数据时,也就会引入线程安全的问题。 多线程安全要解决的问题是,不同线程访问同一数据时的数据安全问题。 解决线程安全的常用方法是增加 synchronized 关键字. synchronized使用示例: //synchronized 加在方法上 private synchronized void setNewValue 线程数据同步安全中,volatile关键字也是比较常用的, volatile关键字能够保证可见性,被volatile修饰的变量,在一个线程中被改变时会立刻同步到主内存中,而另一个线程在操作这个变量时都会先从主内存更新这个变量的值
这些面试题常被问,答案是,左边的都是非线程安全,右边都是线程安全! 然后又问你,什么是线程安全,什么是非线程安全呢? A.线程安全 当多个线程类并发操作某类的方法A,来修改这个A方法的某个成员变量的值B,B不会出错,则我们就说,该的这个A方法是线程安全的。 B.非线程安全 当多个线程类并发操作某类的方法A,来修改这个A方法的某个成员变量的值B,B会出错,则我们就说,该的这个A方法是非线程安全的。 线程执行dou()方法的时候,实例pi返回的是当前线程的对象。这样的调用是线程安全的。 线程安全跟非线程安全如何取舍 从第一个例子可得知,非线程的方法添加synchronized修饰就可以转化为线程安全,但是性能会相差20倍左右,如果不加的话,该类的成员变量又可能发生错误,所以具体就看你的需求
线程安全 线程是越多越好吗?答案否,线程太多的话,会造成CPU频繁的切换反而会造成很多线程处于等待状态。 除了浪费资源和效率之外,多线程带来的其他风险:安全、死锁等 比如下面程序: public class CountAdd implements Callable { private Map<String map.getOrDefault("count", 0); map.put("count", ++integer); } 为什么加synchronized就可以让线程变的安全 synchronized是一种锁,JUC的Lock是一种锁,锁是在多线程中为了保障程序的安全性的一种同步机制。 多线程+锁=万无一失?多度的使用锁,锁的创建和销毁相应的开销越大。 形成死锁的四种必要条件: 1.互斥条件 ——一个资源每次只能被一个进程使用 2.不可剥夺条件 ——进程对已有资源未使用完,不可剥夺 3.请求与保持条件 ——一个进程为请求一个一个资源而阻塞
将结果赋值 index 数据漏过 主要是由于线程1修改后index值已改变未输出前,cpu将权利交给线程2,线程2继续累加并输出 2.数据重复 主要是由于线程1执行到index +1但是还没赋值index ,cpu就将执行权交给线程2 3.超过最大值 当index=499 时线程1和线程2都看到满足条件,线程1将index增加到500后,线程2恢复执行变为501 synchronized synchronized 已经被其他线程所拥有,则其他线程再尝试获取所有权时,被陷入阻塞状态,直到monitor计数器变为0,才能再次获取 Monitor exit 释放monitor所有权就是将计数器减一,前提是必须拥有所有权 注意: 1. monitor关联对象不能为空 2. synchronized的作用域不要太大,越大效率越低 3.不同的monitor不要使用相同的锁, 4. 两个方法被加同样的class 锁 死锁的原因 交叉锁导致死锁 A 持有 R1 等待 R2 , B 持有 R2 等待 R1 2.内存不足 共30M内存,A持有 10 ,B 持有 20 , 都在等待资源 3.
这个操作不是原子操作,基本分为3个操作,读取i,进行+1,赋值给i。 假设有两个线程,当第一个线程读取i=1时,还没进行+1操作,切换到第二个线程,此时第二个线程也读取的是i=1。 随后两个线程进行后续+1操作,再赋值回去以后,i不是3,而是2。显然数据出现了不一致性。 再比如在32位的JVM上面去读取64位的long型数值,也不是一个原子操作。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class OrderExample { int a = 0; boolean flag 3. 可见性 可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。 可见性问题可能有各个环节产生。 线程安全的概念 指某个函数、函数库在多线程环境中被调用时,能够正确地处理各个线程的局部变量,使程序功能正确完成。 比如最开始所说的i++的例子 ? 就会导致线程不安全。
欢迎关注微信公众号:数据科学与艺术 作者WX:superhe199 线程安全指的是在多线程环境下,共享的资源能够被正确地访问和操作,不会出现数据不一致或者异常的情况。 原子操作能够保证不可分割地执行,从而避免了线程切换的时候出现的数据不一致问题。 使用线程安全的数据结构,如线程安全的队列、集合等。 这些数据结构在设计时考虑了多线程操作的问题,能够保证在多线程环境下的安全性。 使用线程局部存储(Thread Local Storage,TLS)来存储线程私有的数据,避免共享资源的竞争。 count变量的递增操作是线程安全的。 最终输出的结果应该是10000,如果没有线程安全措施,可能会出现不确定的结果。
回归正题,当我们查看JDK API的时候,总会发现一些类说明写着,线程安全或者线程不安全,比如说StringBuilder中,有这么一句,“将StringBuilder 的实例用于多个线程是不安全的。 ”,那么下面手动创建一个线程不安全的类,然后在多线程中使用这个类,看看有什么效果。 ,看一下输出结果: Thread-0-55 Thread-1-110 Thread-2-165 Thread-4-220 Thread-5-275 Thread-6-330 Thread-3-385 Thread 上述测试,我们发现,存在成员变量的类用于多线程时是不安全的,不安全体现在这个成员变量可能发生非原子性的操作,而变量定义在方法内也就是局部变量是线程安全的。 想想在使用struts1时,不推荐创建成员变量,因为action是单例的,如果创建了成员变量,就会存在线程不安全的隐患,而struts2是每一次请求都会创建一个action,就不用考虑线程安全的问题。
2.1 线程安全 如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 这种问题,几个窗口 ( 线程 ) 票数不同步了,这种问题称为线程不安全。 线程安全问题都是由全局变量及静态变量引起的。 若每个线程中对全局变量、静态变量只有读操作,而无写 操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。 2.2 线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 3. 锁机制。 2.3 同步代码块 同步代码块 : synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。