Java多线程超级详解(看这篇就足够了)
yuyutoo 2024-10-12 01:02 5 浏览 0 评论
多线程能够提升程序性能,也属于高薪必能核心技术栈,本篇会全面详解Java多线程 @mikechen
主要会详解以下六大点:
基本概念
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
进程
在操作系统中运行的程序就是进程,比如你的QQ、播放器、游戏、IDE等等
线程
一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕,等等。
多线程
多线程:多个线程并发执行。
同步
Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。
比如:synchronized关键字,在保证结果准确的同时,提高性能,线程安全的优先级高于性能。
并行
多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发
通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。
并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程的生命周期
在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态
- 新建状态:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值
- 就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
- 运行状态:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
- 阻塞状态:当处于运行状态的线程失去所占用资源之后,便进入阻塞状态
- 死亡状态:线程在run()方法执行结束后进入死亡状态。此外,如果线程执行了interrupt()或stop()方法,那么它也会以异常退出的方式进入死亡状态。
线程状态的控制
可以对照上面的线程状态流转图来看具体的方法,这样更清楚具体作用:
1.start()
启动当前线程, 调用当前线程的run()方法
2.run()
通常需要重写Thread类中的此方法, 将创建的线程要执行的操作声明在此方法中
3.yield()
释放当前CPU的执行权
4.join()
在线程a中调用线程b的join(), 此时线程a进入阻塞状态, 知道线程b完全执行完以后, 线程a才结束阻塞状态
5.sleep(long militime)
让线程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态
6.wait()
一旦执行此方法,当前线程就会进入阻塞,一旦执行wait()会释放同步监视器。
7.sleep()和wait()的异同
相同点:两个方法一旦执行,都可以让线程进入阻塞状态。
不同点:
1) 两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
2) 调用要求不同:sleep()可以在任何需要的场景下调用。wait()必须在同步代码块中调用。
2) 关于是否释放同步监视器:如果两个方法都使用在同步代码块呵呵同步方法中,sleep不会释放锁,wait会释放锁。
8.notify()
一旦执行此方法,将会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先度最高的。
9.notifyAll()
一旦执行此方法,就会唤醒所有被wait的线程 。
10.LockSupport
LockSupport.park()和LockSupport.unpark()实现线程的阻塞和唤醒的。
多线程的5种创建方式
1.继承Thread类
package com.mikechen.java.multithread;
/**
* 多线程创建:继承Thread
*
* @author mikechen
*/
class MyThread extends Thread {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
}
}
2.实现Runnable接口
package com.mikechen.java.multithread;
/**
* 多线程创建:实现Runnable接口
*
* @author mikechen
*/
public class MyRunnable implements Runnable {
private int i = 0;
@Override
public void run() {
for (i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
Thread thread = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
thread.start();
}
}
3.线程池创建
线程池:其实就是一个可以容纳多个线程的容器,其中的线程可以反复的使用,省去了频繁的创建线程对象的操作,无需反复创建线程而消耗过多的系统资源。
package com.mikechen.java.multithread;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* 多线程创建:线程池
*
* @author mikechen
*/
public class MyThreadPool {
public static void main(String[] args) {
//创建带有5个线程的线程池
//返回的实际上是ExecutorService,而ExecutorService是Executor的子接口
Executor threadPool = Executors.newFixedThreadPool(5);
for(int i = 0 ;i < 10 ; i++) {
threadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+" is running");
}
});
}
}
}
核心参数
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
{
....
}
从上图可以看出,提交任务之后,首先会尝试着交给核心线程池中的线程来执行,但是必定核心线程池中的线程数有限,所以必须要由任务队列来做一个缓存,先将任务放队列中缓存,然后等待线程去执行。
最后,由于任务太多,队列也满了,这个时候线程池中剩下的线程就会启动来帮助核心线程池执行任务。
如果还是没有办法正常处理新到的任务,则线程池只能将新提交的任务交给饱和策略来处理了。
4.匿名内部类
适用于创建启动线程次数较少的环境,书写更加简便
package com.mikechen.java.multithread;
/**
* 多线程创建:匿名内部类
*
* @author mikechen
*/
public class MyThreadAnonymous {
public static void main(String[] args) {
//方式1:相当于继承了Thread类,作为子类重写run()实现
new Thread() {
public void run() {
System.out.println("匿名内部类创建线程方式1...");
};
}.start();
//方式2:实现Runnable,Runnable作为匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("匿名内部类创建线程方式2...");
}
} ).start();
}
}
5.Lambda表达式创建
package com.mikechen.java.multithread;
/**
* 多线程创建:lambda表达式
*
* @author mikechen
*/
public class MyThreadLambda {
public static void main(String[] args) {
//匿名内部类创建多线程
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"mikchen的互联网架构创建新线程1");
}
}.start();
//使用Lambda表达式,实现多线程
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"mikchen的互联网架构创建新线程2");
}).start();
//优化Lambda
new Thread(()-> System.out.println(Thread.currentThread().getName()+"mikchen的互联网架构创建新线程3")).start();
}
}
线程的同步
线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏,线程的同步是保证多线程安全访问竞争资源的一种手段。
1.普通同步方法
锁是当前实例对象 ,进入同步代码前要获得当前实例的锁。
/**
* 用在普通方法
*/
private synchronized void synchronizedMethod() {
System.out.println("--synchronizedMethod start--");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("--synchronizedMethod end--");
}
2.静态同步方法
锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁。
/**
* 用在静态方法
*/
private synchronized static void synchronizedStaticMethod() {
System.out.println("synchronizedStaticMethod start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synchronizedStaticMethod end");
}
3.同步方法块
锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
/**
* 用在类
*/
private void synchronizedClass() {
synchronized (SynchronizedTest.class) {
System.out.println("synchronizedClass start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synchronizedClass end");
}
}
4.synchronized底层实现
synchronized的底层实现是完全依赖JVM虚拟机的,所以谈synchronized的底层实现,就不得不谈数据在JVM内存的存储:Java对象头,以及Monitor对象监视器。
1.Java对象头
在JVM虚拟机中,对象在内存中的存储布局,可以分为三个区域:
- 对象头(Header)
- 实例数据(Instance Data)
- 对齐填充(Padding)
Java对象头主要包括两部分数据:
1)类型指针(Klass Pointer)
是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
2)标记字段(Mark Word)
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等,它是实现轻量级锁和偏向锁的关键.
所以,很明显synchronized使用的锁对象是存储在Java对象头里的标记字段里。
2.Monitor
monitor描述为对象监视器,可以类比为一个特殊的房间,这个房间中有一些被保护的数据,monitor保证每次只能有一个线程能进入这个房间进行访问被保护的数据,进入房间即为持有monitor,退出房间即为释放monitor。
下图是synchronized同步代码块反编译后的截图,可以很清楚的看见monitor的调用。
使用syncrhoized加锁的同步代码块在字节码引擎中执行时,主要就是通过锁对象的monitor的取用(monitorenter)与释放(monitorexit)来实现的。
多线程引入问题
多线程的优点很明显,但是多线程的缺点也同样明显,线程的使用(滥用)会给系统带来上下文切换的额外负担,并且线程间的共享变量可能造成死锁的出现。
1.线程安全问题
1)原子性
在并发编程中很多的操作都不是原子操作,比如:
i++; // 操作2
i = j; // 操作3
i = i + 1; // 操作4
xxxxxxxxxxbr i++; // 操作2bri = j; // 操作3bri = i + 1; // 操作4
在单线程环境中这3个操作都不会出现问题,但是在多线程环境中,如果不通过加锁操作,往往很可能会出现意料之外的值。
在java中可以通过synchronized或者ReentrantLock来保证原子性。
2)可见性
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即得到这个修改的值。
如上图所示,每个线程都有自己的工作内存,工作内存和主存间要通过store和load进行交互。
为了解决多线程的可见性问题,java提供了volatile关键字,当一个共享变量被volatile修饰时,他会保证修改的值会立即更新到主存,当有其他线程需要读取时,他会去主存中读取新值,而普通共享变量不能保证其可见性,因为变量被修改后刷回到主存的时间是不确定的。
2.线程死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。
当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁,如图所示:
举一个例子:
public void add(int m) {
synchronized(lockA) { // 获得lockA的锁
this.value += m;
synchronized(lockB) { // 获得lockB的锁
this.another += m;
} // 释放lockB的锁
} // 释放lockA的锁
}
public void dec(int m) {
synchronized(lockB) { // 获得lockB的锁
this.another -= m;
synchronized(lockA) { // 获得lockA的锁
this.value -= m;
} // 释放lockA的锁
} // 释放lockB的锁
}
xxxxxxxxxxbr public void add(int m) {br synchronized(lockA) { // 获得lockA的锁br this.value += m;br synchronized(lockB) { // 获得lockB的锁br this.another += m;br } // 释放lockB的锁br } // 释放lockA的锁br}brbrpublic void dec(int m) {br synchronized(lockB) { // 获得lockB的锁br this.another -= m;br synchronized(lockA) { // 获得lockA的锁br this.value -= m;br } // 释放lockA的锁br } // 释放lockB的锁br}
两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。
3.上下文切换
多线程并发一定会快吗?其实不一定,因为多线程有线程创建和线程上下文切换的开销。
CPU是很宝贵的资源,速度也非常快,为了保证均衡,通常会给不同的线程分配时间片,当CPU从一个线程切换到另外一个线程的时候,CPU需要保存当前线程的本地数据,程序指针等状态,并加载下一个要执行的线程的本地数据,程序指针等,这个切换称之为上下文切换。
一般减少上下文切换的方法有:无锁并发编程,CAS算法,使用协程等方式。
多线程用好了可以成倍的增加效率,用不好可能比单线程还慢。
以上!
更多架构技术干货,私信【架构】即可查看我原创的300期+BAT架构技术系列文章与1000+大厂面试题答案合集。
相关推荐
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
-
Mysql和Oracle实现序列自增/*ORACLE设置自增序列oracle本身不支持如mysql的AUTO_INCREMENT自增方式,我们可以用序列加触发器的形式实现,假如有一个表T_WORKM...
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
-
概述今天主要简单介绍一下Oracle12c的一些新特性,仅供参考。参考:http://docs.oracle.com/database/121/NEWFT/chapter12102.htm#NEWFT...
- MySQL CREATE TABLE 简单设计模板交流
-
推荐用MySQL8.0(2018/4/19发布,开发者说同比5.7快2倍)或同类型以上版本....
- mysql学习9:创建数据库(mysql5.5创建数据库)
-
前言:我也是在学习过程中,不对的地方请谅解showdatabases;#查看数据库表createdatabasename...
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
-
执行"CREATETABLE新表ASSELECT*FROM原表;"后,新表与原表的字段一致,但主键、索引不会复制到新表,会把原表的表记录复制到新表。...
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
-
在街上看到的PandaDunk的超载可能让一些球鞋迷们望而却步,但Dunk的浪潮仍然强劲,看不到尽头。我们看到的很多版本都是为女性和儿童制作的,这种新配色为后者引入了一种令人耳目一新的新选择,而...
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
-
多功能雷达AN/SPY-1的特性和技术能力,该雷达已经在美国海军服役了30多年,其修改-AN/SPY-1A、AN/SPY-1B(V)、AN/SPY-1D、AN/SPY-1D(V),以及雷神...
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
-
全面分析汽车音响使用或安装技术常识一:主机是大多数人最熟习的音响器材,有关主机的各种性能及规格,也是耳熟能详的事,以下是一些在使用或安装时,比较需要注意的事项:LOUDNESS:几年前的主机,此按...
- 【推荐】ProAc Response系列扬声器逐个看
-
有考牌(公认好声音)扬声器之称ProAcTablette小音箱,相信不少音响发烧友都曾经,或者现在依然持有,正当大家逐渐掌握Tablette的摆位设定与器材配搭之后,下一步就会考虑升级至表现更全...
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
-
作者:初吻给了烟sco混迹张大妈时日不短了,手没少剁。家里有了汪星人,吸尘器使用频率相当高,偶尔零星打扫用卧式的实在麻烦(汪星人:你这分明是找借口,我掉毛是满屋子都有,铲屎君都是用卧式满屋子吸的,你...
- 专题|一个品牌一件产品(英国篇)之Quested(罗杰之声)
-
Quested(罗杰之声)代表产品:Q212FS品牌介绍Quested(罗杰之声)是录音监听领域的传奇品牌,由英国录音师RogerQuested于1985年创立。在成立Quested之前,Roger...
- 常用半导体中英对照表(建议收藏)(半导体英文术语)
-
作为一个源自国外的技术,半导体产业涉及许多英文术语。加之从业者很多都有海外经历或习惯于用英文表达相关技术和工艺节点,这就导致许多英文术语翻译成中文后,仍有不少人照应不上或不知如何翻译。为此,我们整理了...
- Fyne Audio F502SP 2.5音路低音反射式落地音箱评测
-
FyneAudio的F500系列,有新成员了!不过,新成员不是新的款式,却是根据原有款式提出特别版。特别版产品在原有型号后标注了SP字样,意思是SpecialProduction。Fyne一共推出...
- 有哪些免费的内存数据库(In-Memory Database)
-
以下是一些常见的免费的内存数据库:1.Redis:Redis是一个开源的内存数据库,它支持多种数据结构,如字符串、哈希表、列表、集合和有序集合。Redis提供了快速的读写操作,并且支持持久化数据到磁...
- RazorSQL Mac版(SQL数据库查询工具)
-
RazorSQLMac特别版是一款看似简单实则功能非常出色的SQL数据库查询、编辑、浏览和管理工具。RazorSQLformac特别版可以帮你管理多个数据库,支持主流的30多种数据库,包括Ca...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
-
- Mysql和Oracle实现序列自增(oracle创建序列的sql)
- 关于Oracle数据库12c 新特性总结(oracle数据库19c与12c)
- MySQL CREATE TABLE 简单设计模板交流
- mysql学习9:创建数据库(mysql5.5创建数据库)
- MySQL面试题-CREATE TABLE AS 与CREATE TABLE LIKE的区别
- Nike Dunk High Volt 和 Bright Spruce 预计将于 12 月推出
- 美国多功能舰载雷达及美国海军舰载多功能雷达系统技术介绍
- 汽车音响怎么玩,安装技术知识(汽车音响怎么玩,安装技术知识视频)
- 【推荐】ProAc Response系列扬声器逐个看
- #本站首晒# 漂洋过海来看你 — BLACK&DECKER 百得 BDH2000L无绳吸尘器 开箱
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)