文章简介

上一篇文章【「阿里面试系列」搞懂并发编程,轻松应对80%的面试场景】我们了解了进程和线程的发展历史、线程的生命周期、线程的优势和使用场景,这一篇,我们从Java层面更进一步了解线程的使用。关注我的技术公众号【架构师修炼宝典】一周出产1-2篇技术文章。Q群725219329分享并发编程,分布式,微服务架构,性能优化,源码,设计模式,高并发,高可用,Spring,Netty,tomcat,JVM等技术视频。

内容导航

  1. 并发编程的挑战
  2. 线程在Java中的使用

并发编程的挑战

引入多线程的目的在第一篇提到过,就是为了充分利用CPU是的程序运行得更快,当然并不是说启动的线程越多越好。在实际使用多线程的时候,会面临非常多的挑战

线程安全问题

线程安全问题值的是当多个线程访问同一个对象时,如果不考虑这些运行时环境采用的调度方式或者这些线程将如何交替执行,并且在代码中不需要任何同步操作的情况下,这个类都能够表现出正确的行为,那么这个类就是线程安全的
比如下面的代码是一个单例模式,在代码的注释出,如果多个线程并发访问,则会出现多个实例。导致无法实现单例的效果

public class SingletonDemo {
private static SingletonDemo singletonDemo=null;
private SingletonDemo(){}
public static SingletonDemo getInstance(){
if(singletonDemo==null){/***线程安全问题***/
singletonDemo=new SingletonDemo();
}
return singletonDemo;
}
}

通常来说,我们把多线程编程中的线程安全问题归类成如下三个,至于每一个问题的本质,在后续的文章中我们会单独讲解

  1. 原子性
  2. 可见性
  3. 有序性

上下文切换问题

在单核心CPU架构中,对于多线程的运行是基于CPU时间片切换来实现的伪并行。由于时间片非常短导致用户以为是多个线程并行执行。而一次上下文切换,实际就是当前线程执行一个时间片之后切换到另外一个线程,并且保存当前线程执行的状态这个过程。上下文切换会影响到线程的执行速度,对于系统来说意味着会消耗大量的CPU时间

减少上下文切换的方式

  1. 无锁并发编程,在多线程竞争锁时,会导致大量的上下文切换。避免使用锁去解决并发问题可以减少上下文切换
  2. CAS算法,CAS是一种乐观锁机制,不需要加锁
  3. 使用与硬件资源匹配合适的线程数

死锁

在解决线程安全问题的场景中,我们会比较多的考虑使用锁,因为它使用比较简单。但是锁的使用如果不恰当,则会引发死锁的可能性,一旦产生死锁,就会造成比较严重的问题:产生死锁的线程会一直占用锁资源,导致其他尝试获取锁的线程也发生死锁,造成系统崩溃

以下是死锁的简单案例

public class DeadLockDemo {
//定义锁对象
private final Object lockA = new Object();
private final Object lockB = new Object();
private void deadLock(){
new Thread(()->{
synchronized (lockA){
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println("Lock B");
}
}
}).start();
new Thread(()->{
synchronized (lockB){
synchronized (lockA){
System.out.println("Lock A");
}
}
}).start();
}
public static void main(String[] args) {
new DeadLockDemo().deadLock();
}
}

通过jstack分析死锁

1.首先通过jps获取当前运行的进程的pid

6628 Jps
17588 RemoteMavenServer
19220 Launcher
19004 DeadLockDemo

