【Java SE】多线程
1.1 线程的生命周期

| 方法名 | 说明 |
|---|---|
| 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)充当同步监视器,类本身也是一个对象。

方式二:同步方法
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 ...
随机推荐
- 测试Lock锁
package com.company;import java.util.concurrent.locks.ReentrantLock;//测试Lock锁public class TestLock i ...
- Windows 解决teamview远程必须mstsc连接
真实原因是你的TeamViewer一直在用远程桌面的ID进行登录,所以一旦远程桌面断开,TeamViewer就无法连接了.因此我们只需要切换为服务器的TeamViewer ID即可,服务器的TeamV ...
- sql server 自动核算
USE tempdb; CREATE TABLE #temptable ( [姓名] NVARCHAR(255), [加班日期] DATE, [加班时长] DECIMAL(8, 2) ); INSER ...
- unittest框架基本使用
1.简介 unittest是python内置的单元测试框架,具备编写用例.组织用例.执行用例.输出报告等自动化框架的条件.使用unittest前需要了解该框架的五个概念: 即test case,tes ...
- Net6读取AppSettings.json
1.创建Helper类 public class AppHelper { private static IConfiguration _config; public AppHelper(IConfig ...
- C#向其实进程子窗体发送指令
近日,想在自己的软件简单控制其它软件的最大化最小化,想到直接向进程发送指令,结果一直无效,经过Spy++发现,原来快捷方式在子窗体上,所以需要遍历子窗体在发送指令,以下为参考代码: 1 [DllImp ...
- 01.html大致主体格式
<!DOCTYPE html> 不是HTML标签,就是文档声明标签 告诉浏览器使用哪种html版本来显示网页,其必须在文档中的最前面位置,要放在<html>标签之前, < ...
- Matlab笔记--Matlab概述(初登场)
Matlab概述 安装MATLAB教程 可以参考这里:https://www.cnblogs.com/sixuwuxian/p/15858196.html Matlab的启动 右键图标,选择属性,可以 ...
- Gym - 101845E (图形转换思维)
题意:给你个边长为n(1 <= n <= 50)的下图这种三角形,图形所有点构成集合.找多少对a,b满足条件,条件为:ab两点之间还有其他点. 题解:刚开始以为直接找规律就行,wa了两次发 ...
- Linux & 标准C语言学习 <DAY5>
一.if分支语句 if(表达式) //单分支语句 { //表达式的值为真,则执行此处代码 } if(表达式) //双分支语句 { ...