线程简介
任务
多任务:
进程和线程
程序、进程、线程
多线程:
进程Process和线程Thread
- 我们把一个任务叫做一个进程,浏览器是一个进程,音乐播放器是另一个进程。
- 一些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把这些子任务叫做线程。
- 进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。线程由CPU调度和执行。
注意*️:很多的多线程是模拟出来的,真正的多线程是指有多个CPU,即多核处理器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有了同时执行的错觉。
本章核心概念
- 线程是独立的执行单元
- 在程序运行时,即使自己没有创建线程,后台也会有多个线程,如主线程,gc线程。
- main()叫做主线程,是系统的入口,用于执行整个程序。
- 在一个进程中,如果开辟了多个线程,由调度器安排调度线程的运行,调度器是与操作系统紧密相关的,执行的先后顺序不能人为干预。
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制。
- 线程会带来额外的开销,如CPU调用时间,并发控制开销。
- 每个线程在自己的工作内存中交互,内存控制不当会造成数据不一致。
线程实现【重点难点】
线程的三种创建方式:
继承Thread类【重点】
普通方法调用和多线程:
多线程
代码实现:
package com.thread.demo01;
/*
创建线程的方式一:继承Thread类
1.继承Thread类
2.重写run()方法
3.调用start()方法,开启线程
注意:线程开启不一定立即执行,由CPU调度执行,无法人为干预
*/
public class ThreadTest01 extends Thread {
@Override
public void run() {
//run()方法的线程体
for (int i = 1; i <= 100; i++) { //执行100次循环
System.out.println("我在看代码呀----" + i);
}
}
public static void main(String[] args) {
//main线程,是主线程
//创建线程对象
ThreadTest thread = new ThreadTest();
//调用start()方法开启线程,现在两个线程同时执行
thread.start();
for (int i = 1; i <= 500; i++) {
System.out.println("我在学习多线程----" + i);
}
}
}
效果:多个线程交替执行
单线程:先执行完run()方法再执行后面,按顺序执行,只有一个线程
代码实现:
package com.thread.demo01;
/*
创建单线程的方式一:继承Thread类
1.继承Thread类
2.重写run()方法
3.调用run()方法
注意:线程开启不一定立即执行,由CPU调度执行,无法人为干预
*/
public class ThreadTest extends Thread {
@Override
public void run() {
//run()方法的线程体
for (int i = 1; i <= 100; i++) {
System.out.println("我在看代码呀----" + i);
}
}
public static void main(String[] args) {
//main线程,是主线程
//创建线程对象
ThreadTest thread = new ThreadTest();
thread.run(); //先执行run()方法再执行后面,按顺序执行,只有一个线程
for (int i = 1; i <= 500; i++) {
System.out.println("我在学习多线程----" + i);
}
}
}
效果:
应用:使用多线程下载图片
谷歌搜索并下载Commons IO包,把commons-io.jar包复制到项目中,需要把包Add as Library添加到类库。
代码实现:
package com.thread.test;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/*
使用多线程同时下载图片
*/
public class ThreadTest extends Thread {
private String url;
private String name;
//构造器
public ThreadTest(String url, String name) {
this.url = url; //网络图片地址
this.name = name; //保存的文件名
}
//下载图片的线程体
@Override
public void run() {
WebDownload webDownload = new WebDownload(); //创建下载器对象
webDownload.download(url, name); //下载文件
System.out.println("下载的文件名为:" + name);
}
public static void main(String[] args) {
//创建线程对象
ThreadTest thread01 = new ThreadTest("https://img.syt5.com/2021/0607/20210607042123800.jpg.420.554.jpg", "1.jpg");
ThreadTest thread02 = new ThreadTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTRWvlbwRLHLSUMxDKEw3S8yJjN5UqrnZlDvQ&usqp=CAU", "2.jpg");
ThreadTest thread03 = new ThreadTest("https://n.sinaimg.cn/sports/2_img/upload/0b43a5e3/83/w750h933/20190704/3596-hzfekep7254308.jpg", "3.jpg");
ThreadTest thread04 = new ThreadTest("https://lh3.googleusercontent.com/proxy/ZkdwKWp3f4XlSpjTCKDqjWs5Fy80NOA-FB3t-vwCUKWX_CVlKc5LceE1Z3elABY_byjVRym3Q1hHRPnLxS5ycS0C490-MUSkCFLLQR3J9yfAkD34EttiUfK-NLrG", "4.jpg");
ThreadTest thread05 = new ThreadTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_iysqZXf0MRZtsm5GpUw_SvxCSnZrUFsaqQ&usqp=CAU", "5.jpg");
//开启线程
//理想执行顺序是:先下载t1,然后t2,最后t3
//实际执行顺序是:t1,t3,t2,每次执行顺序可能都不一样
thread01.start();
thread02.start();
thread03.start();
thread04.start();
thread05.start();
}
}
//下载器
class WebDownload {
//下载方法
public void download(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载失败");
}
}
}
实现Runnable接口【重点】
创建线程方式二,实现Runnable接口(推荐使用)
package com.thread.demo01;
/*
使用多线程
创建线程方法二:实现Runnable接口
1、实现Runnable接口,重写run()方法
2、创建Thread对象需要传入runnable接口,通过Thread对象开启线程
*/
public class ThreadTest implements Runnable {
//编写线程体
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("我在看代码呀----" + i);
}
}
public static void main(String[] args) {
//创建实现类对象
ThreadTest threadTest = new ThreadTest();
//创建Thread对象,通过Thread对象开启线程
Thread thread = new Thread(threadTest); //需要一个runnable接口
thread.start();
// new Thread(thread03).start(); //可读性差
//主线程的500次循环
for (int i = 1; i <= 500; i++) {
System.out.println("我在学习多线程呀----" + i);
}
}
}
效果:多个线程之间交替执行
线程实现小结:
应用:多个线程同时操作同一个对象
问题:多个线程操作同一个资源的情况下,线程不安全,数据混乱!
多次运行看结果!
package com.thread.test;
/*
多个线程同时操作同一个对象
场景:买火车票
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据混乱!
*/
public class ThreadTest implements Runnable{
//火车票数量
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) { //没有余票的情况
break;
}
try { //有票的情况
//线程睡眠,0.2秒
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程信息
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "-->拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
//创建实现类对象
ThreadTest ticketThread = new ThreadTest();
//创建Thread对象
Thread thread01 = new Thread(ticketThread, "小明");
Thread thread02 = new Thread(ticketThread, "小红");
Thread thread03 = new Thread(ticketThread, "黄牛");
thread01.start(); //开启线程
thread02.start();
thread03.start();
}
}
执行结果:产生抢夺问题,造成数据不安全
应用:龟兔赛跑
package com.thread.demo01;
/*
使用多线程-模拟龟兔赛跑
*/
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟兔子休息
if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) { //当i能被10整除时,线程睡眠一次
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;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
实现Callable接口【了解】
使用Callable的好处:
1.可以定义返回值类型
2.可以抛出异常
package com.thread.test;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/*
使用多线程
方法3:实现Callable接口
1、实现Callable接口,重写call()方法
2、创建实现类对象
3、创建执行服务的接口
*/
public class CallableTest implements Callable<Boolean> {
private String url;
private String name;
public CallableTest(String url, String name) {
this.url = url; //网络图片地址
this.name = name; //保存的文件名
}
//下载图片线程的执行体
@Override
public Boolean call() {
WebDownload webDownload = new WebDownload();
webDownload.download(url, name);
System.out.println("下载的文件名为:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建实现类对象
CallableTest thread01 = new CallableTest("https://img.syt5.com/2021/0607/20210607042123800.jpg.420.554.jpg", "1.jpg");
CallableTest thread02 = new CallableTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTRWvlbwRLHLSUMxDKEw3S8yJjN5UqrnZlDvQ&usqp=CAU", "2.jpg");
CallableTest thread03 = new CallableTest("https://n.sinaimg.cn/sports/2_img/upload/0b43a5e3/83/w750h933/20190704/3596-hzfekep7254308.jpg", "3.jpg");
CallableTest thread04 = new CallableTest("https://lh3.googleusercontent.com/proxy/ZkdwKWp3f4XlSpjTCKDqjWs5Fy80NOA-FB3t-vwCUKWX_CVlKc5LceE1Z3elABY_byjVRym3Q1hHRPnLxS5ycS0C490-MUSkCFLLQR3J9yfAkD34EttiUfK-NLrG", "4.jpg");
CallableTest thread05 = new CallableTest("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS_iysqZXf0MRZtsm5GpUw_SvxCSnZrUFsaqQ&usqp=CAU", "5.jpg");
//创建执行服务的接口
ExecutorService ser = Executors.newFixedThreadPool(5);
//提交执行
Future<Boolean> r1 = ser.submit(thread01);
Future<Boolean> r2 = ser.submit(thread02);
Future<Boolean> r3 = ser.submit(thread03);
Future<Boolean> r4 = ser.submit(thread04);
Future<Boolean> r5 = ser.submit(thread05);
//获取结果
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();
Boolean rs3 = r3.get();
Boolean rs4 = r4.get();
Boolean rs5 = r5.get();
//关闭服务
ser.shutdown();
}
}
//下载器
class WebDownload {
//下载方法
public void download(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,download方法出现问题");
}
}
}
静态代理模式
事例:
1.Marry接口
package com.thread.test;
//结婚接口
public interface Marry {
/*
人生四大喜事
久旱逢甘露
他乡遇故知
洞房花烛夜
金榜题名时
*/
void HappyMarry();
}
2.You类,新郎新娘
package com.thread.test;
//实现Marry接口,编写抽象方法的具体实现
//角色:新郎新娘
public class You implements Marry {
@Override
public void HappyMarry() {
System.out.println("要结婚了,很开心!");
}
}
3.WeddingCompany类,婚庆公司
package com.thread.test;
//实现Marry接口,编写抽象方法的具体实现
//角色:婚庆公司,功能:代理角色,帮助你结婚
public class WeddingCompany implements Marry {
//代理谁--》真实目标角色:新郎新娘
private Marry target;
//构造器
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before(); //调用代理角色的方法
this.target.HappyMarry(); //调用真实对象的方法
after();
}
private void before() {
System.out.println("结婚之前,布置现场");
}
private void after() {
System.out.println("结婚之后,收尾款");
}
}
4.StaticProxy类,静态代理模式
package com.thread.test;
/*
静态代理模式:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情,在代理对象中添加真实对象没有的方法
真实对象可以专注做自己的事情
可以做的事 范围:代理对象>真实对象
*/
public class StaticProxy {
public static void main(String[] args) {
You you = new You(); // 你要结婚
WeddingCompany weddingCompany = new WeddingCompany(you); //婚庆公司代帮助你结婚
weddingCompany.HappyMarry(); //调用婚庆公司的方法,在里面调用你的方法!
}
}
Lambda表达式【难点】
使用Lambda表达式的前提是有函数式接口。
函数式接口的定义:如果一个接口中,只有一个抽象方法,那这个接口就是函数式接口。
推导Lambda表达式
1.Like接口,是一个函数式接口
package com.thread.test;
//函数式接口:接口中只有一个抽象方法
public interface Like {
void lambda();
}
2.ILike类,实现Like接口
package com.thread.test;
//实现接口,重写抽象方法
public class ILike implements Like {
@Override
public void lambda() {
System.out.println("i like lambda");
}
}
3.LambdaTest类,推导Lambda表达式
package com.thread.test;
/*
推导Lambda表达式
*/
public class LambdaTest {
public static void main(String[] args) {
Like like = new ILike(); //接口指向实现类,多态
like.lambda(); //本质是调用实现类重写的方法
Like like2 = new ILike2();
like2.lambda();
//2.局部内部类
class ILike3 implements Like {
@Override
public void lambda() {
System.out.println("局部内部类----i like lambda3");
}
}
Like like3 = new ILike3();
like3.lambda();
//3.匿名内部类,没有类的名称,必须借助接口或者父类
Like like4 = new Like() {
@Override
public void lambda() {
System.out.println("匿名内部类----i like lambda4");
}
};
like4.lambda();
//4.用Lambda简化
Like like5 = ()-> {
System.out.println("使用Lambda表达式----i like lambda5");
};
like5.lambda();
}
//1.静态内部类
//实现接口,重写抽象方法
static class ILike2 implements Like {
@Override
public void lambda() {
System.out.println("静态内部类----i like lambda2");
}
}
}
Lambda表达式简化
- Lambda表达式在只有一行代码的情况下才能简写成一行,如果有多行,就必须用代码块{}括起来。
- 使用Lambda表达式的前提,接口必须是函数式接口
- 多个参数也可以去掉参数类型,要去掉就都去掉,参数必须加上括号()
代码实现
1.Love接口,是一个函数式接口
package com.thread.test;
//函数式接口:接口中只有一个抽象方法
public interface Love {
void love(int num);
}
2.ILove类,实现Love接口
package com.thread.test;
//实现接口,重写抽象方法
public class ILove implements Love {
@Override
public void love(int num) {
System.out.println("i love code" + num);
}
}
3.LambdaTest类,演示简化Lambda表达式的步骤
package com.thread.test;
/*
推导Lambda表达式
*/
public class LambdaTest {
public static void main(String[] args) {
Love love = new ILove(); //接口指向实现类,多态
love.love(1); //本质是调用实现类重写的方法
Love love2 = new ILove2();
love2.love(2);
//2.局部内部类
class ILove3 implements Love {
@Override
public void love(int num) {
System.out.println("局部内部类----i love code" + num);
}
}
Love love3 = new ILove3();
love3.love(3);
//3.匿名内部类,没有类的名称,必须借助接口或者父类
Love love4 = new Love() {
@Override
public void love(int num) {
System.out.println("匿名内部类----i love code" + num);
}
};
love4.love(4);
//4.用Lambda简化
Love love5 = (int num)-> {
System.out.println("使用Lambda表达式----i love code" + num);
};
love5.love(5);
//简化1:去掉参数类型
Love love6 = (num)-> {
System.out.println("使用Lambda表达式----i love code" + num);
};
love6.love(6);
//简化2:去掉括号()
Love love7 = num-> {
System.out.println("使用Lambda表达式----i love code" + num);
};
love7.love(7);
//简化3:去掉花括号{}
Love love8 = num-> System.out.println("使用Lambda表达式----i love code" + num);
love8.love(8);
/*
总结:
Lambda表达式在只有一行代码的情况下才能简写成一行,如果有多行,就必须用代码块{}括起来。
使用Lambda表达式的前提,接口必须是函数式接口
多个参数也可以去掉参数类型,要去掉就都去掉,参数必须加上括号()
*/
}
//1.静态内部类
//实现接口,重写抽象方法
static class ILove2 implements Love {
@Override
public void love(int num) {
System.out.println("静态内部类----i love code" + num);
}
}
}
线程状态【重点】
线程五大状态:
线程方法
停止线程
package com.thread.test;
/*
停止线程
1.建议线程正常停止--->利用次数,不建议死循环。
2.使用标志位--->设置一个标志位(推荐)
3.不要使用stop或者destroy等过时方法,以及JDK不建议使用的方法
思路:当标志位为false的时候,停止执行线程
1、提供一个改变标志位Boolean值的方法
2、在循环中使用标志位作为判断条件,停止循环也就停止线程
*/
public class ThreadStop implements Runnable {
//设置一个标志位
private Boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
System.out.println("我在看代码鸭----" + (i++));
}
}
//提供一个改变标志位的公有方法
public void flagStop() {
this.flag = false;
}
public static void main(String[] args) {
//创建实现类对象
ThreadStop threadStop = new ThreadStop();
//创建Thread对象
Thread thread = new Thread(threadStop);
thread.start(); //开启线程
//主线程执行1000次循环,要求到500次时停止线程
for (int i = 1; i <= 1000; i++) {
System.out.println("我在写bug鸭----" + i);
if (i == 500) {
threadStop.flagStop(); //调用stop()方法改变标志位,让线程停止
System.out.println("线程已停止~");
}
}
}
}
线程休眠
模拟网络延时:
package com.thread.test;
/*
线程休眠
示例:模拟网络延时,抢火车票
模拟网络延时的作用:放大问题的发生性,本质是多个线程对同一个资源进行操作
*/
public class ThreadSleep implements Runnable {
//火车票数量
private int ticketNums = 10;
//编写线程体
@Override
public void run() {
while (true) {
if (ticketNums <= 0) { //没有余票的情况
break;
}
//线程睡眠,模拟延时
try { //有票的情况
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//获取线程信息
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "-->拿到了第" + ticketNums-- + "张票"); //买票操作,票数-1
}
}
public static void main(String[] args) {
//创建实体类对象
ThreadSleep threadSleep = new ThreadSleep();
//创建Thread对象
Thread thread01 = new Thread(threadSleep, "小李");
Thread thread02 = new Thread(threadSleep, "小陈");
Thread thread03 = new Thread(threadSleep, "黄牛");
thread01.start(); //开启线程
thread02.start();
thread03.start();
}
}
模拟倒计时:
package com.thread.test;
import java.text.SimpleDateFormat;
import java.util.Date;
/*
线程休眠
示例:模拟倒计时
思路:10秒倒计时,每过一秒就递减打印一个数,到0秒的时候结束。
*/
public class ThreadSleep {
public static void main(String[] args) {
ThreadSleep threadSleep = new ThreadSleep();
threadSleep.timeDown(10); //调用倒计时方法,设置倒计时秒数
//打印当前系统时间
Date date = new Date(System.currentTimeMillis()); //获取系统当前时间
//创建SimpleDateFormat对象
String formatDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
System.out.println(formatDate);
}
//倒计时方法
public void timeDown(int num) {
int count = num;
while (true) {
if (num <= 0) { //到0秒的情况
System.out.println(count + "秒倒计时结束~");
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num--);
}
}
}
线程礼让
多运行几次看结果
代码实现:
1.MyYield类,执行线程礼让操作
package com.thread.test;
//线程礼让
public class MyYield implements Runnable {
@Override
public void run() {
//i等于3时,执行线程礼让操作
for (int i = 0; i < 5; i++) {
if (i == 3) {
System.out.println("当前的线程是: " + Thread.currentThread().getName());
Thread.yield(); //线程礼让
}
System.out.println("执行的线程是:" + Thread.currentThread().getName());
}
}
}
2.ThreadYield类
package com.thread.test;
/*
线程礼让
礼让不一定成功,看CPU心情
*/
public class ThreadYield {
public static void main(String[] args) {
//创建实现类对象
MyYield myYield = new MyYield();
//创建Thread对象
Thread thread01 = new Thread(myYield, "迪丽热巴");
Thread thread02 = new Thread(myYield, "古力娜扎");
thread01.start();
thread02.start();
}
}
运行结果:
线程插队
package com.thread.test;
/*
线程插队join()
可以想象成插队
*/
public class ThreadJoin implements Runnable {
//编写线程体
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程vip来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
//创建实现类对象
ThreadJoin threadJoin = new ThreadJoin();
//创建Thread对象,开启线程
Thread thread = new Thread(threadJoin);
thread.start(); //开启线程
//主线程
for (int i = 0; i < 50; i++) {
if (i == 5) {
thread.join(); //线程插队
}
System.out.println("main" + i);
}
}
}
运行结果:
一旦线程开始插队就会一直执行,直到完成它的操作,才会让其他进程执行。
查看线程状态
package com.thread.test;
/*
查看线程的状态
*/
public class ThreadState implements Runnable {
public static void main(String[] args) throws InterruptedException {
//创建实现类对象
ThreadState threadState = new ThreadState();
//创建Thread对象,开启线程
Thread thread = new Thread(threadState);
//观察线程状态
Thread.State state = thread.getState();
System.out.println(state); //NEW
//观察启动后
thread.start();
state = thread.getState();
System.out.println(state); // RUNNABLE
// TIMED_WAITING,TERMINATED,BLOCKED,WAITING
while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState(); //更新线程状态
System.out.println(state); //输出线程状态
}
}
//编写线程体
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(".......");
}
}
线程优先级
守护线程
代码实现
1.God类,守护进程
package com.thread.test;
//上帝,模拟守护线程
public class God implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("上帝守护着你!");
}
}
}
2.You类,用户进程
package com.thread.test;
//你,用户进程
public class You implements Runnable {
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("一生都要开心的活着!");
}
}
}
3.ThreadDaemon类,设置守护进程
package com.thread.test;
/*
守护线程
示例:上帝守护着你
*/
public class ThreadDaemon {
public static void main(String[] args) {
//创建实体类对象
God god = new God();
You you = new You();
//创建Thread对象,开启线程
//创建守护进程
Thread threadDaemon = new Thread(god);
threadDaemon.setDaemon(true); //默认是false表示用户线程,正常的线程都是用户线程
threadDaemon.start();
//创建用户进程
Thread threadUser = new Thread(you);
threadUser.start(); //开启用户进程
}
}
线程同步【重点难点】
多个线程操作同一个资源
线程同步机制
并发
线程同步
队列和锁
三大不安全事例
同步方法
同步方法的弊端
同步块
同步方法:
1.BuyTicket类,定义买票操作
package com.thread.test;
//买票操作
//思路:设置总票数,每买一张票总票数-1。如果有余票就执行买票操作,否则退出买票程序
public class BuyTicket implements Runnable {
//火车票数量
private int TicketNums = 10;
//标志位
private Boolean flag = true;
//编写线程体
@Override
public void run() {
buy();
}
//因为数据不安全,所以加上 synchronized同步方法
//synchronized 同步方法,锁的是this,BuyTicket对象
//一个线程独占buy()方法,等它用完,其他线程才能使用buy()方法
public synchronized void buy() {
while (flag) {
if (TicketNums <= 0) { //没有余票的情况
flag = false; //改变标志位停止循环
System.out.println("所有车票已经售光");
break;
}
//模拟延时
try { //有票的情况
Thread.sleep(100); //线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
//买票操作
System.out.println(Thread.currentThread().getName() + "拿到第" + TicketNums-- + "张票");
}
}
}
2.ThreadUnsafeBuyTicket类,程序入口
package com.thread.test;
/*
解决“不安全的买票”问题
使用synchronized 同步方法
*/
public class ThreadUnsafeBuyTicket {
public static void main(String[] args) {
//创建实现类对象
BuyTicket buyTicket = new BuyTicket();
//创建Thread对象,开启线程
Thread thread01 = new Thread(buyTicket, "机智如我");
Thread thread02 = new Thread(buyTicket, "乱抢的你们");
Thread thread03 = new Thread(buyTicket, "可恶的黄牛");
thread01.start();
thread02.start();
thread03.start();
}
}
运行结果:没有加synchronized 同步方法的情况
运行结果:加了synchronized 同步方法的情况
同步块:
1.Account类,银行账户信息
package com.thread.test;
//银行账户
public class Account {
int money; //余额
String name; //卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
2.Drawing类,取款操作
package com.thread.test;
//银行:模拟取款
//思路:取款操作,一个账户会减钱,另一个账户会加钱
public class Drawing extends Thread {
Account account;
int drawingMoney; //取钱的金额
int nowMoney; //手上的金额
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account = account;
this.drawingMoney = drawingMoney;
}
//编写线程体
//取钱操作,用 synchronized代码块,默认锁的是this
@Override
public void run() {
//同步块
//锁的对象,就是变化的量
synchronized (account) {
//判断有没有钱
if (account.money - drawingMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
return;
}
//模拟延时,可以放大问题的发生性
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money - drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name + "余额为:" + account.money);
System.out.println(this.getName() + "手里的钱:" + nowMoney);
// System.out.println(Thread.currentThread().getName() + "手里的钱:" + nowMoney);
}
}
}
3.ThreadUnsafeBank,程序入口
package com.thread.test;
/*
解决“不安全的取钱”问题
事例:两个人去银行取钱,需要账户
*/
public class ThreadUnsafeBank {
public static void main(String[] args) {
//账户
Account account = new Account(10000, "结婚基金");
//你
Drawing you = new Drawing(account, 500, "你");
Drawing girlfriend = new Drawing(account, 1000, "girlfriend");
you.start();
girlfriend.start();
}
}
运行结果:
一个账户取钱操作完,另一个账户才能去取钱
死锁【重点难点】
避免死锁的方法
1.Lipstick类
package com.thread.test;
//口红
public class Lipstick {
}
2.Mirror类
package com.thread.test;
//镜子
public class Mirror {
}
3.Makeup类,
package com.thread.test;
//化妆
public class Makeup extends Thread {
//需要的资源只有一份,用static来保证
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String girlName; //使用化妆品的人
Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
makeup();
}
//化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() {
if (choice == 0) {
synchronized (lipstick) { //获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// synchronized (mirror) { //一秒钟后想获得镜子的锁
// System.out.println(this.girlName + "获得镜子的锁");
// }
}
//把同步块拿出来,就可以解决死锁问题
synchronized (mirror) { //一秒钟后想获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
}
} else {
synchronized (mirror) { //获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//
// synchronized (lipstick) { //一秒钟后想获得口红的锁
// System.out.println(this.girlName + "获得口红的锁");
// }
}
synchronized (lipstick) { //一秒钟后想获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
package com.thread.test;
/*
死锁
死锁:多个线程互相持有对方需要的资源,然后形成僵持
*/
public class DeadLock {
public static void main(String[] args) {
Makeup girl1 = new Makeup(0, "亚丝娜");
Makeup girl2 = new Makeup(1, "加藤惠");
girl1.start();
girl2.start();
}
}
Lock(锁)【重点难点】
可重入锁ReentrantLock
代码实现:
1.Lock类,定义买票操作,给线程加锁/解锁
package com.thread.test;
import java.util.concurrent.locks.ReentrantLock;
public class Lock implements Runnable {
//火车票数量
int ticketNums = 10;
//定义Lock锁
private final ReentrantLock lock = new ReentrantLock();
//编写线程体
@Override
public void run() {
while (true) {
try {
//加锁
lock.lock();
if (ticketNums > 0) { //有票的情况
try {
Thread.sleep(100); //线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + ticketNums-- + "张票");
} else { //没有余票的情况
System.out.println(Thread.currentThread().getName() + "所有车票已售光");
break;
}
} finally {
//解锁
lock.unlock();
}
}
}
}
2.LockTest类,程序入口
package com.thread.test;
//使用ReentrantLock,给线程加锁/解锁,解决数据不安全问题
public class LockTest {
public static void main(String[] args) {
//创建实现类对象
Lock lock = new Lock();
//创建Thread对象,开启线程
Thread thread01 = new Thread(lock);
Thread thread02 = new Thread(lock);
thread01.start();
thread02.start();
}
}
运行结果:
线程通信【重点难点】
生产者消费者问题
管程法
1.Chicken类,产品
package com.thread.test;
//产品
public class Chicken {
int id; //产品编号
public Chicken(int id) {
this.id = id;
}
}
2.SynContainer类,缓冲区
package com.thread.test;
//缓冲区
public class SynContainer {
//设置一个容器的大小
Chicken[] chickens = new Chicken[10]; //放产品的地方
//容器计数器
int count = 0;
//生产者放入产品
public synchronized void push(Chicken chicken) {
//如果容器满了,就需要等待消费者消费
//通知消费者消费,生产者等待
if (count == chickens.length) {
try { //容器满了的情况,生产者等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就要丢入产品
chickens[count] = chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop() {
//判断是否能消费
if (count == 0) {
//等待生产者生产,消费者等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
3.Producer类,生产者
package com.thread.test;
//生产者
public class Producer extends Thread {
SynContainer container;
public Producer(SynContainer container) {
this.container = container;
}
//生产产品
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("生产了" + i + "只鸡");
container.push(new Chicken(i));
}
}
}
4.Consumer类,消费者
package com.thread.test;
//消费者
public class Consumer extends Thread {
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("消费了-->" + container.pop().id + "只鸡");
}
}
}
5.PCTest类,程序入口
package com.thread.test;
/*
生产者消费者模型-->利用缓冲区解决:管程法
要素:生产者,消费者,产品,缓冲区
*/
public class PCTest {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Producer(container).start();
new Consumer(container).start();
}
}
运行结果:
信号灯法
代码实现
1.Program类,产品–>节目
package com.thread.test;
//产品-->节目
public class Program {
//演员表演,观众等待 true
//观众观看,演员等待 false
String voice; //表演的节目
boolean flag = true; //标志位
//表演,对于演员来说
public synchronized void play(String voice) {
if (!flag) { //flag为false的情况
try {
this.wait(); //演员等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//flag为true的情况
System.out.println("演员表演了:" + voice);
//通知观众观看
this.notifyAll(); //通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
//观看,对于观众来说
public synchronized void watch() {
if (flag) { //flag为true的情况
try {
this.wait(); //观众等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了:" + voice);
//通知演员表演
this.notifyAll();
this.flag = !this.flag;
}
}
2.actor类,生产者–>演员
package com.thread.test;
//生产者-->演员
public class actor extends Thread {
Program program; //产品
public actor(Program program) {
this.program = program;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i % 10 == 0) {
this.program.play("广告-抖音,记录美好生活");
} else {
this.program.play("正在为你播放:延禧攻略");
}
}
}
}
3.audience类,消费者–>观众
package com.thread.test;
//消费者-->观众
public class audience extends Thread {
Program program; //产品
public audience(Program program) {
this.program = program;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
program.watch();
}
}
}
4.PCTest类,程序入口
package com.thread.test;
/*
生产者消费者模型2-->利用标志位解决:信号灯法
*/
public class PCTest {
public static void main(String[] args) {
//创建实现类对象
Program program = new Program();
//创建Thread对象,开启线程
actor threadActor = new actor(program);
audience threadAudience = new audience(program);
threadActor.start(); //开启线程
threadAudience.start();
}
}
线程池【重点】
d
代码实现
1.MyThreadPool类,编写线程体
package com.thread.test;
//线程池
public class MyThreadPool implements Runnable {
//编写线程体
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
2.ThreadPoolTest类,线程池
package com.thread.test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池
public class ThreadPoolTest {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyThreadPool()); //需要一个Runnable接口
service.execute(new MyThreadPool());
service.execute(new MyThreadPool());
service.execute(new MyThreadPool());
//2.关闭连接池
service.shutdown();
}
}
运行结果:
多线程总结
线程实现的三个方式,锁
package com.thread.demo09;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/*
线程实现的三个方式
*/
public class AllInOne {
public static void main(String[] args) {
//1
new MyThread1().start();
//2
new Thread(new MyThread2()).start();
//3
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
try {
Integer integer = futureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//1.继承Thread类
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("MyThread1");
}
}
//2.实现Runnable接口
class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2");
}
}
//3.实现Callable接口
class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyThread3");
return 100;
}
}
转自:https://www.cnblogs.com/hongchengg/p/15239547.html