1. 线程简介
程序:程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程:执行程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。由它自身的产生、存在和消亡的过程
线程是由进程创建的,是进程的一个实体。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位
很多多线程是模拟出来的,真正多线程实现并行,是有多个CPU,即多核。一个CPU统一时间点只能执行一个代码,因为切换快,所以就有同时执行的错觉。 宏观并行,微观串行
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//获取当前电脑的CPU数量
int i = runtime.availableProcessors();
System.out.println(i);
}
2. 线程实现(重点)
2.1 实现方式一:继承Thread类
实现步骤:
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
package com.ThreadStudy;
/**
* @ClassName ThreadDemo01
* @Description TODO 创建线程方式一:继承Thread类
* @Author Mark
* @Date 2022/6/23 13:40
* @Version 1.0
*/
//创建线程方式一:继承Thread类,重写run方法,调用start开始线程
public class ThreadDemo01 {
public static void main(String[] args) {
Cat cat = new Cat(); //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
cat.start();//启动线程
//当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行
/*
* 源码:
* public synchronized void start() {
* start0();
* }
*
* private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
* //真正实现多线程效果由start0()实现
* */
}
}
//Thread实现了Runnable接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
int times = 0;
@Override
public void run() {//重写run方法,写入自己的业务代码
while (true) {
//每隔一秒输出一次“喵喵”
System.out.println(\"喵喵 \" + (++times)+\" 线程名:\"+Thread.currentThread().getName());
//sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80)
break;//当times=8,线程停止
}
}
}
进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0
可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况,当所有的线程结束后,进程才会结束(当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭)
案例:多线程下载图片
package com.ThreadStudy;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @ClassName ThreadDemo02
* @Description TODO 练习Thread,实现多线程同步下载图片
* @Author Mark
* @Date 2022/6/23 19:09
* @Version 1.0
*/
public class ThreadDemo02 extends Thread {
private String url;//网络地址
private String name;//保存的文件名
public ThreadDemo02(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println(\"下载了的文件名为:\" + name);
}
public static void main(String[] args) {
ThreadDemo02 thread1 = new ThreadDemo02(\"https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg\", \"fpx.jpeg\");
ThreadDemo02 thread2 = new ThreadDemo02(\"https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png\", \"mgn.jpeg\");
ThreadDemo02 thread3 = new ThreadDemo02(\"https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png\", \"edg.png\");
thread1.start();
thread2.start();
thread3.start();
//下载了的文件名为:mgn.jpeg --thread2
//下载了的文件名为:fpx.jpeg --thread1
//下载了的文件名为:edg.png --thread3
}
}
//下载器
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(\"myThread\\\\src\\\\com\\\\\" + name));
} catch (IOException e) {
e.printStackTrace();
System.out.println(\"IO异常,downloader方法出现异常\");
}
}
}
2.2 实现方式二:实现Runnable接口
java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类创建线程显然不可行了,因此我们通过实现Runnable接口来创建线程
实现步骤:
-
定义MyRunnable类实现Runnable接口
-
实现run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
ThreadDemo03 threadDemo03=new ThreadDemo03();
new Thread(threadDemo03).start();
推荐使用Runnable对象,因为Java单继承的局限性
package com.ThreadStudy;
/**
* @ClassName TheardDemo03
* @Description TODO 创建线程方式二:实现Runnable接口
* @Author Mark
* @Date 2022/6/23 20:54
* @Version 1.0
*/
public class ThreadDemo03 {
public static void main(String[] args) {
// Dog dog = new Dog();
// //dog.start; dog不能调用start
// //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
// new Thread(dog).start();
// //这里底层使用了设计模式:代理模式
Tiger tiger = new Tiger();
Proxy proxy = new Proxy(tiger);
proxy.start();
}
}
class Animal{
}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println(\"嗷嗷\");
}
}
//线程代理类,模拟一个极简的Thread类
class Proxy implements Runnable { //这里可以把Proxy类当作Thread
private Runnable target = null;//属性、类型是Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定
}
}
public Proxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//⭐⭐⭐⭐⭐
}
private void start0() {
run();
}
}
class Dog implements Runnable {
int times = 0;
@Override
public void run() {
while (true) {
System.out.println(\"汪汪\" + (++times) + \" 线程名:\" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 8) {
break;
}
}
}
}
代理是为了不改变原来代码的基础上增强代码
静态代理:
package com.ThreadStudy;
/**
* @ClassName StaticProxy
* @Description TODO 静态代理
* @Author Mark
* @Date 2022/6/24 16:10
* @Version 1.0
*/
public class StaticProxy {
public static void main(String[] args) {
//代理是为了不改变原来代码的基础上增强代码
//在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
// WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
// weddingCompany.Marry();
You you=new You();
new Thread(()-> System.out.println(\"我爱你\")).start();
new WeddingCompany(you).Marry();
}
}
//功能接口
interface Marry{
//结婚方法
void Marry();
}
//You:真实对象,实现结婚接口
class You implements Marry{
@Override
public void Marry() {
System.out.println(\"嘿嘿嘿\");
}
}
//WeddingCompany:代理角色,帮助You
class WeddingCompany implements Marry{
//帮助对象
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void Marry() {
before();
this.target.Marry();
after();
}
private void after() {
System.out.println(\"付尾款\");
}
private void before() {
System.out.println(\"准备婚礼\");
}
}
方式一和方式二对比:
继承Thread类 | 实现Runnable接口 |
---|---|
子类继承Thread类具备多线程能力 | 实现接口Runnable具有多线程能力 |
启动线程:子类对象.start() | 启动线程:线程对象(目标对象).start() |
不建议使用:避免OOP单继承局限性 | 建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用 |
案例:售票
通过继承Thread实现:
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
}
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println(\"售票结束\");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
\" 剩余票数:\" + (--ticketNum));
}
}
}
通过实现Runnable接口实现
public class SellTicket02 {
public static void main(String[] args) {
SellTicketWindow sellTicketWindow=new SellTicketWindow();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
}
}
class SellTicketWindow implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println(\"end...\");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
\" 剩余票数:\" + (--ticketNum));
}
}
}
出现问题:票数超卖
窗口 Thread-0售出一张票 剩余票数:-1
每个线程都会进行if判断,第一个线程if判断出ticketNum>0,还没有执行--ticketNum的时候,第二个线程已经开始了if判断。
案例:龟兔赛跑
- 首先创建赛道,即实现类ThreadDemo05
- 判断比赛是否结束
- 打印出胜利者
- 龟兔赛跑开始
- 兔子要睡觉:模拟睡觉
- 最终乌龟赢得比赛
public class ThreadDemo05 implements Runnable {
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if(Thread.currentThread().getName().equals(\"小兔子\") && i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameOver(i);
//如果比赛结束了,就停止程序
if (flag) {
break;
}
System.out.println(Thread.currentThread().getName() + \"跑了\" + i + \"步\");
}
}
//判断比赛是否结束
private boolean gameOver(int steps) {
if (winner != null) {
return true;
} else if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println(\"winner is\" + winner);
return true;
} else {
return false;
}
}
public static void main(String[] args) {
ThreadDemo05 threadDemo05 = new ThreadDemo05();
new Thread(threadDemo05, \"小兔子\").start();
new Thread(threadDemo05, \"老乌龟\").start();
}
}
2.3 实现方式三:实现Callable接口(了解即可)
实现步骤:
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行:
Future<Boolean r1> result1 = ser.submit(t1);
- 获取结果:
Boolean r1 = result1.get()
- 关闭服务:
ser.shutdownNow();
package com.ThreadCallable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* @ClassName ThreadCallableDemo
* @Description TODO 线程创建方式三:实现Callable接口
* @Author Mark
* @Date 2022/6/23 22:06
* @Version 1.0
*/
//①实现Callable接口,需要返回值类型
public class ThreadCallableDemo implements Callable {
private String url;//网络地址
private String name;//保存的文件名
public ThreadCallableDemo(String url, String name) {
this.url = url;
this.name = name;
}
//②重写call方法,下载图片线程的执行体
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println(\"下载了的文件名为:\" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//③创建目标对象
ThreadCallableDemo thread1 = new ThreadCallableDemo(\"https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg\", \"fpx.jpeg\");
ThreadCallableDemo thread2 = new ThreadCallableDemo(\"https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png\", \"mgn.jpeg\");
ThreadCallableDemo thread3 = new ThreadCallableDemo(\"https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png\", \"edg.png\");
//④创建执行服务:
ExecutorService ser= Executors.newFixedThreadPool(1);
//⑤提交执行:
Future<Boolean> result1= ser.submit(thread1);
Future<Boolean> result2= ser.submit(thread2);
Future<Boolean> result3= ser.submit(thread3);
//⑥获取结果:
boolean rs1=result1.get();
boolean rs2=result2.get();
boolean rs3=result3.get();
//⑦关闭服务:
ser.shutdown();
}
}
//下载器
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(\"myThread\\\\src\\\\com\\\\\" + name));
} catch (IOException e) {
e.printStackTrace();
System.out.println(\"IO异常,downloader方法出现异常\");
}
}
}
2.5 Lamda表达式
Java1.8中更新后出现
目的:简化代码
-
避免匿名内部类定义过多
-
可以让代码看起来简洁
-
去掉了没有意义的代码,只留下核心逻辑
eg:
new Thread(()->System.out.println(\"多线程\")).start();
实质是属于函数式编程的概念,只有函数式接口才能使用Lambda表达式
-
函数式接口的定义:
-
任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口
public interface Runnable{ public abstract void run(); }
-
-
对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
推导lambda表达式:
package com.lambda;
/**
* @ClassName LambadDemo01
* @Description TODO 推导Lambda表达式
* @Author Mark
* @Date 2022/6/24 17:15
* @Version 1.0
*/
public class LambdaDemo01 {
//2. 静态内部类
static class Love1 implements ILove {
@Override
public void lambda() {
System.out.println(\"I Love CC Twice\");
}
}
public static void main(String[] args) {
ILove love = new Love();//创建接口对象=new 实现类
//1.2 实现类方法调用
love.lambda();//实现类.方法
//2.1 静态内部类创建对象调用方法
love = new Love1();
love.lambda();
//3. 局部内部类
class Love2 implements ILove {
@Override
public void lambda() {
System.out.println(\"I Love CC Three Times\");
}
}
//3.1 局部内部类创建对象调用方法
love = new Love2();
love.lambda();
//4. 匿名内部类:没有类的名称,必须借助接口或者父类
love = new ILove() {
@Override
public void lambda() {
System.out.println(\"I Love CC Four Times\");
}
};
love.lambda();
//5. 用lambda简化
love = () -> {
System.out.println(\"I Love CC Three thousand\");
};
love.lambda();
}
}
//定义函数式接口
interface ILove {
void lambda();//默认抽象方法
}
//1.1 实现类
class Love implements ILove {
@Override
public void lambda() {
System.out.println(\"I Love CC\");
}
}
lambda简化:
package com.lambda;
/**
* @ClassName LambdaDemo02
* @Description TODO
* @Author Mark
* @Date 2022/6/24 17:39
* @Version 1.0
*/
public class LambdaDemo02 {
public static void main(String[] args) {
// ILike iLike = (int a)->{
// System.out.println(\"I Like You \" + a + \" Times\");
// };
//简化1,去掉参数类型
// ILike iLike1=(a)->{
// System.out.println(\"I Like You \" + a + \" Times\");
// };
//简化2,简化括号(只有一个参数时或多个参数类型不同时)
// ILike iLike2= a->{
// System.out.println(\"I Like You \" + a + \" Times\");
// };
//简化3,简化花括号(方法中只有一条语句时)
ILike iLike3= a-> System.out.println(\"I Like You \" + a + \" Times\");
// iLike.like(3000);
// iLike1.like(3000);
// iLike2.like(3000);
iLike3.like(3000);
}
}
interface ILike {
void like(int a);
}
3. 线程状态
线程方法:
方法 | 说明 |
---|---|
void setName(String name) | 将此线程的名称更改为等于参数 name |
void getName() | 返回此线程的名称 |
void start() | 导致此线程开始执行; Java虚拟机调用此线程的run 方法 |
void run() | 调用该Runnable 对象的run 方法 |
void setPriority(int newPeiority) | 更改此线程的优先级 |
void getPriority() | 返回此线程的优先级 |
void join() | 等待这个线程死亡。 |
static void sleep(long millis) | 导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停 |
void interrupt() | 中断这个线程 |
boolean isAilve() | 测试这个线程是否活着 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
3.1 停止线程
-
不推荐使用JDK提供的
stop()
、destory()
方法(@Deprecated(since=\"1.2\")
:已废弃) -
推荐线程自己停止下来:将线程体写到循环内,利用次数终止循环,不建议死循环
-
建议用一个标志位进行终止变量。当flag=false,终止线程运行:通知方式
public class StopThread { public static void main(String[] args) throws InterruptedException { ThreadExit_ threadExit_ = new ThreadExit_(); new Thread(threadExit_).start(); Thread.sleep(10000); threadExit_.setFlag(false); } } class ThreadExit_ implements Runnable { private int count = 0; private boolean flag=true; public void setFlag(boolean flag) { this.flag = flag; } @Override public void run() { while (flag) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + \" 运行中...\" + (++count)); } } }
3.2 线程中断
class T implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+\" 吃包子...........\"+i);
}
try {
System.out.println(Thread.currentThread().getName()+\" 休眠中...........\");
Thread.sleep(20002);
} catch (InterruptedException e) {
//该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
//InterruptedException是捕获到一个中断异常
System.out.println(Thread.currentThread().getName()+\" 被interrupt了\");
}
}
}
3.3 线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延迟、倒计时等
- 每个对象都有一个锁,sleep不会释放锁
package com.ThreadStudy;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @ClassName SleepThread2
* @Description TODO 倒计时+打印系统时间
* @Author Mark
* @Date 2022/6/24 21:16
* @Version 1.0
*/
public class SleepThread2 {
public static void main(String[] args) {
// try {
// timeDown();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Date startTime =new Date(System.currentTimeMillis());
while(true){
try {
System.out.println(new SimpleDateFormat(\"HH:mm:ss\").format(startTime));
startTime =new Date(System.currentTimeMillis());//更新当前时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//计时器
// public static void timeDown() throws InterruptedException {
// int num=10;
// while(true){
// Thread.sleep(1000);
// System.out.println(num--);
// if (num<0){
// break;
// }
// }
// }
}
3.4 线程优先级
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
线程的优先级用数字表示,范围从1~10
-
Thread.MIN_PRIORITY=1
-
Thread.MAX_PRIORITY=10
-
Thread.NORM_PRIORITY=5
/** * The minimum priority that a thread can have. */ public static final int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public static final int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public static final int MAX_PRIORITY = 10;
-
-
使用以下方法改变或获取优先级
- getPriority()
- setPriority(int xx)
package com.ThreadStudy;
/**
* @ClassName PriorityThread
* @Description TODO
* @Author Mark
* @Date 2022/6/27 15:57
* @Version 1.0
*/
public class PriorityThread {
public static void main(String[] args) throws InterruptedException {
Eat eat = new Eat();
Thread thread = new Thread(eat);
thread.setName(\"小明\");
thread.setPriority(Thread.MIN_PRIORITY);
thread.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println(\"hi\"+i);
}
thread.interrupt();//当执行到这里时就会中断thread的休眠
}
}
class Eat implements Runnable {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + \" 吃包子...........\" + i);
}
try {
System.out.println(Thread.currentThread().getName() + \" 休眠中...........\");
Thread.sleep(20000);
} catch (InterruptedException e) {
//该线程执行到一个interrupt方法时,就会catch一个异常,可以加入自己的业务代码
//InterruptedException是捕获到一个中断异常
System.out.println(Thread.currentThread().getName() + \" 被interrupt了\");
}
}
}
}
3.5 线程礼让
- 礼让线程yield(),让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,但礼让的时间不确定,所以礼让不一定成功
package com.ThreadStudy;
/**
* @ClassName YieldThread
* @Description TODO 主线程输出20次hello,子线程输出20次hi,主线程输出5次后子线程插队
* @Author Mark
* @Date 2022/6/24 21:45
* @Version 1.0
*/
public class ThreadDemo06 {
public static void main(String[] args) throws InterruptedException {
ThreadSon threadSon = new ThreadSon();
Thread thread = new Thread(threadSon);
thread.start();
for (int i = 1; i <= 20; i++) {
Thread.sleep(500);
System.out.println(\"hi\");
if (i == 5) {
thread.join();
}
}
}
}
class ThreadSon implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"hello\");
}
}
}
3.6 线程强制执行
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象成插队
package com.ThreadStudy;
/**
* @ClassName JoinDemo
* @Description TODO Join方法,想象为插队
* @Author Mark
* @Date 2022/6/24 22:00
* @Version 1.0
*/
public class JoinDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(\"线程VIP: \"+i);
}
}
public static void main(String[] args) throws InterruptedException {
JoinDemo joinDemo=new JoinDemo();
Thread thread=new Thread(joinDemo);
for (int i = 0; i < 300; i++) {
if (i==200){
thread.start();
thread.join();
}
System.out.println(\"主线程: \"+i);
}
}
}
4. 守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或以通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
package com.ThreadStudy;
/**
* @ClassName DaemonThread
* @Description TODO 将一个线程设置为守护线程,当主线程结束后,子线程也结束
* @Author Mark
* @Date 2022/7/3 18:10
* @Version 1.0
*/
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
myDaemonThread myDaemonThread = new myDaemonThread();
Thread thread = new Thread(myDaemonThread);
//如果希望当主线程结束后,子线程可以自动结束,只需将子线程设为守护线程
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println(\"呜呜呜...\");
Thread.sleep(1000);
}
}
}
class myDaemonThread implements Runnable{
@Override
public void run() {
for(;;){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"哈哈哈\");
}
}
}
5. 线程的生命周期
线程状态:Thread.State 枚举了线程可能处于以下状态之一
- New:尚未启动的线程处于此状态
- RUNNABLE:在Java虚拟机中执行的线程出于此状态
- Ready:线程进入就绪状态,可以被执行
- Running:线程开始执行,进入运行状态
- BLOCKED:被阻塞等待监视器锁定的线程处于此状态
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态
- TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
- TERMINATED:已退出的线程处于此状态
package com.ThreadStudy;
/**
* @ClassName ThreadState
* @Description TODO 观察测试线程的状态
* @Author Mark
* @Date 2022/6/27 15:38
* @Version 1.0
*/
public class ThreadState {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
System.out.println(thread.getName() + \" 状态 \" + thread.getState());
thread.start();
while (Thread.State.TERMINATED != thread.getState()) {
System.out.println(thread.getName() + \" 状态 \" + thread.getState());
Thread.sleep(500);
}
System.out.println(thread.getName() + \" 状态 \" + thread.getState());
}
}
class MyThread implements Runnable {
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println(\"hi\" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
运行结果:
Thread-0 状态 NEW
Thread-0 状态 RUNNABLE
hi0
Thread-0 状态 TIMED_WAITING
hi1
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi2
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi3
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi4
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi5
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi6
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi7
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi8
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
hi9
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TIMED_WAITING
Thread-0 状态 TERMINATED
5. 线程同步(重点)
线程同步机制
-
在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问计数,保证数据在任何时刻,最多有一个线程访问,以保证数据完整性
-
也可以理解为:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进程操作,直到该线程完成操作,其他线程才能对该内存地址进程操作。
-
同步具体方法:Synchronized
-
同步代码块:
synchronized(对象){//得到对象的锁,才能操作同步代码 //需要被同步代码 }
-
synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void fun(String name){ //需要被同步的代码 }
-
案例:售票系统升级
package com.Synchronized;
/**
* @ClassName SellTicket02
* @Description TODO
* @Author Mark
* @Date 2022/7/3 12:31
* @Version 1.0
*/
public class SellTicket02 {
public static void main(String[] args) {
SellTicketWindow sellTicketWindow = new SellTicketWindow();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
}
}
//使用同步方法synchronized实现线程同步
class SellTicketWindow implements Runnable {
private boolean flag = true;
private int ticketNum = 100;
//同步方法,在同一时刻只能有一个线程来执行run方法
//这时锁在this对象
//也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象
//synchronized(this){}
public synchronized void fun() {
if (ticketNum <= 0) {
System.out.println(\"end...\");
flag = false;
return;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\"窗口 \" + Thread.currentThread().getName() + \"售出一张票\" +
\" 剩余票数:\" + (--ticketNum));
}
@Override
public void run() {
while (flag) {
fun();
}
}
}
6. 互斥锁
- 在Java中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
- 多个线程的锁对象必须是同一个
- 同步的局限性:导致程序的执行效率降低
- 静态同步方法:静态同步方法的锁是当前类的字节码文件(类名.class)
- 非静态同步方法:非静态同步方法的锁是this
- 同步代码块:同步代码块可以使用自定义的Object对象,也可以使用this或者当前类的字节码文件(类名.class)
7. 线程死锁
- 基本介绍
多个线程都占用了对方的锁的资源,但不肯相让,导致了死锁,一定要避免死锁的发生
-
实际案例:
妈妈:你先完成作业,才能打游戏
儿子:我先打游戏,才能完成作业
package com.Synchronized; /** * @ClassName DeadLockDemo * @Description TODO * @Author Mark * @Date 2022/7/8 20:09 * @Version 1.0 */ public class DeadLockDemo { public static void main(String[] args) { DeadLock deadLock1 = new DeadLock(true); DeadLock deadLock2 = new DeadLock(false); new Thread(deadLock1,\"A线程\").start(); new Thread(deadLock2,\"B线程\").start(); } } class DeadLock implements Runnable { static Object o1 = new Object();//保证多线程共享一个对象,使用static static Object o2 = new Object(); boolean flag; public DeadLock(boolean flag) { this.flag = flag; } @Override public void run() { if (flag) { synchronized (o1) {//对象互斥锁,下面是同步代码 System.out.println(Thread.currentThread().getName() + \"进入1\"); synchronized (o2) { System.out.println(Thread.currentThread().getName() + \"进入2\"); } } } else { synchronized (o2) { System.out.println(Thread.currentThread().getName() + \"进入3\"); synchronized (o1) { System.out.println(Thread.currentThread().getName() + \"进入4\"); } } } } }
8.释放锁
释放锁的操作:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
不会释放锁的操作:
- 线程执行同步代码或同步方法时,程序调用了Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁
- 应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
来源:https://www.cnblogs.com/hackertyper/p/16459588.html
本站部分图文来源于网络,如有侵权请联系删除。