java并发基础知识导图
 

一 java线程用法

1.1 线程使用方式

1.1.1 继承Thread类

继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继承其他类。

 /**
* 继承Thread类的方式创建线程
*/
public class ThreadExtendTest extends Thread{ @Override
public void run() {
System.out.println("create thread by thread extend");
} public static void main(String[] args) {
new ThreadExtendTest().start();
} }

1.1.2 实现Runnable接口

无返回值,但由于实现的是接口,可以继承其他类。

 /**
* 实现Runnable接口的方式创建线程,无返回值
*/
public class ThreadRunnableTest implements Runnable{
@Override
public void run() {
System.out.println("create thread by runnable implements");
} public static void main(String[] args) {
new Thread(new ThreadRunnableTest()).start();
}
}

1.1.3 实现Callable接口

有返回值,且可以继承其他类。

 /**
* 实现Callable接口,和FutureTask结合,创建线程,有返回值
*/
public class ThreadCallableTest implements Callable<String> {
@Override
public String call() throws Exception {
return "create thread by implements Callable";
} public static void main(String[] args) throws ExecutionException, InterruptedException{
FutureTask<String> future1 = new FutureTask<String>(new ThreadCallableTest());
Thread thread1 = new Thread(future1);
thread1.start();
System.out.println(future1.get());
}
}

1.1.4 Runnable或Callable和Future结合的线程池方式

 /**
* 线程池的方式使用线程
*/
public class ThreadPoolTest implements Callable<String> {
@Override
public String call() throws Exception {
return "create thread by thread pool";
} public static void main(String[] args) throws ExecutionException, InterruptedException{
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<String> future1 = executorService.submit(new ThreadPoolTest());
System.out.println(future1.get());
executorService.shutdown();
}
}

java线程的四种用法,本质其实分为两类,一个是继承方式,一个是实现接口方式,由于java只支持单继承,继承Thread类后,不能再继承其他类,而使用实现接口的方式,还可以再继承其它有用的类。Runnable方式无返回值,Callable用于需要返回值的场景。利用线程池创建线程,实际上依然是借助Runnable或者Callable。

1.2 线程状态

  java线程状态分为NEW、RUNNABLE、BLOCKED、TIME_WAITING、WAITING、TERMINATED六种状态,线程状态间可以转换,状态转换图如下:
图1.1 java线程状态转换图
  当进程出现问题时,可查看进程各线程的运行状况。
  1. ps -aux | grep java查看进程pid,如图1.2。
  2. top -Hp pid,查看进程各线程cpu、内存等占用情况,如图1.3。
  3. jstack pid,查看进程各线程的堆栈信息,包括运行状态等,如图1.4。
图1.2 查找进程pid
 
图1.3 查看进程各线程资源占用
 
图1.4 查看线程状态
 

1.3 安全终止线程

1.3.1 volatile变量+轮询

 /**
* volatile变量+轮询安全终止线程
*/
class VolatileThread implements Runnable{
private volatile boolean cancelled;
@Override
public void run() {
while(!cancelled){
System.out.println("VolatileThread");
}
System.out.println("thread stop by volatile variable");
}
public void cancel() { cancelled = true; }
}

1.3.2 中断+轮询

 //中断+轮询安全终止线程
class InterruptThread implements Runnable{
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println("InterruptThread");
}
System.out.println("thread stop by interrupt thread");
} }

1.4 线程通信

1.4.1 等待通知机制

等待/通知经典范式:
等待方遵循如下原则。
  1. 获取对象锁
  2. 如果条件不满足,调用对象wait方法,被通知后仍要检查条件
  3. 条件满足则执行对应的逻辑
synchronized (对象){
    while(条件不满足){
        对象.wait();
    }
    对应的处理逻辑;
}
通知方遵循如下原则:
  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。
synchronized (对象){
    改变条件;
    对象.notifyAll();
}
用法示例:
 /**
* 线程通信,等待/通知机制
*/
public class ThreadCommunication {
public static void main(String[] args) {
final Object lock = new Object(); new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
System.out.println("thread A do wait method");
lock.wait();
System.out.println("thread A wait end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock) {
System.out.println("thread B get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.notify();
System.out.println("thread B do notify method");
}
}
}).start(); }
}
  wait能让当前线程阻塞,并释放拥有此对象的monitor(锁),当notify或者notifyAll被调用时,一个或所有线程被唤醒去竞争锁,调用notify/notifyAll方法的线程不会立即释放锁,只有当主动释放锁时,其它被唤醒的线程才能获得锁。
 

