前言:

最近也是在后台收到很多小伙伴私信问我线程和线程池这一块的问题,说自己在面试的时候老是被问到这一块的问题,被问的很头疼。前几天看到后帮几个小伙伴解决了问题,但是问的人有点多我一个个回答也回答不过来,干脆花了一个上午时间写了这篇文章分享给大家。话不多说,满满的干货都在下面了!

并发与并行

并发:指两个或多个事件在同一个时间段内发生。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每 一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分 时交替运行的时间是非常短的。

并行:指两个或多个事件在同一时刻发生(同时发生)。
在多个 CPU 系统中,这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,能够并行处理的程序数量越多,这能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多 个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创 建、运行到消亡的过程。
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程 中是可以有多个线程的,这个应用程序也可以称之为多线程程序

创建线程类

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。
Java中通过继承Thread类来创建并启动多线程的步骤如下:

定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。
创建Thread子类的实例,即创建了线程对象。
调用线程对象的start()方法来启动该线程。
首先自定义一个线程类

public class ThreadClass extends Thread {
//重写run方法
@Override
public void run()
{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

  

主线程:

public class DemoTest {
public static void main(String[] args) {
//创建一个线程对象
ThreadClass mythread = new ThreadClass();
//开启线程
mythread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程正在执行" + i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

  

其实实际上我们一般不会去继承线程类,由于java的单继承特性,当我们继承了线程类就无法继承别的父类了,一般我们是通过重写接口来开启线程的。

重写Runnable接口

步骤如下:

定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正 的线程对象。
调用线程对象的start()方法来启动线程。
首先重写接口

public class Runnableimp implements Runnable {
//重写run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

  

主线程:

public class DemoTest {
public static void main(String[] args) {
//创建一个线程对象,传入重写了run方法的接口对象
Thread mythread = new Thread(new Runnableimp());
//开启线程
mythread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程正在执行" + i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

  

执行的结果和刚才相同。

匿名内部类方式实现线程的创建

public class DemoTest {
public static void main(String[] args) {
//创建一个线程对象,使用匿名内部类重写run方法
Thread mythread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//开启线程
mythread.start();
}
}

  

使用lambda表达式

public class DemoTest {
public static void main(String[] args) {
//创建一个线程对象,使用lambda表达式重写run方法
Thread mythread = new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//开启线程
mythread.start();
}
}

  

线程安全

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步, 否则的话就可能影响线程安全。

线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 (synchronized)来解决。

同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁){
需要同步操作的代码
}

  

示例

private int num = 100;
private Object lock = new Object();
synchronized (lock)
{
num--;
}

  

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。

public synchronized void method()
{
可能会产生线程安全问题的代码
}

  

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁如下:

public void lock() :加同步锁。
public void unlock() :释放同步锁。

Lock lock = new ReentrantLock();
//加锁
lock.lock();
可能会产生线程安全问题的代码
//释放锁
lock.unlock();

  

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。

线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。

Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是 java.util.concurrent.ExecutorService 。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优 的,因此在 java.util.concurrent.Executors线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。
newFixedThreadPool方法

public static ExecutorService newFixedThreadPool(int nThreads)

  

创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

参数:

nThreads - 池中的线程数

返回:

新创建的线程池

抛出:

IllegalArgumentException - 如果 nThreads <= 0
获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下: public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行。

下面的代码通过四种方式向线程池中提交任务执行

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class Demo01 {
public static void main(String[] args) throws InterruptedException {
//创建线程池,线程数量为2
ExecutorService es = Executors.newFixedThreadPool(2);
//将任务扔到线程池的四种方式
//使用匿名内部类,
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//使用lambda表达式
es.submit(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"正在执行"+i);
try {
//休眠500毫秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//使用重写的接口
es.submit(new Runnableimp());
//使用重写的线程类
es.submit(new ThreadClass());
//启动一次顺序关闭,执行以前提交的任务,但不接受新任务
es.shutdown();
//主线程等待所有线程将任务执行完毕
while (!es.isTerminated());
System.out.println("线程执行完毕!");
}
}

  

除此之外java还提供了:

newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
newSingleThreadExecutor:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。
newSingleThreadScheduledExecutor: 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期
地执行。

小结:

今天的分享就到这里了,大家看完有什么不懂的话可以发私信问我,我看到都会回复的。

面试阿里,字节跳动99%会被问到的java线程和线程池,看完这篇你就懂了!的更多相关文章

  1. 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!

    Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...

  2. 还在问什么是JavaScript构造函数、实例、原型对象以及原型链?看完这篇你就懂

    1概述 ES6, 全称 ECMAScript 6.0 ,2015.06 发版.在ES6之前,对象不是基于类创建的,而是用一种称为构造函数的特殊函数来定义对象和它们的特征. 2构造函数 构造函数是一种特 ...

  3. 看完这篇Redis缓存三大问题,保你面试能造火箭,工作能拧螺丝。

    前言 日常的开发中,无不都是使用数据库来进行数据的存储,由于一般的系统任务中通常不会存在高并发的情况,所以这样看起来并没有什么问题. 一旦涉及大数据量的需求,如一些商品抢购的情景,或者主页访问量瞬间较 ...

  4. 面试阿里,腾讯,字节跳动90%都会被问到的Spring中的循环依赖

    前言 Spring中的循环依赖一直是Spring中一个很重要的话题,一方面是因为源码中为了解决循环依赖做了很多处理,另外一方面是因为面试的时候,如果问到Spring中比较高阶的问题,那么循环依赖必定逃 ...

  5. 面试阿里被“吊打”,一问Spring三不知,半年后二战终拿下offer

    Spring框架是一个为Java应用程序的开发提供了综合.广泛的基础性支持的Java平台.Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发. 近两年来,许多大厂在面 ...

  6. 看完这篇HTTP,跟面试官扯皮就没问题了

    我是一名程序员,我的主要编程语言是 Java,我更是一名 Web 开发人员,所以我必须要了解 HTTP,所以本篇文章就来带你从 HTTP 入门到进阶,看完让你有一种恍然大悟.醍醐灌顶的感觉. 最初在有 ...

  7. 应聘阿里,字节跳动美团90%会问到的JVM面试题! 史上最全系列!

    Java 内存分配 • 寄存器:程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码.• 静态域:static 定义的静态成员.• 常量池:编译时被确定并保存在 .class 文件中的(f ...

  8. WorkSkill 面试之 字节跳动一面

  9. 面试总被问到HTTP缓存机制及原理?看完你就彻底明白了

    前言 Http 缓存机制作为 web 性能优化的重要手段,对于从事 Web 开发的同学们来说,应该是知识体系库中的一个基础环节,同时对于有志成为前端架构师的同学来说是必备的知识技能. 但是对于很多前端 ...

随机推荐

  1. 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走

    前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...

  2. unittest学习

    unittest的四大特点 TestCase:测试用例.所有的用例都是直接继承与UnitTest.TestCase类. TestFixture:测试固件.setUp和tearDown分别作为前置条件和 ...

  3. String题目解析1

    Java又不是C++,什么时候字符数组等于字符串了(对这句话我不负责任)? 而常量池中的字符串,只有变量名不同是可以用双等号判断是否相等的,内存都是常量池中的字符串. 但是new出来的字符串,只能用e ...

  4. this()与super()

    1. 构造器中第一行默认是super(),一旦直接父类的构造器中没有无参的,那么必须显式调用父类的某个有参构造. 2. 构造器中第一行的super()可以换成this(),但是this()和super ...

  5. HashMap 中的哈希值计算问题

    date: 2020-08-21 16:48:00 updated: 2020-08-21 16:52:00 HashMap 中的哈希值计算问题 1. hash 计算 JDK1.8 HashMap源码 ...

  6. D. Road to Post Office 解析(思維)

    Codeforce 702 D. Road to Post Office 解析(思維) 今天我們來看看CF702D 題目連結 題目 略,請直接看原題. 前言 原本想說會不會也是要列式子解或者二分搜,沒 ...

  7. httpx和requests之间有什么区别?

    requests只能发送同步请求,httpx能够发送同步和异步请求. httpx的api和requests是兼容的,切换的成本几乎是零.在异步模式下,测试大量接口的话,httpx的速度是能够显著得到提 ...

  8. Android 教你如何发现 APP 卡顿

    最近部门打算优化下 APP 在低端机上的卡顿情况,既然想优化,就必须获取卡顿情况,那么如何获取卡顿情况就是本文目的. 一般主线程过多的 UI 绘制.大量的 IO 操作或是大量的计算操作占用 CPU,导 ...

  9. 浅析I/O模型-select、poll、epoll

    I/O流 概念 (1)c++中将数据的输入输出称之为流(stream),在c++中,流被定义为类,成为流类(stream class),其定义的对象为流对象. (2)文件,套接字(socket),管道 ...

  10. 835. Image Overlap —— weekly contest 84

    Image Overlap Two images A and B are given, represented as binary, square matrices of the same size. ...