java多线程(六)线程控制类
1. 多线程控制类
为了保证多线程的三个特性,Java引入了很多线程控制机制,下面介绍其中常用的几种:
l ThreadLocal
l 原子类
l Lock类
l Volatile关键字
1.1. ThreadLocal
1.1.1. 作用
ThreadLocal提供线程局部变量,即为使用相同变量的每一个线程维护一个该变量的副本。
当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,比如数据库连接Connection,每个请求处理线程都需要,但又不相互影响,就是用ThreadLocal实现。
1.1.2. 示例
两个线程分别转账
package com.controller; /**
* @Auther: lanhaifeng
* @Date: 2019/11/21 0021 10:09
* @Description:线程局部变量ThreadLocal
* @statement:
*/
public class ThreadLocaclDemo {
//1.创建银行对象:钱,取款,存款
static class Bank {
private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 0;
}
}; public Integer get(){
return threadLocal.get();
} public void set(Integer money){
threadLocal.set(threadLocal.get()+money);
}
}
//2.创建转账对象:从银行中取钱,转账,保存到帐户
static class Transfer implements Runnable{
private Bank bank; public Transfer(Bank bank){
this.bank = bank;
}
public void run() {
for(int i=0; i<10; i++){
bank.set(10);
System.out.println(Thread.currentThread().getName()+"帐户余额:"+bank.get());
}
}
}
//3.在main方法中使用两个对象模拟转账
public static void main(String[] args){
Bank bank = new Bank();
Transfer transfer = new Transfer(bank);
Thread thread1 = new Thread(transfer, "客户1");
Thread thread2 = new Thread(transfer, "客户2"); thread1.start();
thread2.start();
}
}
执行效果:
1.1.3. 分析
l 在ThreadLocal类中定义了一个ThreadLocalMap,
l 每一个Thread都有一个ThreadLocalMap类型的变量threadLocals
l threadLocals内部有一个Entry,Entry的key是ThreadLocal对象实例,value就是共享变量副本
l ThreadLocal的get方法就是根据ThreadLocal对象实例获取共享变量副本
l ThreadLocal的set方法就是根据ThreadLocal对象实例保存共享变量副本
1.2. 原子类
Java的java.util.concurrent.atomic包里面提供了很多可以进行原子操作的类,分为以下四类:
l 原子更新基本类型:AtomicInteger、AtomicBoolean、AtomicLong
l 原子更新数组:AtomicIntegerArray、AtomicLongArray
l 原子更新引用:AtomicReference、AtomicStampedReference等
l 原子更新属性:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater
提供这些原子类的目的就是为了解决基本类型操作的非原子性导致在多线程并发情况下引发的问题。
1.2.1. 非原子性操作问题演示
非原子性的操作会引发什么问题呢?下面以i++为例演示非原子性操作问题。
i++并不是原子操作,而是由三个操作构成:
tp1 = i;
tp2 = tp1+1;
i = tp2;
所以单线程i的值不是有问题,但多线程下就会出错,多线程示例代码如下:
package com.multithread.thread; import java.util.concurrent.atomic.AtomicInteger; public class AtomicClass {
static int n = 0;
public static void main(String[] args) throws InterruptedException {
int j = 0;
while(j<100){
n = 0;
Thread t1 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
n++;
}
}
};
Thread t2 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
n++;
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("n的最终值是:"+n);
j++;
} }
}
执行结果如下:发现n的最终值可能不是2000
1.2.2. 原子类解决非原子性操作问题
以上代码修改如下:
package com.multithread.thread; import java.util.concurrent.atomic.AtomicInteger; public class AtomicClass {
static AtomicInteger n;
public static void main(String[] args) throws InterruptedException {
int j = 0;
while(j<100){
n = new AtomicInteger(0);
Thread t1 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
n.getAndIncrement();
}
}
};
Thread t2 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
n.getAndIncrement();
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("n的最终值是:"+n);
j++;
} }
}
执行结果如下:n的值永远是2000
1.2.3. 原子类CAS原理分析
1.2.4. CAS的ABA问题及解决
1.2.4.1. ABA问题分析
当前内存的值一开始是A,被另外一个线程先改为B然后再改为A,那么当前线程访问的时候发现是A,则认为它没有被其他线程访问过。在某些场景下这样是存在错误风险的。如下图:
1.2.4.2. ABA问题解决
package com.multithread.thread; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicClass {
static AtomicStampedReference<Integer>n;
public static void main(String[] args) throws InterruptedException {
int j = 0;
while(j<100){
n = new AtomicStampedReference<Integer>(0,0);
Thread t1 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
int stamp;
Integer reference;
do{
stamp = n.getStamp();
reference = n.getReference();
} while(!n.compareAndSet(reference, reference+1, stamp, stamp+1));
}
}
};
Thread t2 = new Thread(){
public void run(){
for(int i=0; i<1000; i++){
int stamp;
Integer reference;
do{
stamp = n.getStamp();
reference = n.getReference(); } while(!n.compareAndSet(reference, reference+1, stamp, stamp+1));
}
}
};
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("n的最终值是:"+n.getReference());
j++;
} }
}
执行效果如下:执行结果也是2000
注意:采用AtomicStampedReference会降低性能,慎用。
1.3. Lock类
1.3.1. Lock接口关系图
Lock和ReadWriteLock是两大锁的根接口
Lock 接口支持重入、公平等的锁规则:实现类 ReentrantLock、ReadLock和WriteLock。
ReadWriteLock 接口定义读取者共享而写入者独占的锁,实现类:ReentrantReadWriteLock。
1.3.2.
可重入锁
不可重入锁,即线程请求它已经拥有的锁时会阻塞。
可重入锁,即线程可以进入它已经拥有的锁的同步代码块。
publicclassReentrantLockTest {
public static void main(String[] args) throws InterruptedException {
ReentrantLock
lock=
newReentrantLock();
for(
inti =
1; i <=
3; i++) {
lock.
lock();
}
for(
inti=
1;i<=
3;i++){
try {
}
finally{
lock.unlock();
}
}
}
}
1.3.3. 读写锁
读写锁,即可以同时读,读的时候不能写;不能同时写,写的时候不能读。
示例代码:
package com.multithread.thread; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; /**
* 读写操作类
*/
public class ReadWriteLockDemo { private Map<String, Object>map = new HashMap<String, Object>();
//创建一个读写锁实例
private ReadWriteLock rw = new ReentrantReadWriteLock();
//创建一个读锁
private Lock r = rw.readLock();
//创建一个写锁
private Lock w = rw.writeLock(); /**
* 读操作
*
* @param key
* @return
*/
public Object get(String key) {
r.lock();
System.out.println(Thread.currentThread().getName() + "读操作开始执行......");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
return map.get(key);
} finally {
r.unlock();
System.out.println(Thread.currentThread().getName() + "读操作执行完成......");
}
} /**
* 写操作
*
* @param key
* @param value
*/
public void put(String key, Object value) {
try {
w.lock();
System.out.println(Thread.currentThread().getName() + "写操作开始执行......");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
} finally {
w.unlock();
System.out.println(Thread.currentThread().getName() + "写操作执行完成......");
}
} public static void main(String[] args) {
final ReadWriteLockDemo d = new ReadWriteLockDemo();
d.put("key1", "value1");
new Thread(new Runnable() {
public void run() {
d.get("key1");
}
}).start(); new Thread(new Runnable() {
public void run() {
d.get("key1");
}
}).start();
new Thread(new Runnable() {
public void run() {
d.get("key1");
}
}).start();
} }
执行效果如下:写操作为独占锁,执行期间不能读;读操作可
1.4. Volatile关键字
1.4.1. 作用
一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
l 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(注意:不保证原子性)
l 禁止进行指令重排序。(保证变量所在行的有序性)
当程序执行到volatile变量的读操作或者写操作时,在其前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见;在其后面的操作肯定还没有进行;
在进行指令优化时,不能将在对volatile变量访问的语句放在其后面执行,也不能把volatile变量后面的语句放到其前面执行。
1.4.2. 应用场景
基于volatile的作用,使用volatile必须满足以下两个条件:
l 对变量的写操作不依赖于当前值
l 该变量没有包含在具有其他变量的不变式中
常见应用场景如下:
状态量标记:
volatilebooleanflag = false;
while(!flag){
doSomething();
}
publicvoidsetFlag() {
flag = true;
}
volatilebooleaninited = false;
//
线程1:
context = loadContext();
inited = true;
//
线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
双重校验:
classSingleton{
privatevolatilestaticSingleton instance = null;
privateSingleton() {
}
publicstaticSingleton getInstance() {
if(instance==null) {
synchronized(Singleton.class) {
if(instance==null)
instance = newSingleton();
}
}
returninstance;
}
}
最新文章
- LINQ系列:Linq to Object限制操作符
- 低功耗蓝牙4.0BLE编程-nrf51822开发(8)-GATT
- fs event_socket
- robotframework+ride+Selenium2Library+AutoItLibrary配置
- ASP判断当前页面上是否有参数ID传递过来
- webkit javascript
- LinkButton( 按钮)
- asp.net已流的方式下载文件
- DOM事件代码小结
- Data Base mongodb高版本与低版本的区别
- 为什么树莓派不会受到 Spectre 和 Meltdown 攻击
- MyBatis探究-----为实体类Bean取别名,配置typeAliases
- PHP Warning: mysqli_connect(): The server requested authentication method unknown to the client [caching_sha2_password] in /usr/local/php/CreateDB.php on line 5
- 20165304 2017-2018-2 《Java程序设计》第3周学习总结
- jquery的validate表单验证
- 用10分钟,搭建图像处理编程环境,0失败!(python语言,windows系统)
- spring mvc 跨域请求处理——spring 4.2 以上
- 测试人员需要了解的sql知识(提高篇)
- git的软件安装
- mysql/mariadb学习记录——创建删除数据库、表的基本命令
热门文章
- Fedora 安装 MongoDB 教程
- 互斥锁的robust属性的介绍和使用
- C++学习(10)—— 对象模型和this指针
- Codeforces Round #142 (Div. 1) C. Triangles
- python应用-猜数字
- 几种访问HDFS文件的客户端的总结
- GPU的主要工作:图像合成、图形操作、光线表达
- OpenCV 学习笔记(11)像素级别指针操作
- OpenCV 学习笔记(9)RGB转换成灰度图像的一个常用公式Gray = R*0.299 + G*0.587 + B*0.114
- (尚033)Vue_案例_slot(组件间的通信4:slot)