1.4.2 wait/notify底层实现

  wait/notify都是native方法,底层是c++实现。每一对象都对应一个ObjectMonitor对象(synchronized重量级锁,对象头会保存指向ObjectMonitor对象的指针),ObjectMonitor对象如下图,有两个队列分别为_WaitSet和_EntryList,用来保存ObjectWaiter对象列表,处于wait状态的线程,会被加入到wait set,处于等待锁block状态的线程,会被加入到entry set。_owner指向获得ObjectMonitor的线程。在调用某一对象的wait或notify方法前,需对这一对象加synchronized锁,加锁成功的线程会设置ObjectMonitor对象的_owner指针。当加锁成功但调用对象的wait方法,会将线程封装成ObjectWaiter对象,并放入_WaitSet队列,线程调用park方法阻塞。当对象调用notify时,会将_WaitSet头节点移入_EntryList。当锁被释放时,会调用_EntryList中线程的unpark方法竞争锁。
图1.5 ObjectMonitor对象结构
 
 

二 java线程常用方法含义及原理

2.1 daemon线程

  daemon线程主要用作程序中后台调度以及支持性工作,Thread.setDaemon(true)将线程设置为守护线程,当java虚拟机不存在非Daemon线程的时候,java虚拟机将会退出。虚拟机退出时,Daemon线程会立即终止,不能依靠finally块中的内容来确保关闭或清理资源。
 

2.2 join方法

thread.join()方法,会等待其它线程thread执行完,继续执行当前线程。join方法源码如下:

 public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
} if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
 Thread.join()方法通过wait/notify模式实现,调用join方法,线程对象最终会调用wait阻塞。线程执行完,会调用notify方法唤醒。

2.3 sleep()方法

sleep方法是native方法,其最终是利用内核提供的sleep系统调用实现。linux中的sleep()大致流程如下:
 #include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
///时钟编程 alarm()
void wakeUp()
{
printf("please wakeup!!/n");
}
int main(void)
{
printf("you have 4 s sleep!/n");
signal(SIGALRM,wakeUp);
alarm();
//将进程挂起
pause();
printf("good morning!/n");
return EXIT_SUCCESS;
}
 linux中的sleep,利用进程信号通信,注册信号SIGALRM的信号处理函数,并调用alarm设置定时器,接着调用pause()将进程挂起,将进程状态改为挂起状态,内核会重新分配CPU时间片,当alarm设置的时间到达时,操作系统会发送SIGALRM信号,此挂起的进程会执行WarkUp信号处理函数,pause函数会在进程捕捉到信号,并进行信号处理后返回。

2.4 yield()方法

  yield方法是native方法,最终调用Linux内核的sched_yield系统调用,它会使当前线程放弃CPU使用权,加入同等优先级队列的队尾,和其它线程一起重新竞争CPU。sleep当传入参数为0时,和yield相同。
  wait、sleep、yield都会让当前线程暂停执行,但使用场景、方式和原理各不相同。wait方法用于线程间通信,使用wait方法需先获得对象的锁,wait会释放线程拥有的锁;sleep方法用于短时间暂停当前线程,无强制要求必须先获取对象锁,且sleep不会释放线程拥有的锁。yield只是出让CPU使用权,让线程进入就绪调度队列重新调度,可能马上又重新获取CPU运行。
 

2.5 ThreadLocal用法及源码解析

  ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量,而不会出现冲突的问题,但由于每个线程都会有一个变量的副本,会导致消耗的内存变多。ThreadLocal类的get方法源码如下:
 public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//返回线程内部变量t.threadLocals
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//调用t.threadLocals = new ThreadLocalMap(this, firstValue);
}
  get方法会获得当前线程的map结构,map不为空,则以ThreadLocal变量为健,获取此变量的值,当ThreadLocalMap变量为空时,则会为线程创建一个ThreadLocalMap变量,可以看到,每个线程都有一个map的结构,当访问ThreadLocal变量时,会去当前访问线程的线程栈中取各自的threadLocalsb变量,从而达到多线程副本的目的。

参考文献

  1. Java并发学习之四种线程创建方式的实现与对比.
  2. 如何使用jstack分析线程状态.
  3. 如何安全地终止线程?
  4. Java 并发:线程间通信与协作.
  5. JVM源码分析之Object.wait/notify实现.
  6. sleep实现原理.
  7. pause()函数.
  8. Java中Wait、Sleep和Yield方法的区别.
  9. Java线程源码解析之yield和sleep.
  10. Java并发编程:深入剖析ThreadLocal.

