多线程

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. 百度推出 MIP Baidu Path链接

    在站长将站点 MIP 化时,需要关注 URL 的一共有三个:MIP URL, MIP-Cache URL 以及 MIP Baidu Path. 从 URL 说起 在互联网中,URL 定义页面的地址,每 ...

  2. 响应式WEB设计的基本原则大总结

    响 应式Web设计对于解决多类型屏幕问题来说是个不错方案,但从印刷的角度来看,其却存在着很多的困难.没有固定的页面尺寸.没有毫米或英寸,没有任何物理 限制,让人感到无从下手.随着建立网站可用的各种小工 ...

  3. 一份完整的阿里云 Redis 开发规范,值得收藏!

    来源:yq.aliyun.com/articles/531067 作者:付磊-起扬 本文主要介绍在使用阿里云Redis的开发规范,从下面几个方面进行说明. 键值设计 命令使用 客户端使用 相关工具 通 ...

  4. angular2-7中的变化监测

      最近做公司新项目用的angular7,中碰到了一个很头疼的问题在绑定对象中的数据改变时,页面视图没有跟新,需点击页面中的时间元素后才会更新.以前使用angularJs也经常碰到类似情况,这种时候一 ...

  5. python接口自动化(八)--发送post请求的接口(详解)

    简介 上篇介绍完发送get请求的接口,大家必然联想到发送post请求的接口也不会太难,被聪明的你又猜到了.答案是对的,虽然发送post请求的参考例子很简单,但是实际遇到的情况却是很复杂的,因为所有系统 ...

  6. 【Python3爬虫】常见反爬虫措施及解决办法(三)

    上一篇博客的末尾说到全网代理IP的端口号是经过加密混淆的,而这一篇博客就将告诉你如何破解!如果觉得有用的话,不妨点个推荐哦~ 一.全网代理IP的JS混淆 首先进入全网代理IP,打开开发者工具,点击查看 ...

  7. 通过模拟JDK中的动态代理,由浅入深讲解动态代理思想.

    目录 场景引入 动态代理引入 动态代理进阶 总结 个人认为动态代理在设计模式中算是比较难的, 本篇文章将从无到有, 从一个简单代码示例开始迭代, 逐步深入讲解动态代理思想. 场景引入 假设现在有一个坦 ...

  8. java基础(八)-----深入解析java四种访问权限

    Java中的访问权限理解起来不难,但完全掌握却不容易,特别是4种访问权限并不是任何时候都可以使用.下面整理一下,在什么情况下,有哪些访问权限可以允许选择. 一.访问权限简介 访问权限控制: 指的是本类 ...

  9. C# 将object对象转换为实体对象

    C# 将object对象转换为实体对象.一共两种方法. 第一种方法,代码如下: /// <summary> /// 将object对象转换为实体对象 /// </summary> ...

  10. Redis主从同步要深入理解?一篇文章足矣!

    前言: 今天想和大家分享有关 Redis 主从同步(也称「复制」)的内容. 我们知道,当有多台 Redis 服务器时,肯定就有一台主服务器和多台从服务器.一般来说,主服务器进行写操作,从服务器进行读操 ...