2.jstack打印堆栈信息,输入 jstack19004, 会打印如下日志,可以很明显看到死锁的信息提示

Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000000001d461e68 (object 0x000000076b310df8, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000000001d463258 (object 0x000000076b310e08, a java.lang.Object),
which is held by "Thread-1"

解决死锁的手段
1.保证多个线程按照相同的顺序获取锁
2.设置获取锁的超时时间,超过设定时间以后自动释放
3.死锁检测

资源限制

资源限制主要指的是硬件资源和软件资源,在开发多线程应用时,程序的执行速度受限于这两个资源。硬件的资源限制无非就是磁盘、CPU、内存、网络;软件资源的限制有很多,比如数据库连接数、计算机能够支持的最大连接数等
资源限制导致的问题最直观的体现就是前面说的上下文切换,也就是CPU资源和线程资源的严重不均衡导致频繁上下文切换,反而会造成程序的运行速度下降

资源限制的主要解决方案,就是缺啥补啥。CPU不够用,可以增加CPU核心数;一台机器的资源有限,则增加多台机器来做集群。

线程在Java中的使用

在Java中实现多线程的方式比较简单,因为Java中提供了非常方便的API来实现多线程。
1.继承Thread类实现多线程
2.实现Runnable接口
3.实现Callable接口通过Future包装器来创建Thread线程,这种是带返回值的线程
4.使用线程池ExecutorService

关注我的技术公众号【架构师修炼宝典】一周出产1-2篇技术文章。Q群725219329分享并发编程,分布式,微服务架构,性能优化,源码,设计模式,高并发,高可用,Spring,Netty,tomcat,JVM等技术视频。

继承Thread类

继承Thread类,然后重写run方法,在run方法中编写当前线程需要执行的逻辑。最后通过线程实例的start方法来启动一个线程

public class ThreadDemo extends Thread{
@Override
public void run() {
//重写run方法,提供当前线程执行的逻辑
System.out.println("Hello world");
}
public static void main(String[] args) {
ThreadDemo threadDemo=new ThreadDemo();
threadDemo.start();
}
}

Thread类其实是实现了Runnable接口,因此Thread自己也是一个线程实例,但是我们不能直接用 newThread().start()去启动一个线程,原因很简单,Thread类中的run方法是没有实际意义的,只是一个调用通过构造函数传递寄来的另一个Runnable实现类的run方法,这块的具体演示会在Runnable接口的代码中看到

public
class Thread implements Runnable {
/* What will be run. */
private Runnable target;
...
@Override
public void run() {
if (target != null) {
target.run();
}
}
...

实现Runnable接口

如果需要使用线程的类已经继承了其他的类,那么按照Java的单一继承原则,无法再继承Thread类来实现线程,所以可以通过实现Runnable接口来实现多线程

public class RunnableDemo implements Runnable{
@Override
public void run() {
//重写run方法,提供当前线程执行的逻辑
System.out.println("Hello world");
}
public static void main(String[] args) {
RunnableDemo runnableDemo=new RunnableDemo();
new Thread(runnableDemo).start();
}
}

上面的代码中,实现了Runnable接口,重写了run方法;接着为了能够启动RunnableDemo这个线程,必须要实例化一个Thread类,通过构造方法传递一个Runnable接口实现类去启动,Thread的run方法就会调用target.run来运行当前线程,代码在上面.

实现Callable接口

在有些多线程使用的场景中,我们有时候需要获取异步线程执行完毕以后的反馈结果,也许是主线程需要拿到子线程的执行结果来处理其他业务逻辑,也许是需要知道线程执行的状态。那么Callable接口可以很好的实现这个功能

public class CallableDemo implements Callable<String>{
@Override
public String call() throws Exception {
return "hello world";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> callable=new CallableDemo();
FutureTask<String> task=new FutureTask<>(callable);
new Thread(task).start();
System.out.println(task.get());//获取线程的返回值
}
}

在上面代码案例中的最后一行 task.get()就是获取线程的返回值,这个过程是阻塞的,当子线程还没有执行完的时候,主线程会一直阻塞直到结果返回

使用线程池

为了减少频繁创建线程和销毁线程带来的性能开销,在实际使用的时候我们会采用线程池来创建线程,在这里我不打算展开多线程的好处和原理,我会在后续的文章中单独说明。

public class ExecutorServiceDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个固定线程数的线程池
ExecutorService pool = Executors.newFixedThreadPool(1);
Future future=pool.submit(new CallableDemo());
System.out.println(future.get());
}
}

pool.submit有几个重载方法,可以传递带返回值的线程实例,也可以传递不带返回值的线程实例,源代码如下

/*01*/Future<?> submit(Runnable task);
/*02*/<T> Future<T> submit(Runnable task, T result);
/*03*/<T> Future<T> submit(Callable<T> task);

关注我的技术公众号【架构师修炼宝典】一周出产1-2篇技术文章。Q群725219329分享并发编程,分布式,微服务架构,性能优化,源码,设计模式,高并发,高可用,Spring,Netty,tomcat,JVM等技术视频。

