【Java】MultiThread 多线程 Re01
学习参考:
https://www.bilibili.com/video/BV1ut411T7Yg
一、线程创建的四种方式:
1、集成线程类
/**
* 使用匿名内部类实现子线程类,重写Run方法
*/
static class CustomThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("custom subThread2 is running " + new Date().getTime());
}
}
}
执行
Thread thread2 = new CustomThread();
thread.start();
2、实现Runnable接口
/**
* 或者重写Runnable接口完成
*/
static class RunnableImpl implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) System.out.println("custom RunnableImpl is running " + new Date().getTime());
}
}
执行
Thread thread2 = new Thread(new RunnableImpl(), "custom RunnableThread");
thread2.start();
3、实现Callable接口
/**
* 实现Callable接口方式
*/
static class CustomCallable implements Callable<String> {
@Override
public String call() {
for (int i = 0; i < 10; i++) System.out.println(Thread.currentThread().getName() + " execute" + new Date().getTime() + " loopCount " + i);
return "CustomCallable execute finish";
}
}
执行代码
Callable接口
FutureTask<String> futureTask = new FutureTask<>(new CustomCallable());
Thread thread = new Thread(futureTask, "futureTask");
thread.start();
for (int i = 0; i < 10; i++) System.out.println("main thread is running " + System.currentTimeMillis());
boolean stopFlag = false;
if (stopFlag) {
// 可以中断执行
futureTask.cancel(stopFlag);
}
// boolean cancelled = futureTask.isCancelled();
try {
// 判断是否执行完成
if (futureTask.isDone()) { // 如果执行完成,可以获取Callable返回的结果
String s = futureTask.get();
System.out.println("thread execute finish, result => " + s);
}
} catch (Exception exception) {
exception.printStackTrace();
}
4、使用线程池,只需要提供Runnable接口的实现实例
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
fixedThreadPool.execute(() -> {
for (int i = 0; i < 10; i++) System.out.println("custom Runnable-N-Impl is running " + new Date().getTime());
});
四种线程方式的总结:
- 接口适合多个相同的程序代码的线程 共享一个资源
- 接口避免了Java单继承的局限性
- 接口代码可以被多个线程共享,代码和线程是独立的
- 线程池只能放入实现Runnable或者Callable接口的线程,不能直接放入继承Thread的类
Java程序运行至少有几个线程?
至少两个线程,一个Main主线程,一个GC垃圾回收线程
相同点:
- 都是接口定义
- 都可以用来编写多线程程序
- 都需要调用Thread.start()来启动线程
不同点:
- Runnable是重写run方法,该方法的返回类型是void,和Thread一样,不能返回结果
- Callable是重写call方法,该方法的返回类型通过泛型决定,默认Object 在FutureTask对象的get方法返回
- Callable允许使用FutureTask对象的cancel方法,取消执行,Runnable则没有对应的方法可以取消 注意事项:
FutureTask对象的get方法会阻塞Main线程,直到获取方法结果获取
- 创建阶段 Thread对象已经被new分配内存资源
- 就绪阶段 Thread对象调用start()方法 JVM为线程对象创建方法栈和程序计数器,等待线程调度器调度
- 运行阶段 就绪状态的线程获取CPU资源 开始运行run方法
- 阻塞阶段
1、主动调用sleep方法 主动放弃CPU资源,该线程线程阻塞
2、调用了阻塞式的IO方法 知道该方法返回之前该线程一直阻塞
3、获取同步锁(同步监视器)但是该同步锁被其他线程所持有
4、线程正在等待某个通知 (调用了notify方法)
5、调用线程挂起方法suspend(),该方法容易造成死锁,避免使用
- 死亡阶段
1、run() 或 call() 方法执行完成,线程结束
2、线程抛出未捕获的Exception或者Error
3、调用该线程stop()方法结束该线程,但是容易早曾死锁,避免使用
线程安全问题:
售票代码实例:
public class ThreadSecurity {
public static final String TICKET_WINDOW_1 = "12306 TicketWindow1";
public static final String TICKET_WINDOW_2 = "12306 TicketWindow2";
public static final String TICKET_WINDOW_3 = "12306 TicketWindow3"; static class Ticket implements Runnable {
private int ticketTotal = 100;
@Override
public void run() {
while (true) {
if (ticketTotal > 0) {
try {
Thread.sleep(100);
} catch (Exception exception) {
exception.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + " 剩余票数 " + ticketTotal);
ticketTotal --;
}
}
}
}
/**
* 线程安全问题
* 1、多个线程同事执行写操作,需要考虑线程同步
* 2、对于全局变量不需要写入操作,仅读取,该变量线程安全
*
* - 代码执行过程,同一个变量值多个线程执行好几回
* - 出现小于1的情况
*/
public void threadProblem() {
Ticket ticket = new Ticket();
Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1);
Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2);
Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3);
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
}
public static void main(String[] args) {
new ThreadSecurity().threadProblem();
}
}
执行后,会出现三个窗口都在售卖同一张票的情况
或者票已经卖完了,窗口任然在售卖的问题
二、解决线程安全的方式:
/**
* 线程安全问题的解决办法:
* 线程修改共享资源的时候其他线程必须等待当前执行的线程修改完逼同步之后,才能抢夺CPU资源执行对应的操作
* 这样保证数据的同步性,解决线程不安全的现象
*
* Java提供了7种线程同步机制
* 1、同步代码块
* 2、同步方法
* 3、同步锁 ReenreantLock
* 4、特殊域变量 volatile
* 5、局部变量 ThreadLocal
* 6、阻塞队列 LinkedBlockingQueue
* 7、原子变量 Atomic
*/
1、使用同步代码块来解决:
static class TicketLocked implements Runnable {
private int ticketTotal = 100;
private Object lockKey = new Object();
@Override
public void run() {
while (true) {
// 使用同步代码块 需要一个锁对象,线程执行必须通过【开锁】的方式执行代码块内容,以保证变量写入正确
synchronized (lockKey) {
if (ticketTotal > 0) {
try {
Thread.sleep(100);
} catch (Exception exception) {
exception.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + " 剩余票数 " + ticketTotal);
ticketTotal --;
}
}
}
}
}
执行部分:
public void threadSync() {
TicketLocked ticket = new TicketLocked();
Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1);
Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2);
Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3);
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
}
2、使用同步方法解决
原理和同步代码块的原理是一致的
锁对象不再使用随意变量,而是this当前对象
static class TicketLocked2 implements Runnable {
private int ticketTotal = 100;
@Override
public void run() {
while (true) {
saleTicket();
}
} /**
* 使用 synchronized 修饰方法
* 原理和 synchronized代码块是一样的
* 只是同步锁这个对象直接使用的是当前对象 this
* 如果是static方法,则是这个类对象 this.getClass()
*/
private synchronized void saleTicket() {
if (ticketTotal > 0) {
try {
Thread.sleep(100);
} catch (Exception exception) {
exception.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + " 剩余票数 " + ticketTotal);
ticketTotal --;
}
}
}
执行部分
public void threadSyncBySyncMethod() {
TicketLocked2 ticket = new TicketLocked2();
Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1);
Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2);
Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3);
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
}
3、使用锁对象实现
static class TicketLocked3 implements Runnable {
private int ticketTotal = 100;
/**
* 参数:是否公平
* true 公平锁 多个线程公平拥有执行权
* false 独占锁 当线程占用时直到释放,其他线程才能使用
*/
private final Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
// 上锁和解锁必须都要执行
lock.lock();
try {
if (ticketTotal > 0) {
try {
Thread.sleep(100);
} catch (Exception exception) {
exception.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + " 剩余票数 " + ticketTotal);
ticketTotal --;
}
} catch (Exception exception) {
exception.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
执行部分:
/**
* 使用同步锁解决
* 发现同步锁线程执行是均等机会的,
*/
public void threadSyncBySyncLock() {
TicketLocked3 ticket = new TicketLocked3();
Thread ticketThread1 = new Thread(ticket, TICKET_WINDOW_1);
Thread ticketThread2 = new Thread(ticket, TICKET_WINDOW_2);
Thread ticketThread3 = new Thread(ticket, TICKET_WINDOW_3);
ticketThread1.start();
ticketThread2.start();
ticketThread3.start();
}
安全问题的总结:
/**
* 线程安全问题
* 总结:
* synchronized & lock对象的区别:
* 1、synchronized 关键字基于JVM层面上的控制, Lock锁对象是程序编码上的控制
* 2、synchronized自动上锁解锁,Lock锁对象需要手动编写,且解锁必须释放(存在死锁的风险), 同样,使用Lock对象可以在编码上灵活调整
* 3、synchronized属于独占锁,可重入,不可中断, Lock锁可重入,可判断 可公平
* 4、Lock适用场景更为灵活,例如部分需要同步的代码,非全部代码都需要同步
*
*/
三、死锁问题:
/**
* 死锁问题
*
* 死锁产生的条件
* 1、互斥
* 2、不可剥夺
* 3、请求与保持关系
* 4、循环等待条件
*
* 处理死锁问题
* 1、预防 通过限制条件破坏上述条件的产生
* 2、避免
* 3、检查死锁
* 4、解除死锁
*
*/
死锁案例:
package cn.cloud9.test.multithread; /**
* 死锁问题
*
* 死锁产生的条件
* 1、互斥
* 2、不可剥夺
* 3、请求与保持关系
* 4、循环等待条件
*
* 处理死锁问题
* 1、预防 通过限制条件破坏上述条件的产生
* 2、避免
* 3、检查死锁
* 4、解除死锁
*
*/
public class DeadLock { private static class DeadRunnable implements Runnable { private boolean flag;
// 是静态的
private static Object lockObj1 = new Object();
private static Object lockObj2 = new Object(); public DeadRunnable(boolean flag) {
this.flag = flag;
} @Override
public void run() {
if (flag) {
// 线程1执行
synchronized (lockObj1) {
System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj1 开始请求lockObj2");
try {
Thread.sleep(1000);
} catch (Exception exception) {
exception.printStackTrace();
}
synchronized (lockObj2) {
System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj2 & lockObj1");
}
}
} else {
// 线程2执行
synchronized (lockObj2) {
System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj2 开始请求lockObj1");
try {
Thread.sleep(1000);
} catch (Exception exception) {
exception.printStackTrace();
}
synchronized (lockObj1) {
System.out.println(Thread.currentThread().getName() + " 获取当前资源lockObj1 & lockObj2");
}
}
}
}
}
}
执行部分:
public static void main(String[] args) {
DeadRunnable deadRunnable1 = new DeadRunnable(true);
DeadRunnable deadRunnable2 = new DeadRunnable(false); Thread thread1 = new Thread(deadRunnable1, "deadRunnable1");
Thread thread2 = new Thread(deadRunnable2, "deadRunnable2"); thread1.start();
thread2.start(); }
执行后的结果可以看到,锁对象1和锁对象2在线程1和2都上过锁了,
执行到二次上锁的代码块的时候,锁对象没有释放,则线程1和2都发生阻塞,程序既不停止也不结束执行
【Java】MultiThread 多线程 Re01的更多相关文章
- Java的多线程 简单入门
Java的多线程 简单入门 首先能够先搞清楚什么是程序.进程.线程,以及它们之间的关系: 定义: 一 程序仅仅是一组指令的有序集合.它是静态的 二 进程是具有一定独立功能的程序关于某个数据集合上的一次 ...
- Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- java之多线程 二
线程的生命周期: 当线程被创建并被启动时,它既不是一启动就进入了执行状态,在线程的生命周期中,它要经过new(新建),就绪(Runnable),运行(Running),阻塞(Blocked),dead ...
- Java的多线程机制系列:(一)总述及基础概念
前言 这一系列多线程的文章,一方面是个人对Java现有的多线程机制的学习和记录,另一方面是希望能给不熟悉Java多线程机制.或有一定基础但理解还不够深的读者一个比较全面的介绍,旨在使读者对Java的多 ...
- Java Thread 多线程 介绍
1.线程概述 几乎所有的操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序就是一个进程. 当一个程序运行时,内部可能包含了多个顺序执行流,每个顺序执行流就是一个线程. 2.线程 ...
- Java:多线程<一>
程序运行时,其实是CPU在执行程序的进程,为了提高工作效率一个进程可以有多个线程. Java的多线程: 其实我们之前就见过Java的线程,main就是Java的一个线程,还有另一个条线程总是和main ...
- Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...
- Java的多线程机制系列:(三)synchronized的同步原理
synchronized关键字是JDK5之实现锁(包括互斥性和可见性)的唯一途径(volatile关键字能保证可见性,但不能保证互斥性,详细参见后文关于vloatile的详述章节),其在字节码上编译为 ...
- Java基础——多线程
Java中多线程的应用是非常多的,我们在Java中又该如何去创建线程呢? http://www.jianshu.com/p/40d4c7aebd66 一.常用的有三种方法来创建多线程 新建一个类继承自 ...
- JAVA之多线程的创建
转载请注明源出处:http://www.cnblogs.com/lighten/p/5967853.html 1.概念 老调重弹,学习线程的时候总会牵扯到进程的概念,会对二者做一个区分.网上有较多的解 ...
随机推荐
- Java 集合的概念
目录 集合 单列集合(Collection) Collection中的一些方法 public static < T > boolean addAll(Collection<? sup ...
- LeetCode 39. Combination Sum 组合总和 (C++/Java)
题目: Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), ...
- kettle从入门到精通 第三十二课 mysql 数据连接集群/分区配置
1.这里的集群实际上是数据分区或者分片的概念,如中国全国的学生,应该不会都存在一张表里面,有可能每个省市一个表进行存储. 2.集群(分区),如下图所示 设置在"集群"标签,勾选&q ...
- 夜莺项目发布 v6.1.0 版本,增强可观测性数据串联
大家好,夜莺项目发布 v6.1.0 版本,这是一个中版本迭代,不止是 bugfix 了,而是引入了既有功能的增强.具体增强了什么功能,下面一一介绍. 1. 增强可观测性数据串联 从 v6.1.0 开始 ...
- 如何将 iPhone 的照片同步到 windows 电脑上
首先在电脑上,新建一个文件夹,并把共享权限打开. 文件夹 右键 属性,共享,添加 Everyone. 然后,让手机和电脑连接到同一个局域网,手机热点即可. 在手机端看 文件 app,找到电脑的共享文件 ...
- 《Android开发卷——HTTP网络通信,HTTP网络连接》
为了访问互联网,需要设置应用程序获取"androd.permission.INTERNET"权限的许可. 一.使用Apache接口(org.apache.http)并实现网络连接的 ...
- 在线Base64转文件、文件转Base64工具
在线Base64转换神器,一键实现Base64编码与文件互转!支持图片.文档等各类文件,快速准确,安全无服务器存储.拖拽上传,轻松编码解码,提升开发效率.跨平台兼容,移动端友好,让数据转换再无障碍. ...
- MoneyPrinterPlus:AI自动短视频生成工具,详细使用教程
MoneyPrinterPlus是一款使用AI大模型技术,一键批量生成各类短视频,自动批量混剪短视频,自动把视频发布到抖音,快手,小红书,视频号上的轻松赚钱工具. 之前有出过一期基本的介绍,但是后台收 ...
- Linux基本编程环境安装
前言 可以采用组合式安装,如:https://oneinstack.com/ 选择好要安装的,然后复制安装命令就可以一键搞定很多东西了 VMware安装Centos7 按照物理机CPU实际情况,选择处 ...
- Babel 7 初探
Babel有两大功能,转译和polyfill.转译就是把新的JS的语法,转化成旧的JS的语法.polyfill则是针对JS中新增的一些对象(Map, Set)和实例方法,这些对象和方法,在旧的浏览器中 ...