【Java SE】多线程
1.1 线程的生命周期
![](file://D:\资料\学习笔记\Java\多线程\1.png?msec=1648087619803)
方法名 | 说明 |
---|---|
yield() | |
stop() | |
sleep() | |
wait() | 阻塞 |
suspend() | 挂起 |
notify()/notifyAll() | 唤醒 |
resume() | 取消挂起 |
1.2 线程的安全问题
1.2.1 通过同步机制解决线程安全问题
方式一:同步代码块
synchronized(同步监视器) {
//需要被同步的代码
}
class Window implements Runnable {
private int tickets = 100;
private Object obj = new Object();//多线程共用同一把锁,即是同步监视器
@Override
public void run() {
while (true) {
synchronized (obj) {
if (tickets > 0) {
System.out.println("售票,票号为:" + tickets);
tickets--;
} else {
break;
}
}
}
}
}
说明:操作共享数据的代码,即为需要被同步的代码。
同步监视器,俗称锁。任何类的对象,都可以充当锁。但要求多个线程必须共用同一把锁。在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。在继承Thread创建多线程的方式中,可以使用当前类(Bank.class)充当同步监视器,类本身也是一个对象。
![](file://D:\资料\学习笔记\Java\多线程\2.png?msec=1648087619803)
方式二:同步方法
1.同步方法解决实现Runnable接口的线程创建方式的线程安全问题
@Override
public void run() {
while(true) {
show();
}
}
private synchronized void show() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--;
}
此时同步方法隐藏使用this作为同步监视器(锁)
2.同步方法解决实现继承Thread的线程创建方式的线程安全问题
@Override
public void run() {
while(true) {
show();
}
}
private static synchronized void show() {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + tickets);
tickets--;
}
}
此时同步监视器为当前的类
1.2.2 线程同步解决单例模式懒汉式的线程安全问题
方式一:效率稍差
public class Bank {
private static Bank bank = null;
private Bank() {
}
public static Bank getBank() {
synchronized (Bank.class) {
if (bank == null)
bank = new Bank();
return bank;
}
}
}
方式二:效率更高
public class Bank {
private static Bank bank = null;
private Bank() {
}
public static Bank getBank() {
if(bank == null) {
synchronized (Bank.class) {
if (bank == null)
bank = new Bank();
}
}
return bank;
}
}
1.3 线程的死锁问题 DeadLock
不同线程分别占用对方需要同步的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。出现死锁,不会出现异常,不会出现提示,只是所有的线程都处于堵塞状态,无法继续。
public class theadsTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread() {
@Override
public void run() {
synchronized (s1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
s1.append("a");
s2.append("1");
synchronized (s2) {
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2) {
s1.append("c");
s2.append("3");
synchronized (s1) {
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
解决方法
专门的算法、原则
尽量减少共享资源的使用
尽量避免嵌套同步
1.4 Lock锁解决线程安全问题 jdk5.0新增
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}finally {
lock.unlock();
}
}
}
lock方法默认this为同步监视器,因此线程创建方式只能使用实现Runnable接口的方式,继承的方式需要加static。synchronized在执行完同步代码后会手动释放同步监视器,lock方式需要手动启动同步(lock()),同时结束同步也需要
优先使用顺序:Lock->同步代码块(已经进入方法体,分配了相应资源)->同步方法(在方法体之外)
1.4 线程的通信
@Override
public void run() {
while(true) {
synchronized (this) {
notify();
if (num <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + num);
num++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
else {
break;
}
}
}
}
线程一首先获得同步监视器(锁),执行输出后wait()进入了阻塞状态,释放了手中的锁,随后线程二执行notify()唤醒线程一,线程二获得锁进入执行输出(线程一此时没有锁不能够执行),随后线程二wait()进入堵塞状态释放手中的锁,进程一获得锁后执行notify唤醒线程二......以此实现交叉输出。
线程通信方法 | 说明 |
---|---|
wait() | 当前线程进入堵塞状态并释放同步监视器 |
notify() | 唤醒一个wait的线程,若多个线程处于堵塞状态则唤醒优先级高的哪一个。 |
notifyAll() | 唤醒所有的线程。 |
三个方法必须使用在同步代码块或者同步方法中,且三个方法的调用者必须是同步代码块或者同步方法的同步检测器,因此synchronized (this)参数不能为类或者obj。否则会出现IllegalMonitorStateException,或者写为obj.wait()。此外三个方法定义在java,lang.object中。
面试题 sleep() 和 wait()方法的异同
相同点:都能够使线程堵塞。
不同点:①Thread中定义的sleep,Object中定义的wait。
②wait只能使用在同步代码块和同步方法中,由同步检测器调用。
③wait会释放线程的同步检测器,sleep则不会。
生产者消费者问题
package com.hikaru.exer;
class Clerk {
private static int num = 0;
public synchronized void product() {
if(num < 20) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
System.out.println(Thread.currentThread().getName() + "正在生产第" + num + "产品...");
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void consume() {
if(num > 0) {
// try {
// Thread.sleep(500);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + "正在消费第" + num + "产品...");
num--;
notify();
}else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
private Clerk clerk;
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(this.getName() + "开始生产产品...");
while(true) {
clerk.product();
}
}
}
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(this.getName() + "开始消费产品...");
while(true) {
clerk.consume();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Customer c1 = new Customer(clerk);
p1.setName("生产者一");
c1.setName("消费者一");
c1.start();
p1.start();
}
}
1.5 通过实现Callable接口新增线程 jdk5.0新增
优点:①call方法相比run()方法,可以有返回值
②方法可以抛出异常
③支持泛型的返回值
④需要借助FutureTask类,比如获取返回结果
FutureTask
FutureTask是Future的唯一实现类,可以对Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。FutureTask同时实现了Runnable、Callable接口,它既可作为Runnable被线程执行,又可作为Future得到Callable的结果。
NumThread numThread = new NumThread();//创建Callable接口实现类
FutureTask futureTask = new FutureTask(numThread);
Thread t1 = new Thread(futureTask);//作为Runnable被线程执行
t1.start();
try {
System.out.println(futureTask.get());//作为Future得到Callable的结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
1.6 使用线程池创建线程
经常创建和销毁线程、使用量特别大的资源,比如并发情况下的线程,对性能的影响很大。提前创建好多个线程放入线程池中,使用时直接获取,用完放回线程池。能够提高响应速度,降低资源消耗,便于资源管理。
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new NumThread());//适用于Runnable
// service.submit();//适用于Callable
service.shutdown();
}
ExecutorServic
真正的连接池接口。常见子类ThreadPoolExecutor
execute(Runnable command) | 执行命令,没有返回值,一般用来执行Runnable |
submit(Callable task) | 执行命令,有返回值,一般用来执行Callable |
shutdown | 关闭连接池 |
Executors
工具类、线程池的工厂类,用于创建返回不同类型的线程池
【Java SE】多线程的更多相关文章
- Java SE之快速失败(Fast-Fail)与快速安全(Fast-Safe)的区别[集合与多线程/增强For](彻底详解)
声明 特点:基于JDK源码进行分析. 研究费时费力,如需转载或摘要,请显著处注明出处,以尊重劳动研究成果:博客园 - https://www.cnblogs.com/johnnyzen/p/10547 ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java SE 简介 & 环境变量的配置
Java SE 简介 & 环境变量的配置 一.Java 技术的三个方向 Java 技术分为三个方向 javaSE( Java Platform Standard Edition 标准版)用来开 ...
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- 【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结
虽然看过一些Java 8新特性的资料,但是平时很少用到,时间长了就忘了,正好借着Java 9的发布,来总结下一些Java 8中的新特性. 接口中的默认方法和静态方法 先考虑一个问题,如何向Java中的 ...
- Java SE教程
第0讲 开山篇 读前介绍:本文中如下文本格式是超链接,可以点击跳转 >>超链接<< 我的学习目标:基础要坚如磐石 代码要十份规范 笔记要认真详实 一.java内容介绍 ...
- [零基础学JAVA]Java SE基础部分-01. Java发展及JDK配置
转自:http://redking.blog.51cto.com/27212/114976 重点要会以下两个方面: 1. 抽象类与接口 2. API==>类集 这是两个最重要部分,这两个部分理解 ...
- Java中多线程并发体系知识点汇总
一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...
- Java SE 第二篇
二. Java SE 第二篇 1. Arrays 数组 // 声明一维数组,[]内不允许有值 int[] arr; int arr[]; // 创建一维数组对象,[]内必须有值 arr = new ...
- Java SE 开篇
一. Java SE 开篇 1. Java 基本数据类型及其对应的包装类 基本数据类型 对应的包装类 * byte Byte * boolean Boolean * char Character ...
随机推荐
- jetson TX2 + opencv3.4 + python3 + 双目 +人脸检测
淘宝看到一款很便宜的双目,150元,就买了.想着用它学习一下opencv,好换个工作.当然,也想着能否用它做一些好玩的,比如三维重建之类高大上的东西.先用便宜的入个门,等以后眼界高了再看是不是买那些更 ...
- JVM - 1.内存结构
1 内存结构 1.1 程序计数器 1.1.1 作用 在执行的过程中 , 记住下一条jvm指令的执行地址 物理上通过寄存器实现 1.1.2 特性 每个线程都有自己的程序计数器 - 线程私有 不会存在内存 ...
- C# 事件 初学
1.事件是可以动态添加和删除操作的,如下>>> 添加: ctr.Click+=ctr_Click; 删除:ctr.Click-=ctr_Click; 2.同一控件相同事件可累加不同事 ...
- Hihocoder 1067
最近公共祖先二 离线算法 /**/ #include <cstdio> #include <cstring> #include <cmath> #include & ...
- commons-lang3
字符串的处理类(StringUtils) //判断是否为空(注:isBlank与isEmpty 区别) StringUtils.isBlank(null);StringUtils.isBlank(&q ...
- 九九乘法表打印记一次al面试
for (int i = 1; i <= 9; i++) { for (int j = 1; j <= i; j++) { System.out.print(i + "x&quo ...
- 数据库结构差异比较-SqlServer
/****** Object: StoredProcedure [dbo].[p_comparestructure_2005] Script Date: 2022/10/8 10:00:20 **** ...
- win10修复系统
DISM.exe /Online /Cleanup-image /Restorehealth sfc /scannow
- vue 作者在2022-2-7起宣布 vue3 正式作为默认版本
vue 作者在2022-2-7起宣布 vue3 正式作为默认版本 vue 作者尤雨溪在知乎上发布一篇文章,宣布 Vue3 将在 2022 年 2 月 7 日 成为新的默认版本! 并且还在文章中做出了一 ...
- redis的数据操作和python操作redis+关系非关系数据库差异
关系型数据库(RMDBS) 数据库中表与表的数据之间存在某种关联的内在关系,因为这种关系,所以我们称这种数据库为关系型数据库. 典型:Mysql/MariaDB.postgreSQL.Oracle.S ...