【阿里面试系列】Java线程的应用及挑战的更多相关文章

  1. [不得不知道系列]Java线程面试你不得不知道的基础知识一

    Java内存管理面试指南一 Java基础面试指南一 Java基础面试指南二 Java基础面试指南三 Java基础面试指南四 Java线程面试指南一 Java线程面试指南二 Redis面试指南一 Kaf ...

  2. 阿里面试经历JAVA总结

    为记录阿里的电面经历,特与大家分享,岗位是JAVA研发工程师. 一面主要问题如下: 1)首先自我介绍 2)数据结构算法的基本问题,如排序算法,二叉树遍历,后序遍历非递归,图的最短路径问题 3)对一个数 ...

  3. (Java 多线程系列)Java 线程池(Executor)

    线程池简介 线程池是指管理同一组同构工作线程的资源池,线程池是与工作队列(Work Queue)密切相关的,其中在工作队列中保存了所有等待执行的任务.工作线程(Worker Thread)的任务很简单 ...

  4. Java面试系列--java基础

    Java基础总结 JAVA中的几种基本数据类型是什么,各自占用多少字节. 八大基本数据类型,byte:8位,short:16位,int:32位,long:64位,float:32位,double:64 ...

  5. 面试系列<3>——java并发

    面试系列--java并发 一.使用线程 有三种使用线程的方法: 实现Runnable接口 实现Callable接口 继承Thread类 实现 Runnable 和 Callable 接口的类只能当做一 ...

  6. 程序员面试系列之Java单例模式的攻击与防御

    我写的程序员面试系列 Java面试系列-webapp文件夹和WebContent文件夹的区别? 程序员面试系列:Spring MVC能响应HTTP请求的原因? Java程序员面试系列-什么是Java ...

  7. 当阿里面试官问我:Java创建线程有几种方式?我就知道问题没那么简单

    这是最新的大厂面试系列,还原真实场景,提炼出知识点分享给大家. 点赞再看,养成习惯~ 微信搜索[武哥聊编程],关注这个 Java 菜鸟. 昨天有个小伙伴去阿里面试实习生岗位,面试官问他了一个老生常谈的 ...

  8. 干货,阿里P8浅谈对java线程池的理解(面试必备)

    线程池的概念 线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成:ThreadPoolExecutor ...

  9. 阿里面试回来,想和Java程序员谈一谈(转载)

    引言 其实本来真的没打算写这篇文章,主要是LZ得记忆力不是很好,不像一些记忆力强的人,面试完以后,几乎能把自己和面试官的对话都给记下来.LZ自己当初面试完以后,除了记住一些聊过的知识点以外,具体的内容 ...

随机推荐

  1. angular2报错

    1.运行ng serve的时候卡在95% emitting LicenseWebpackPlugin 放弃使用cnpm,使用yarn 删除node_modules (不需要删除project目录重新n ...

  2. JetBrains系IDE的设置Pycharm PHPStorm

    一.换号边界线 File ->  Settings  ->Editor ->  Code Style -> Right margin (columns) 二.代码自动完成快捷键 ...

  3. kafka单机搭建,并测试api

    所用环境: kafka_2.-.gz centos 6.9 nat动态ip 准备工作: ().将防火墙关闭 service iptables stop 临时关闭 chkconfig iptables ...

  4. JS的变量的值怎么传递给PHP的变量?

    get: <script> name="xxx"; window.location='xxx.php? name='+name; post: <script> ...

  5. pwn学习之二

    刚刚开始学习pwn,记录一下自己学习的过程. 今天get了第二道pwn题目的解答,做的题目是2017年TSCTF的easy fsb,通过这道题了解了一种漏洞和使用该漏洞获取shell的方法:即格式化字 ...

  6. mysql查询出近一周,三个月,一年的数据

    SELECT * FROM 表名 WHERE 时间字段>DATE_SUB(CURDATE(), INTERVAL YEAR) 一年 SELECT * FROM 表名 WHERE 时间字段> ...

  7. C#事件委托概念

    事件就是一个狭义的委托,也就是事件是一个用于事件驱动模型的专用委托. 通俗的讲,委托你可以在客户代码中直接调用委托来激发委托指向的函数,而事件不可以,事件的触发只能由服务代码自己触发 也就是说在你的代 ...

  8. jieba库

    Note of Jieba ( 词云图实例 ) Note of Jieba jieba库是python 一个重要的第三方中文分词函数库,但需要用户自行安装. 一.jieba 库简介 (1) jieba ...

  9. tensorflow 使用 4 非线性回归

    # 输入一个 x 会计算出 y 值 y 是预测值,如果与 真的 y 值(y_data)接近就成功了 import tensorflow as tf import numpy as np # py 的画 ...

  10. BZOJ5412 : circle

    若仅保留这$k$个点仍然有环,那么显然无解. 否则设$A$表示这$k$个点的集合,$B$表示剩下的点的集合,因为是竞赛图,每个集合内部的拓扑关系是一条链,方便起见将所有点按照在所在集合的链上的位置进行 ...