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 = new ReentrantLock();
 
for (int i = 1; i <= 3; i++) {
lock.lock();
        }
 
for(int i=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;

    }

}

java多线程(六)线程控制类的更多相关文章

  1. Java多线程之线程其他类

    Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...

  2. Java多线程(六) —— 线程并发库之并发容器

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/19/326527.html 一.ConcurrentMap API 从这一节开始正式进入并发容器 ...

  3. Java多线程(六) 线程系列总结

    多线程系列终于终结得差不多,本人对该系列所做的总结大致如下: 线程锁模块耗费了大量的时间,底层的AQS实现比较复杂.仍然没有时间总结源码部分,能够坚持写下这么几个篇幅的内容真心佩服自己....希望继续 ...

  4. Java多线程之线程的控制

    Java多线程之线程的控制 线程中的7 种非常重要的状态:  初始New.可运行Runnable.运行Running.阻塞Blocked.锁池lock_pool.等待队列wait_pool.结束Dea ...

  5. Java多线程父子线程关系 多线程中篇(六)

    有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...

  6. Java多线程之线程协作

    Java多线程之线程协作 一.前言 上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了.这就是简单的互斥处理. 假如我们现在想执行更加精确的控制,而不 ...

  7. Java多线程之线程的通信

    Java多线程之线程的通信 在总结多线程通信前先介绍一个概念:锁池.线程因为未拿到锁标记而发生的阻塞不同于前面五个基本状态中的阻塞,称为锁池.每个对象都有自己的锁池的空间,用于放置等待运行的线程.这些 ...

  8. java多线程与线程间通信

    转自(http://blog.csdn.net/jerrying0203/article/details/45563947) 本文学习并总结java多线程与线程间通信的原理和方法,内容涉及java线程 ...

  9. Java多线程之线程的暂停

    Java多线程之线程的暂停 下面该稍微休息一下了呢……不过,这里说的是线程休息,不是我们哦.本节将介绍一下让线程暂停运行的方法. 线程Thread 类中的sleep 方法能够暂停线程运行,Sleep ...

  10. Java多线程与线程池技术

    一.序言 Java多线程编程线程池被广泛使用,甚至成为了标配. 线程池本质是池化技术的应用,和连接池类似,创建连接与关闭连接属于耗时操作,创建线程与销毁线程也属于重操作,为了提高效率,先提前创建好一批 ...

随机推荐

  1. 【PyTorch v1.1.0文档研习】60分钟快速上手

    阅读文档:使用 PyTorch 进行深度学习:60分钟快速入门. 本教程的目标是: 总体上理解 PyTorch 的张量库和神经网络 训练一个小的神经网络来进行图像分类 PyTorch 是个啥? 这是基 ...

  2. 树莓派使用root操作图形界面使用自带的文件管理器

    使用pi用户通过VNC登录图形界面之后,在需要修改一些文件则时提示权限不够, 命令行下使用sudo 运行就可以了.或者直接用root账户. 修改管理员密码:sudo passwd root 修改启用管 ...

  3. Echo团队 对 待就业六人组 和 SkyReach 的Beta产品测试报告

    班级:软件工程1916|W 作业:Beta阶段团队项目互评 团队名称:Echo 目录 对待就业六人组的Beta产品测试报告 对SkyReach的Beta产品测试报告 对待就业六人组的Beta产品测试报 ...

  4. discuz x3.3标题的最少字数限制设置方法

    Discuz帖子标题默认字数最多是80个字节,却没有最少的字节限制.最近看到很多站长想限制一下帖子标题最少字数,不管是利于seo,还是禁止灌水,都有必要.为此把设置方法发上来分享. 1.找到并打开st ...

  5. 学习:SLT_string容器

    前言:这个学了感觉没多大用,自己只需要了解就好,忘记了可以参考以下网站的示例 参考网站:https://github.com/AnkerLeng/Cpp-0-1-Resource/blob/maste ...

  6. Numpy | 08 切片和索引

    ndarray对象的内容可以通过索引或切片来访问和修改,与 Python 中 list 的切片操作一样. (1)ndarray 数组索引可以基于 0 - n 的下标进行: (2)切片对象可以通过内置的 ...

  7. 求等差数列前$n$项和$S_n$的最值

    一.方法依据: 已知数列\(\{a_n\}\)是等差数列,首项为\(a_1\),公差为\(d\),前\(n\)项和为\(S_n\),则求\(S_n\)的最值常用方法有两种: (1).函数法:由于\(S ...

  8. pandas把'<m8[ns]'类型转换为int类型进行运算

    工作中经常碰到两列数据为date类型,当这两列数据相减或者相加时,得到天数,当运用这个值进行运算会报错:ufunc true_divide cannot use operands with types ...

  9. [sdoi 2017]树点涂色

    传送门 Description Bob 有一棵\(n\)个点的有根树,其中\(1\)号点是根节点.Bob 在每个节点上涂了颜色,并且每个点上的颜色不同. 定义一条路径的权值是,这条路径上的点(包括起点 ...

  10. 一周 GitHub 开源项目推荐:阿里、腾讯、陌陌、bilibili……

    阅读本文大概需要 2.8 分钟. 陌陌风控系统正式开源 陌陌风控系统静态规则引擎,零基础简易便捷的配置多种复杂规则,实时高效管控用户异常行为. GitHub 地址 https://github.com ...