多线程

1. 多线程基础

多线程状态转换图

普通方法介绍
yeild

yeild,线程让步。是当前线程执行完后所有线程又统一回到同一起跑线。让自己或者其他线程运行,并不是单纯的让给其他线程。

join

等待线程结束;调用线程等待当前线程结束后才能往下执行,阻塞线程之意。join本质是在当前对象实例上调用线程wait()

如下所示:输出完 thread-1后再输出end

public static void main(String[] args) throws InterruptedException{
System.out.println("main start"); Thread t1 = new Thread(new Worker("thread-1"));
t1.start();
t1.join();
System.out.println("main end");
}
sychronized

sychronized确保安全外,还能保证线程之间的可见性和有序性

  1. 指定加锁对象:对给定的对象加锁,进入同步代码前要先获得给定对象的锁
  2. 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要先获得当前实例的锁。
  3. 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要先获得当前类的锁。

2. JDK并发包

重入锁(ReentrantLock)

决定了线程是否可以访问临界区资源。object.wait()和object.notify()起到线程等待和通知的作用。这些工具能实现多线程相互之间的协作作用

sychronized功能的扩展-重入锁

重入锁:

重入锁使用ReentrantLock来实现

public class ReentrantLocker implements Runnable {

    public static ReentrantLock locker = new ReentrantLock();

    private static int i = 0;

    @Override
public void run() { for (int j = 0; j < 1000; j++) {
locker.lock();
try {
i++;
} catch (Exception e) {
e.printStackTrace();
}finally {
locker.unlock();
}
}
} public static void main(String[] args) throws Exception{
ReentrantLocker locker1 = new ReentrantLocker();
// 对同一个对象加锁。important
Thread t1 = new Thread(locker1);
Thread t2 = new Thread(locker1);
t1.start();
t2.start();
/**调用join方法,阻塞当前线程,待所有线程执行完再往下执行*/
t1.join();
t2.join();
System.out.println(i);
}
}
重入锁对比sychronized有点
  1. 中断响应

    对于sychronized来说,一个线程要访问被同步的资源,要么继续执行,要么继续等待。而使用重入锁,线程可以被中断。程序可以取消对锁的请求,避免死锁的发生。
  2. 锁超时设置
//表示线程对资源锁等待时长。
//如果在规定的时间内没有获取到锁,
//则线程获取锁失败
lock.tryLock(5, TimeUnits.SECONDS)

如果tryLock没加参数,就不需要等待。

3. 公平性

公平锁会按照时间先后顺序,保证先到先得,后到者后得。公平锁的一大特点是:不会产生饥饿现象,只要你排队,最后还是能得到资源的

重入锁条件-Condition
信号量(Semaphore)

信号量是对锁的扩展,无论是内部锁sychronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量指定多个线程同时访问同一资源

  1. 构造函数
public Semaphore(int permits);
public Semahore(int permits, boolean fair);

构造信号量对象时,permits参数表示的是:同时多少个线程能访问同一资源。

读写锁(ReadWriteLock)

读写分离锁可以有效的较少锁竞争,用锁分离机制来提升系统性能。

读写锁的访问约束情况

--
非阻塞 阻塞
阻塞 阻塞

倒计时器(CountDownLatch)

可以让某一线程等待直到倒计时结束,再开始执行。当调用wait方法的时候,主线程被阻塞,直到countdown减到0的时候,程序再往下执行。

循环栅栏(CyclicBarrier)

作用是阻止线程继续执行,要求线程在栅栏处等待,计数器可以循环使用。假如将计数器设置为10,那么凑齐第一批10个进程数后计数器会归为0,然后接着凑齐下一批10个线程

线程池

声明一个线程池

线程池的作用是复用线程,减少系统开销。声明一个线程池有如下方法

/**
该方法返回一个固定线程数量的线程池。
当有一个新的任务提交,线程池有空闲的线程就提交,没有空闲的话就缓存到待执行列表中,
直到有空闲的线程才执行
*/
public ExecutorService newFixedThreadService(int threadNum);
/**
该方法值返回包含一个线程的线程池,
如果有多个任务提交也是将多余的任务保存到一个任务队列中,待有空闲的线程才执行
**/
public ExecutorService newSingleThreadExecutor();
/**
返回可根据实际情况调整线程数量的线程池,线程池中的线程数量是不一定的,但还是能够复用空闲线程的
*/
public ExecutorService newCachedThreadPool();
线程池的内部实现

通过源代码可以看出newFixedThreadServicenewSingleThreadExecutor,newCachedThreadPool内部都是由ThreadPoolExecutor来处理的

ThreadPoolExecutor的内部实现