java并发基础及原理的更多相关文章

  1. Java 并发基础

    Java 并发基础 标签 : Java基础 线程简述 线程是进程的执行部分,用来完成一定的任务; 线程拥有自己的堆栈,程序计数器和自己的局部变量,但不拥有系统资源, 他与其他线程共享父进程的共享资源及 ...

  2. Java并发-volatile的原理及用法

    Java并发-volatile的原理及用法 volatile属性:可见性.保证有序性.不保证原子性.一.volatile可见性 在Java的内存中所有的变量都存在主内存中,每个线程有单独CPU缓存内存 ...

  3. java并发基础(五)--- 线程池的使用

    第8章介绍的是线程池的使用,直接进入正题. 一.线程饥饿死锁和饱和策略 1.线程饥饿死锁 在线程池中,如果任务依赖其他任务,那么可能产生死锁.举个极端的例子,在单线程的Executor中,如果一个任务 ...

  4. java并发基础(二)

    <java并发编程实战>终于读完4-7章了,感触很深,但是有些东西还没有吃透,先把已经理解的整理一下.java并发基础(一)是对前3章的总结.这里总结一下第4.5章的东西. 一.java监 ...

  5. Java并发基础概念

    Java并发基础概念 线程和进程 线程和进程都能实现并发,在java编程领域,线程是实现并发的主要方式 每个进程都有独立的运行环境,内存空间.进程的通信需要通过,pipline或者socket 线程共 ...

  6. 【搞定 Java 并发面试】面试最常问的 Java 并发基础常见面试题总结!

    本文为 SnailClimb 的原创,目前已经收录自我开源的 JavaGuide 中(61.5 k Star![Java学习+面试指南] 一份涵盖大部分Java程序员所需要掌握的核心知识.欢迎 Sta ...

  7. Java并发——volatile的原理

    111 Java并发——volatile的原理

  8. Java 并发编程-不懂原理多吃亏(送书福利)

    作者 | 加多 关注阿里巴巴云原生公众号,后台回复关键字"并发",即可参与送书抽奖!** 导读:并发编程与 Java 中其他知识点相比较而言学习门槛较高,从而导致很多人望而却步.但 ...

  9. Java并发基础框架AbstractQueuedSynchronizer初探(ReentrantLock的实现分析)

    AbstractQueuedSynchronizer是实现Java并发类库的一个基础框架,Java中的各种锁(RenentrantLock, ReentrantReadWriteLock)以及同步工具 ...

随机推荐

  1. Intro to Machine Learning

    本节主要用于机器学习入门,介绍两个简单的分类模型: 决策树和随机森林 不涉及内部原理,仅仅介绍基础的调用方法 1. How Models Work 以简单的决策树为例 This step of cap ...

  2. 041 模块5-jieba库的使用

    目录 一.jieba库基本介绍 1.1 jieba库概述 1.2 jieba库的安装 1.3 jieba分词的原理 二.jieba库使用说明 2.1 jieba分词的三种模式 2.2 jieba库常用 ...

  3. ResourceDictionary主题资源替换(二) :通过加载顺序来覆盖之前的主题资源

    之前的ResourceDictionary主题资源替换(一)通过加载顺序来覆盖之前的主题资源,介绍了WPF框架对ResourceDictionary资源的合并规则. 此篇介绍一种在编译期间,实现资源替 ...

  4. 仿QQ5.0侧滑菜单

    一.概述 侧滑菜单现在已经非常流行了,目前大概有这么几种:最普通的侧滑,抽屉侧滑,QQ侧滑 注:本文来自慕课网 二.最普通的侧滑 先上图 代码如下: public class MainActivity ...

  5. .Net基础篇_学习笔记_第七天_三元数表达式(if-else的转换写法)

    三元表达式语法: 表达式1?表达式2:表达式3; 表达式1一般为一个关系表达式.如果表达式1的值为true,那么表达式2的值就是整个三元表达式的值.如果表达式1的值为false,那么表达式3的值就是整 ...

  6. 【转载】pandas中的循环

    原始文章链接: https://towardsdatascience.com/how-to-make-your-pandas-loop-71-803-times-faster-805030df4f06 ...

  7. Redis继续学习

    1.Redis一共16个数据库 # Set the number of databases. The , you can select # a different one on a per-conne ...

  8. 基于通用jar、动态配置、组件编排的会员任务中心系统设计

    # 一.聊聊本文想说什么:   为更好帮助商家的会员快速成长,保持用户活性,完善用户的成长体系,有赞用户中心-会员成长团队基于现有的业务场景,设计了一套较完备任务中心系统.同时也有很多通用技术组件能够 ...

  9. java语言的开发环境

    1.Java  EE和Java  SE Java SE(Java  Platform Standard Edition)标准版:是在java基础阶段主要学习的内容,该技术是java的最核心技术,适合小 ...

  10. Android Studio 3.1.3填坑之路

      昨天编写程序的时候,遇到了一个非常令人头疼的BUG,如下图:   标题栏和里面的内容都消失了,这对于一个非常在乎排版的软件来说简直就是晴空霹雳,搞了好长时间,终于在今天找到解决方法,原来是升级搞的 ...