面试腾讯,字节跳动首先要掌握的Java多线程,一次帮你全掌握!
一、程序,进程,线程联系和区别
其实程序是一段静态的代码,它是应用程序执行的脚本。进程就是程序动态的执行过程,它具有动态性,并发性,独立性。线程是进程调度和执行的单位。
进程:每个进程都有独立的代码和数据空间(进程上下文),一个进程包含一个或者多个线程,同时线程是资源分配的最小单位。
线程:同一类线程共享代码和数据空间,并且每个线程有独立运行栈和程序计数器,同时线程是调度的最小单位。
那什么是多进程呢? ,常见的是打开我们自己电脑任务管理器里面就还有多个进程,其实指的是我们的操作系统能同时运行多个任务(微信,QQ等)。
多线程其实就是一个进程有多条路径在运行。
二、多线程实现的方式
在Java中实现多线程有三种方式,第一种方式是继承Thread类,第二种方式是实现Runnable接口, 第三种是实现Callable,结合Future使用(了解),并发编程中使用,这里不详细说:
第一种实现多线程的方式,继承Thread类,代码实现如下:
public class RabbitDemo extends Thread {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("兔子跑了:"+i+"步");
}
}
}
public class TortoiseDemo extends Thread {
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println("乌龟跑了:"+i+"步");
}
}
}
public class ClientThreadTest {
public static void main(String[] args) {
RabbitDemo rabbitDemo = new RabbitDemo();
TortoiseDemo tortoiseDemo = new TortoiseDemo();
rabbitDemo.start();
tortoiseDemo.start();
for (int i = 0; i < 10; i++) {
System.out.println("main==>" + i);
}
}
}
第二个实例实现多个线程同时共享一个成员变量线程的运行状态。
p
ublic class Qp12306 implements Runnable {
private int num=50;
@Override
public void run() {
while (true){
if (num<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"抢到票"+num--);
}
}
}
public class ClientQpTest {
public static void main(String[] args) {
Qp12306 qp12306 = new Qp12306();
Thread thread = new Thread(qp12306, "张三");
Thread thread1 = new Thread(qp12306, "李四");
Thread thread2 = new Thread(qp12306, "王五");
thread.start();
thread1.start();
thread2.start();
}
}
第二种方式:实现 Runnable接口,重写run方法,这种方式我们实现多线程常用的方式(避免Java单继承的限制)并且该方式采用了静态代理设计模式(参考: 静态代理),代码实例如下 :
public class RunnableDemo implements Runnable { @Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("Runnable:" + i);
}
}
}
public class ClientRunableTest {
public static void main(String[] args) {
RunnableDemo runnableDemo = new RunnableDemo();
Thread thread = new Thread(runnableDemo);
thread.start();
}
}
第三方式:实现Callable接口,结合Future实现多线程,代码实例如下:
/**
* 使用Callable创建线程
*/
public class Race implements Callable<Integer> {
private String name;
private long time; //延时时间
private boolean flag = true;
private int step = 0; //步 @Override
public Integer call() throws Exception {
while (flag) {
Thread.sleep(time);
step++;
}
return step;
} public Race(String name, long time) {
this.name = name;
this.time = time;
} public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class CallableTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Race race = new Race("张三", 200);
Race race1 = new Race("李四", 500);
Future<Integer> result = executorService.submit(race);
Future<Integer> result1 = executorService.submit(race1);
Thread.sleep(3000);
race.setFlag(false);
race1.setFlag(false);
int num1 = result.get();
int num2 = result1.get();
System.out.println("张三-->" + num1 + "步");
System.out.println("李四-->" + num2 + "步");
//停止服务
executorService.shutdownNow();
}
}
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
实现Runnable接口比继承Thread类所具有的优势:
1、适合多个相同的程序代码的线程去处理同一个资源
2、可以避免java中的单继承的限制
3、增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4、线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM就是在操作系统中启动了一个进程。
三、线程的状态和方法
线程从创建,运行到结束总是处于五种状态之一:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
线程共包括一下5种状态:
1.新建状态 :线程对象被创建后就进入了新建状态,Thread thread = new Thread();
2.就绪状态(Runnable):也被称之为“可执行状态”,当线程被new出来后,其他的线程调用了该对象的start()方法,即thread.start(),此时线程位于“可运行线程池”中,只等待获取CPU的使用权,随时可以被CPU调用。进入就绪状态的进程除CPU之外,其他运行所需的资源都已经全部获得。
3.运行状态(Running):线程获取CPU权限开始执行。注意:线程只能从就绪状态进入到运行状态。
4.阻塞状态(Bloacked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行,知道线程进入就绪状态后才能有机会转到运行状态。
阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池中”。进入这个状态后是不能自动唤醒的,必须依靠其他线程调用notify()或者notifyAll()方法才能被唤醒。
(2)、同步阻塞:运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被其他线程占用,则JVM会吧该线程放入“锁池”中。
(3)、其他阻塞:通过调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新回到就绪状态
- 死亡(Dead):线程执行完了或因异常退出了run()方法,则该线程结束生命周期。
阻塞线程方法的说明:
wait(), notify(),notifyAll()这三个方法是结合使用的,都属于Object中的方法,wait的作用是当当前线程释放它所持有的锁进入等待状态(释放对象锁),而notify和notifyAll则是唤醒当前对象上的等待线程。
wait() —— 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
sleep() 和 yield()方法是属于Thread类中的sleep()的作用是让当前线程休眠(正在执行的线程主动让出cpu,然后cpu就可以去执行其他任务),即当前线程会从“运行状态”进入到阻塞状态”,但仍然保持对象锁,仍然占有该锁。当延时时间过后该线程重新阻塞状态变成就绪状态,从而等待cpu的调度执行。
sleep()睡眠时,保持对象锁,仍然占有该锁。
yield()的作用是让步,它能够让当前线程从运行状态进入到就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行。
wait () , sleep()的区别:
1、每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
2、 wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
3、 sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
4、 sleep()睡眠时,保持对象锁,仍然占有该锁,而wait()释放对象锁;
四、线程的基本信息和优先级
涉及到线程的方法:Thread.currentThread() 当前线程,setName():设置名称,getName():获取名称,isAlive():判断状态
优先级:Java线程有优先级,优先级高的线程能获取更多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
下面代码实现:
基本信息
public static void main(String[] args) throws InterruptedException {
Rundemo rundemo = new Rundemo();
Thread thread = new Thread(rundemo);
thread.setName("线程"); //设置线程的名称
System.out.println(thread.getName()); //获取当前线性的名称
System.out.println(Thread.currentThread().getName()); //main线程
thread.start();
System.out.println("线程启动后的状态:" + thread.isAlive());
Thread.sleep(10);
rundemo.stop();
Thread.sleep(1000); //停止后休眠一下,再看线程的状态
System.out.println("线程停止后的状态:" + thread.isAlive());
test(); }
优先级
public static void test() throws InterruptedException {
Rundemo rundemo1 = new Rundemo();
Thread thread1 = new Thread(rundemo1);
thread1.setName("线程thread1");
Rundemo rundemo2 = new Rundemo();
Thread thread2 = new Thread(rundemo2);
thread2.setName("线程thread2");
thread1.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
Thread.sleep(1000);
rundemo1.stop();
rundemo2.stop(); }
五、线程的同步和死锁问题
同步方法:synchronized
同步代码块:synchronized(引用类型|this|类.class){
}
线程同步是为了多个线程同时访问一份资源确保数据的正确,不会造成数据的桩读,死锁是线程间相互等待锁锁造成的.
下面代码实现:多个线程同时访问一份资源使用synchronized
public class Qp12306 implements Runnable {
private int num = 10;
private boolean flag = true; @Override
public synchronized void run() {
while (true) {
test();
}
} public synchronized void test() {
if (num <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "抢到票" + num--);
}
} public class ClientQpTest {
public static void main(String[] args) {
Qp12306 qp12306 = new Qp12306();
Thread thread = new Thread(qp12306, "张三");
Thread thread1 = new Thread(qp12306, "李四");
Thread thread2 = new Thread(qp12306, "王五");
thread.start();
thread1.start();
thread2.start();
}
}
运行结果
synchronized 静态代码块
public class Client {
public static void main(String[] args) throws InterruptedException {
JvmThread thread1 = new JvmThread(100);
JvmThread thread2 = new JvmThread(500);
thread1.start();
thread2.start(); } } class JvmThread extends Thread{
private long time;
public JvmThread() {
}
public JvmThread(long time) {
this.time =time;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->创建:"+Jvm.getInstance(time));
}
} /**
* 单例设计模式
* 确保一个类只有一个对象
* 懒汉式
* 1、构造器私有化,避免外部直接创建对象
* 2、声明一个私有的静态变量
* 3、创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象
*/
class Jvm {
//声明一个私有的静态变量
private static Jvm instance =null;
//构造器私有化,避免外部直接创建对象
private Jvm(){ }
//创建一个对外的公共的静态方法 访问该变量,如果变量没有对象,创建该对象
public static Jvm getInstance(long time){
if(null==instance){
synchronized(Jvm.class){
if(null==instance ){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance =new Jvm();
}
}
}
return instance;
} public static Jvm getInstance3(long time){
synchronized(Jvm.class){
if(null==instance ){
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance =new Jvm();
}
return instance;
}
} }
六、生产者消费者模式
生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况:
存储空间已满,而生产者占用着它,消费者等着生产者让出空间从而去除产品,生产者等着消费者消费产品,从而向空间中添加产品。互相等待,从而发生死锁.
具体实现代码如下:
/**
* 生产者
*/
public class ProduceRu implements Runnable {
private Movie movie; public ProduceRu(Movie movie) {
this.movie = movie;
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
if (0 == i % 2) {
movie.proTest("向往的生活");
} else {
movie.proTest("好声音");
}
}
}
} /**
*消费者
*/
public class ConsumeRu implements Runnable {
private Movie movie; public ConsumeRu(Movie movie) {
this.movie = movie;
}
@Override
public void run() {
for(int i=0;i<10;i++){
movie.conTest();
}
}
} /**
* 生产者消费者模式共同访问同一份资源
* wait() 等等,释放锁,sleep()不释放锁
* notify()/notifyAll():唤醒
*/
public class Movie { private String pic;
//flag--->true 生产者生产,消费者等待,生产完后通知消费
//flag--->false 消费者消费,生产者等待,消费完通知生产
private boolean flag = true; /**
* 生产者生产,消费者等待,生产完后通知消费
* @param pic
*/
public synchronized void proTest(String pic) {
if (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产了:" + pic);
this.pic = pic;
this.notify();
this.flag = false; } /**
* 消费者消费,生产者等待,消费完通知生产
*/
public synchronized void conTest() {
if (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者:" + pic);
this.notifyAll();
this.flag = true;
}
} public class ClientTest {
public static void main(String[] args) {
Movie movie=new Movie();
new Thread(new ProduceRu(movie)).start();
new Thread(new ConsumeRu(movie)).start();
}
}
七、任务调度
1.Thread实现方法
这是最常见的,创建一个thread,然后让它在while循环里一直运行着,通过sleep方法来达到定时任务的效果。这样可以快速简单的实现,代码如下:
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("任务开始了");
while (true) {
Thread.sleep(1000);
System.out.println("hello");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
2.TimeTask实现方法
Thread方法优点就是简单,但缺少了灵活性,TimeTask实现方法最主要的两个优点是:可以控制启动和取消任务的时间、第一次执行任务时可以指定想要delay的时间。
实现的过程中Timer用于调度任务,TimeTask用户具体的实现,是线程安全的,代码如下:
public class TimeTest {
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("任务运行开始.......");
}
},new Date(),1000);
}
}
3.ScheduledExecutorService实现方法
ScheduledExecutorService是从Java java.util.concurrent里 相比于上两个方法,它有以下好处:
相比于Timer的单线程,它是通过线程池的方式来执行任务的可以很灵活的去设定第一次执行任务 delay时间
具体代码如下:
方法说明:
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit)
创建并执行在给定的初始延迟之后首先启用的定期动作,随后在一个执行的终止和下一个执行的开始之间给定的延迟。 如果任务的执行遇到异常,则后续的执行被抑制。 否则,任务将仅通过取消或终止执行人终止。
参数
command - 要执行的任务
initialDelay - 延迟第一次执行的时间 (就是第一次指定定时延时多久),代码里我延时10秒
delay - 一个执行终止与下一个执行的开始之间的延迟
unit - initialDelay和delay参数的时间单位
public class ScheduledExecutorServiceTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("任务!!!!");
}
},10000,1000, TimeUnit.MILLISECONDS);
}
}
总结:看完有什么不懂的欢迎在下方留言评论!
面试腾讯,字节跳动首先要掌握的Java多线程,一次帮你全掌握!的更多相关文章
- 面试利器!字节跳动2021年Android程序员面试指导小册已开源
整份手册分为两个部分,分别是:Java部分.Android部分.数据结构与算法篇.字节跳动2020年全年面试题总结篇! 每个知识点都有左侧导航书签页,看的时候十分方便,由于内容较多,这里就截取一部分图 ...
- 面试大厂必看!就凭借这份Java多线程和并发面试题,我拿到了字节和美团的offer!
最近好多粉丝私信我说在最近的面试中老是被问到多线程和高并发的问题,又对这一块不是很了解,很简单就被面试官给问倒了,被问倒的后果当然就是被刷下去了,因为粉丝要求,我最近也是花了两天时间 给大家整理了这一 ...
- 史上最全!2020面试阿里,字节跳动90%被问到的JVM面试题(附答案)
前言:最近老是收到小伙伴的私信问我能不能帮忙整理出一份JVM相关的面试题出来,说自己在大厂去面试的时候这一块问的是特别多的,每次自己学的时候每次都学不到重点去.这不他来了,一份详细的JVM面试真题给大 ...
- 面试阿里,字节跳动90%会被问到的Java异常面试题集,史上最全系列!
Java异常架构与异常关键字 Java异常简介 Java异常是Java提供的一种识别及响应错误的一致性机制. Java异常机制可以使程序中异常处理代码和正常业务代码分离,保证程序代码更加优雅,并提高程 ...
- 面试阿里,字节跳动99%会被问到的java线程和线程池,看完这篇你就懂了!
前言: 最近也是在后台收到很多小伙伴私信问我线程和线程池这一块的问题,说自己在面试的时候老是被问到这一块的问题,被问的很头疼.前几天看到后帮几个小伙伴解决了问题,但是问的人有点多我一个个回答也回答不过 ...
- 深度分享:面试阿里,字节跳动,美团90%会被问到的HashMap知识
一,HashTable 哈希表,它相比于hashMap结构简单点,它没有涉及红黑树,直接使用链表的方式解决哈希冲突. 我们看它的字段,和hashMap差不多,使用table存放元素 private t ...
- 面试阿里,字节跳动,华为必须知道的Java创建对象的5种方式
Java创建对象的5种方式 1.直接new,调用了构造器2.通过clone(),没有调用构造器3.通过反射,调用了构造器4.通过反序列化,没有调用构造器5.通过Unsafe类的allocateInst ...
- 深度分析:面试阿里,字节跳动,美团90%被问到的List集合,看完还不懂算我输
1 List集合 1.1 List概述 在Collection中,List集合是有序的,可对其中每个元素的插入位置进行精确地控制,可以通过索引来访问元素,遍历元素. 在List集合中,我们常用到Arr ...
- 深度分析:面试阿里,字节99%会被问到Java类加载机制和类加载器
1. 类加载机制 所谓类加载机制就是JVM虚拟机把Class文件加载到内存,并对数据进行校验,转换解析和初始化,形成虚拟机可以直接使用的Jav类型,即Java.lang.Class. 2. 类加载的过 ...
随机推荐
- ValidatorException-异常
Java在发送Https请求的时候,不可避免的会发生SSL证书认证失败 错误信息:sun.security.validator.ValidatorException: PKIX path buildi ...
- user.ini Operation not permitted
rm: cannot remove '/public/.user.ini': Operation not permitted chattr -i .user.ini rm -f .user.ini
- Camera2使用textureView支持
SurfaceView 绘制会有独立窗口, TextureView 没有独立的窗口,可以像普通的 View 一样,更高效更方便 public class MainActivity extends Ap ...
- 全球最火的程序员学习路线!没有之一!3天就在Github收获了接近1w点赞
大家好,我是G哥,目前人在荆州办事,但是干货还是要安排上! 国外有一个爆火的开发人员学习路线,目前已经在 Github收获了 131 k+ star,Star 数量在 Github 所有仓库中排名第 ...
- 4G DTU和4G工业路由器有哪些区别?
DTU的英文全称是Data Transfer unit,是一种专门用来将将IP数据转换为串口数据或者是将串口数据转换为IP数据并且通过无线通信网络将数据进行传送的无线终端设备.DTU也可以实现无线网络 ...
- rs485通讯模块有什么作用
rs485通讯模块是什么 rs485通讯模块我们可以分为几个部分来理解,rs485简单来说就是一个硬件,是一个物理接口.而这个接口要进行数据传输通讯,就需要采用网络协议和远端的服务器或者是其它网络设备 ...
- Java学习的第十七天
1.静态变量 静态方法 静态代码块 2.今天没问题 3.明天学习abstract和综合实例
- Vue、Node全栈项目~面向小白的博客系统~
个人博客系统 前言 ❝ 代码质量问题轻点喷(去年才学的前端),有啥建议欢迎联系我,联系方式见最下方,感谢! 页面有啥bug也可以反馈给我,感谢! 这是一套包含前后端代码的个人博客系统,欢迎各位提出建议 ...
- php的三元运算符
简单记录一哈php的三元运算符的用法: 啥子是三元运算,即第一个表达式作为判断条件,在后面两个表达式中选择一个执行. 若判断成立,则执行第二个表达式,否则执行第三个表达式. 看到好多网友都说的不 ...
- C# 泛型的协变
class Program { static void Main(string[] args) { Person person = new Person(); Person chinese2 = ne ...