public ThreadPoolExecutor(
// 线程池中线程数量
int corePoolSize,
// 线程池中允许线程最大数量
int maximumPoolSize,
// 线程池中线程数量超出corePoolSize的线程的存活时间
long keepAliveTime,
// 时间单位
TimeUnit unit,
// 任务队列,被提交尚未被执行的任务
BlockingQueue<Runnable> workQueue,
// 拒绝策略,任务太多来不及处理,如何拒绝任务
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}

其中workqueue是指那些尚未被执行的线程队列。它是BlockingQueue的接口对象。在ThreadPoolExecutor中可以使用如下几种 BlockQueue

  • 直接提交的队列
  • 有界的任务队列
  • 无界任务队列
  • 优先任务队列

ThreadLocal

为每个线程运行时存储所需参数。

如下是线程安全的日期格式化方法

public class SafeDataFormat {
static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>();
static Date date = new Date();
static String dateStr = "2019-04-27 04:12:41";
static class ParseDate implements Runnable { @Override
public void run() {
if (null == simpleDateFormatThreadLocal.get()) {
simpleDateFormatThreadLocal.set(new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}
SimpleDateFormat simpleDateFormat = simpleDateFormatThreadLocal.get();
try {
System.out.println(simpleDateFormat.parse(dateStr)); } catch (Exception e) {
e.printStackTrace();
}
}
} public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 2000; i++) {
executorService.execute(new ParseDate());
} executorService.shutdown();
}
}
ThreadLocal实现原理

先看ThreadLocal的get(), set()方法。

set方法
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}

从代码中可以看出首先获取当前线程对象,然后通过getMap()拿到当前线程的ThreadLocalMap,并将值更新到这个map中。

get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

首先get()获取到当前对象的ThreadLocalMap对象,然后将当前线程作为key来获取内部的实际数据。

这些变量是维护在Thread内部的(ThreadLocalMap定义所在类),意味着只要线程不退出,对象的引用就一直都存在着。

ThreadLocalMap的实现使用了弱引用,java虚拟机在进行垃圾回收时,如果发现变量是弱引用,就会立即回收

CAS 比较交换 CompareAndSweep

由于其的非阻塞性,它对死锁天生免疫。

CAS的算法过程

包含三个参数CAS(V, E, N)。V表示要更新的变量,E表示预期值,N表示新值。仅当 V = E时,才会将V设置成N;如果V值跟E值不同,则说明已经被其他线程醉了更新,当前线程什么都不做,最后CAS返回当前V的真实值

CAS操作是抱着乐观的态度进行的,它总认为自己可以完成操作。当多个线程同时CAS同一个值额时候,只有一个线程会胜出并成功更新。失败的线程不会被挂起,仅是被告知操作失败,并允许再次尝试

简单的说,CAS需要额外给出期望值,也就是你认为这个变量最终的样子。如果这个变量最终跟你的预期不同,你就重新读取再次更新就好了。

AtomicInteger

是可变、线程安全的。基于CAS的;就内部实现来说,AtomicIntger保存了核心字段

//代表了当前AtomicInteger的实际值
private volatile int value;
// 保存着value字段在AtomicInteger对象的偏移量。实现AtomicInteger的关键
private static final long valueOffset;

关注一下incrementAndGet()

public final int incrementAndGet() ;
for(; ; )
int v current = get();
int next = cueernt + 1;
if (compareAndSet(current, next))
return next

CAS操作未必是成功的,因此对于不成功的情况,要不断的去重试

