【day17】多线程基础
【day16】回顾
在进入多线程的世界之前,让我们快速回顾一下【day16】中的关键知识点:
- Math类:提供了基础的数学工具,包括
abs
,ceil
,floor
,round
,max
,min
等方法。 - BigInteger类:用于处理超出
long
范围的超大整数,包括add
,subtract
,multiply
,divide
等方法。 - BigDecimal类:解决浮点数运算中的精度问题,提供
add
,subtract
,multiply
,divide
,valueOf(double b)
等方法。 - Date类:用于表示日期,包括
getTime()
和setTime(long time)
等方法。 - Calendar类:日历类,提供
getInstance()
,get()
,set()
,add()
,getTime()
等方法。 - SimpleDateFormat类:日期格式化类,提供
format
和parse
方法。 - JDK8新日期类:包括
LocalDate
和LocalDateTime
,以及时间偏差计算的Period
和Duration
类。 - DateTimeFormatter类:JDK8中新的日期格式化类。
- System类:提供系统级别的操作,如
arrayCopy
。 - Arrays类:提供数组操作,如
toString
,binarySearch
,sort
,copyOf
。 - 包装类:基本数据类型对应的包装类,如
Integer
,Double
,Boolean
等。 - Integer类:提供
valueOf(int i)
和intValue()
方法,以及基本类型和字符串之间的转换。 - JavaBean:定义JavaBean时,基本类型的属性应改为包装类。
模块17重点
本模块将深入探讨多线程的相关概念和操作,包括:
- 掌握多线程的使用方法,主要是
start()
方法。 - 学习通过继承
Thread
类创建多线程。 - 学习通过实现
Runnable
接口实现多线程。 - 学习使用同步代码块解决线程不安全问题。
- 学习使用同步方法解决线程不安全问题。
第一章:多线程基本了解
1.多线程_线程和进程
进程是在内存中执行的应用程序,而线程是进程中最小的执行单元,负责当前进程中程序的运行。一个进程中至少有一个线程,多个线程的应用程序称为多线程程序。
2.并发和并行
并行是指在同一个时刻,多个指令在多个CPU上同时执行。并发是指在同一个时刻,多个指令在单个CPU上交替执行。
细节:
- 之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换
- 现在咱们的CPU都是多核多线程的了,比如2核4线程,那么CPU可以同时运行4个线程,此时不同切换,但是如果多了,CPU就要切换了,所以现在CPU在执行程序的时候并发和并行都存在
3.CPU调度
CPU调度包括分时调度和抢占式调度,Java程序采用的是抢占式调度。
分时调度:值的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的几率大,我们java程序就是抢占式调度
4.主线程介绍
主线程是CPU和内存之间专门为main
方法服务的线程。
第二章:创建线程的方式(重点)
1.第一种方式_extends Thread
通过继承Thread
类并重写run
方法来创建线程,然后调用start
方法开启线程。
public class Test01 {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println("main线程..........执行了"+i);
}
}
}
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread...执行了"+i);
}
}
}
2.多线程在内存中的运行原理
注意:同一个线程对象不能连续调用多次start
,如果想要再次调用start
,则需要新建一个线程对象。
3.Thread类中的方法
Thread
类中的方法包括start()
, run()
, getName()
, setName(String name)
, currentThread()
, sleep(long millis)
等。
4.Thread中其他的方法
Thread
类还提供了setPriority(int newPriority)
, getPriority()
, setDaemon(boolean on)
, yield()
, join()
等方法。
4.1.线程优先级
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");
MyThread1 t2 = new MyThread1();
t2.setName("阿庆");
/*
获取两个线程的优先级
MIN_PRIORITY = 1 最小优先级 1
NORM_PRIORITY = 5 默认优先级 5
MAX_PRIORITY = 10 最大优先级 10
*/
//System.out.println(t1.getPriority());
//System.out.println(t2.getPriority());
//设置优先级
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
4.2.守护线程
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");
MyThread2 t2 = new MyThread2();
t2.setName("阿庆");
//将t2设置成守护线程
t2.setDaemon(true);
t1.start();
t2.start();
}
}
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
public class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"执行了..."+i);
}
}
}
4.3.礼让线程
场景说明: 如果两个线程一起执行,可能会执行一会儿线程A,再执行一会线程B,或者可能线程A执行完毕了,线程B在执行。那么我们能不能让两个线程尽可能的平衡一点 -> 尽量让两个线程交替执行
注意:只是尽可能的平衡,不是绝对的你来我往,有可能线程A线程执行,然后礼让了,但是回头A又抢到CPU使用权了
public class Test01 {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");
MyThread1 t2 = new MyThread1();
t2.setName("阿庆");
t1.start();
t2.start();
}
}
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
Thread.yield();
}
}
}
4.4.插入线程
public class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread1 t1 = new MyThread1();
t1.setName("金莲");
t1.start();
/*
表示把t1插入到当前线程之前,t1要插到main线程之前,所以当前线程就是main线程
*/
t1.join();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"执行了......"+i);
}
}
}
5.第二种方式_实现Runnable接口
通过实现Runnable
接口并重写run
方法来创建线程。利用Thread
类的构造方法:Thread(Runnable target)
,创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中 -> 这一步是让我们自己定义的类成为一个真正的线程类对象
public class Test01 {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
}
6.两种实现多线程的方式区别
继承Thread
类的方式有继承的局限性,而实现Runnable
接口的方式没有继承的局限性。
7.第三种方式_匿名内部类创建多线程
匿名内部类创建多线程实际上是基于实现Runnable
接口的基础上完成的。
public class Test02 {
public static void main(String[] args) {
/*
Thread(Runnable r)
Thread(Runnable target, String name) :name指的是给线程设置名字
*/
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
},"阿庆").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"...执行了"+i);
}
}
},"金莲").start();
}
}
第三章:线程安全
1.线程安全问题–>线程不安全的代码
当多个线程访问同一个资源时,可能会导致数据问题,这种情况称为线程不安全。
public class MyTicket implements Runnable{
int ticket = 100;
@Override
public void run() {
while(true){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");
t1.start();
t2.start();
t3.start();
}
}
原因: CPU在多个线程之间做高速切换导致的
2.解决线程安全问题的第一种方式(使用同步代码块)
同步代码块可以解决线程不安全的问题。
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;
//任意new一个对象
Object obj = new Object();
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (obj){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");
t1.start();
t2.start();
t3.start();
}
}
3.解决线程安全问题的第二种方式:同步方法
3.1.普通同步方法_非静态
public class MyTicket implements Runnable{
//定义100张票
int ticket = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//method01();
method02();
}
}
/* public synchronized void method01(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}*/
public void method02(){
synchronized(this){
System.out.println(this+"..........");
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
System.out.println(myTicket);
Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");
t1.start();
t2.start();
t3.start();
}
}
3.2.静态同步方法
public class MyTicket implements Runnable{
//定义100张票
static int ticket = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//method01();
method02();
}
}
/*public static synchronized void method01(){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}*/
public static void method02(){
synchronized(MyTicket.class){
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
ticket--;
}
}
}
}
public class Test01 {
public static void main(String[] args) {
MyTicket myTicket = new MyTicket();
Thread t1 = new Thread(myTicket, "赵四");
Thread t2 = new Thread(myTicket, "刘能");
Thread t3 = new Thread(myTicket, "广坤");
t1.start();
t2.start();
t3.start();
}
}
第四章:死锁(了解)
1.死锁介绍(锁嵌套就有可能产生死锁)
死锁是指两个或多个线程在执行过程中因为竞争同步锁而产生的阻塞现象,如果没有外力作用,它们将无法继续执行。
2.死锁的分析
3.代码实现
public class LockA {
public static LockA lockA = new LockA();
}
public class LockB {
public static LockB lockB = new LockB();
}
public class DieLock implements Runnable{
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag){
synchronized (LockA.lockA){
System.out.println("if...lockA");
synchronized (LockB.lockB){
System.out.println("if...lockB");
}
}
}else{
synchronized (LockB.lockB){
System.out.println("else...lockB");
synchronized (LockA.lockA){
System.out.println("else...lockA");
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
DieLock dieLock1 = new DieLock(true);
DieLock dieLock2 = new DieLock(false);
new Thread(dieLock1).start();
new Thread(dieLock2).start();
}
}
只需要知道死锁出现的原因即可(锁嵌套),以后尽量避免锁嵌套。
第五章:线程状态
1.线程状态介绍
线程在生命周期中有六种状态,包括NEW(新建)、Runnable(可运行)、Blocked(锁阻塞)、Waiting(无限等待)、Timed Waiting(计时等待)和Terminated(被终止)。
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Terminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop() |