Java多线程知识整理的更多相关文章

  1. JAVA多线程知识总结(二)

    本文是承接上一篇文章:JAVA多线程知识总结(一) 四.Java多线程的阻塞状态与线程控制  上文已经提到线程阻塞的集中具体类型.下面主要看引起JAVA线程阻塞的方法 1,join()-----让一个 ...

  2. 2019-9-16 java上课知识整理总结(动手动脑,课后实验)

    java上课知识整理总结(动手动脑,课后实验) 一,课堂测试 1,题目:课堂测试:像二柱子那样,花二十分钟写一个能自动生成30道小学四则运算题目的 “软件” 要求:(1)题目避免重复: (2)可定制( ...

  3. JAVA 多线程知识总结(一)

    一,线程的生命周期以及五种基本状态 关于JAVA线程的生命周期,首先看一下下面这张图 上图中基本上囊括了Java中多线程各重要知识点.掌握了上图中的各知识点,Java中的多线程也就基本上掌握了. Ja ...

  4. 阿里 P8 高级架构师吐血总结的 《Java 核心知识整理&面试.pdf》| 免费分享

    最近在网上发现一份非常棒的 PDF 资料,据说是阿里 P8 级高级架构师吐血总结的, 其中内容覆盖很广,包括 Java 核心基础.Java 多线程.高并发.Spring.微服务.Netty 与 RPC ...

  5. Java并发知识整理

    整理了一下前段时间学习Java并发的笔记,大约有40篇. 1. Java并发基础知识 并发基础(一) 线程介绍 并发基础(二) Thread类的API总结 并发基础(三) java线程优先级 并发基础 ...

  6. JAVA hashmap知识整理

    HashMap和Hashtable的比较是Java面试中的常见问题,用来考验程序员是否能够正确使用集合类以及是否可以随机应变使用多种思路解决问题.HashMap的工作原理.ArrayList与Vect ...

  7. 面试求职中需要了解的Java多线程知识

    Java中多线程的实现方式 在java的历史版本中,有两种创建多线程程序的方法 1) 通过创建Thread类的子类来实现(Thread类提供了主线程调用其它线程并行运行的机制) 主要步骤: 自定义类继 ...

  8. java基础知识整理

    java基础入门知识(转载请注明出处.) 1.JVM.JRE和JDK的区别. (1)JVM(Java Virtual Machine):java虚拟机,用于保证java跨平台的特性,java语言是跨平 ...

  9. Java多线程知识总结(一)

    一.创建线程的三种方式: 创建线程的方式有三种,一是创建Thread实例,二是实现Runnable接口,三是实现Callable接口,Runnable接口和Callable接口的区别是一个无返回值,一 ...

随机推荐

  1. Python档案袋(生成器、迭代器、队列 )

    生成器: 简单的生成器实现: #生成器,将for循环的变量传递到前面的式子进行处理 #生成的并不是一个列表,而是一个存在算数规则的对象 #不能通过下标直接取值,必须一个一个从头到尾取 va=(i*2 ...

  2. C#串口通讯概念以及简单实现

    最近在研究串口通讯,其中有几个比较重要的概念,RS-232这种适配于上位机和PC端进行连接,RS-232只限于PC串口和设备间点对点的通信.它很简单的就可以进行连接,由于串口通讯是异步的,也就是说你可 ...

  3. 《HelloGitHub》第 35 期

    <HelloGitHub>第 35 期 兴趣是最好的老师,HelloGitHub 就是帮你找到兴趣! 简介 分享 GitHub 上有趣.入门级的开源项目. 这是一个面向编程新手.热爱编程. ...

  4. H5单张、多张图片保存续篇

    前言 这篇是接上篇内容.还没看的可以看H5单张.多张图片上传这篇文章预热. 图片入库 本章我们就来看看如何让多种图片保存至数据库中.数据库:mysql   后端:.NET Core 我们回顾一下上篇我 ...

  5. PostgreSQL:安装及中文显示

    一.PostgreSQL PostgreSQL (也称为Post-gress-Q-L)是一个跨平台的功能强大的开源对象关系数据库管理系统,由 PostgreSQL 全球开发集团(全球志愿者团队)开发. ...

  6. Wmyskxz文章目录导航附Java精品学习资料

    前言:这段时间一直在准备校招的东西,所以一晃眼都好长时间没更新了,这段时间准备的稍微好那么一点点,还是觉得准备归准备,该有的学习节奏还是要有..趁着复习的空隙来整理整理自己写过的文章吧..好多加了微信 ...

  7. 企业级Harbor介绍及安装

    企业级Harbor介绍及安装 一.Harbor介绍 VMware公司最近开源了企业级Registry项目Harbor,其的目标是帮助用户迅速搭建一个企业级的Docker registry 服务.它以D ...

  8. Sharepoint 2013内容查询Web部件自定义显示样式(实战)

    分享人:广州华软 星尘 一. 前言 在进行Sharepoint开发时,经常会遇到内容展示个性化需求的问题,当然如果通过自定义开发控件对于内容展示的需求基本都可以很好的解决,但自定义开发也有不好的地方, ...

  9. 小程序后端项目【Springboot框架】部署到阿里云服务器【支持https访问】

    前言: 我的后端项目是Java写的,用的Springboot框架.在部署服务器并配置https访问过程中,因为做了一些令人窒息的操作(事后发现),所以老是不能成功. 不成功具体点说就是:域名地址可以正 ...

  10. SQL SERVER 查看近期死锁

    在项目运行的过程中,死锁不可能完全避免,但要尽可能减少死锁的出现, 产生死锁的原因主要是: 1,系统资源不足. 2,进程运行推进的顺序不合适. 3,资源分配不当等. 产生死锁的四个必要条件:- 互斥条 ...