Java多线程
什么是进程?
电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。
什么是线程?
进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
- 进程是程序执行的一次过程,它是一个动态的概念。是系统资源分配的单位。
- 通常在一个进制中可以包含多个线程,线程是CPU调度和执行的单位
- 多线程是模拟出来的,在一个CPU下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉
- main()被称为主线程,为系统入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器调度,调度器与操作系统紧密相关的,先后顺序是不能人为干预。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发开销控制
- 每个线程在自己的工作内存交互,内存控制不会造成数据不一致
线程的创建
Thread. Runnable .Callabele
三种创建方式
-
Thread class 继承 Thread 类
-
Runnable 接口 实现Runnable 接口
-
Callable 接口 实现Callable 接口
-
自定义线程类继承 Thread类
-
重写Run()方法,编写线程执行体
-
创建线程对象,使用start()方法启动线程
package Thread;
//线程开启,不一定立即执行,由cpu调度
//创建线程方式一 : 继承Thread类,重写run()方法,调用 start 开启 线程
public class TestThread extends Thread {
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("你好呀"+i);
}
}
public static void main(String[] args) {
//开启 Thread线程
TestThread testThread = new TestThread();
//调用start方法
testThread.start();
//main 线程,主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程--"+i);
}
}
}
案例:下载图片
package Thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread, 实现多线程同步下载图片
public class TestThread extends Thread {
private String url;//网络图片地址
private String name;//网络图片保存地址
public TestThread(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) {
TestThread testThread1 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "1.jpg");
TestThread testThread2 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "2.jpg");
TestThread testThread3 = new TestThread("https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1113%2F041620103S8%2F200416103S8-4-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1611366394&t=5011c9066b705d0e9713f989cd6d44c8", "3.jpg");
testThread1.start();
testThread2.start();
testThread3.start();
}
}
//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO 异常,dwonloader方法出现问题");
}
}
}
实现Runnable
- 定义MyTunnable类实现Runnable接口
- 实现Run()方法,编写线程执行体
- 创建线程执行体,调用start()方法启动
package Thread;
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码---"+i);
}
}
public static void main(String[] args) {
//创建runnable接口实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象来开启我们的线程,代理
//把 runnable的接口对象 丢进去
// Thread thread = new Thread(testThread3);
//threa 调用 start
// thread.start();
new Thread(testThread3).start();
for (int i = 0; i < 200; i++) {
System.out.println("我在学习!!"+i);
}
}
}
建议使用 Runnable
避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
//一份资源
StratThread station = new StartThread();
//多个代理
new Thread(Startion,"小明").start();
new Thread(Startion,"小红").start();
new Thread(Startion,"小强").start();
并发问题
多个线程同时操作 同一个对象 有一个问题 : 发生了数据不安全 数据紊乱
抢火车票的例子
package Thread;
import static java.lang.Thread.sleep;
//多个线程同时操作 同一个对象 有一个问题 : 发生了数据不安全 数据紊乱
//抢火车票模拟
public class TesrThread4 implements Runnable{
//票书
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//制造延时
try {
sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
}
}
public static void main(String[] args) {
TesrThread4 ticket = new TesrThread4();
new Thread(ticket,"小明").start();
new Thread(ticket,"老师").start();
new Thread(ticket,"黄牛党").start();
}
}
输出结果:
小明拿到了第10张票
老师拿到了第9张票
黄牛党拿到了第10张票
小明拿到了第8张票
黄牛党拿到了第7张票
老师拿到了第8张票
小明拿到了第6张票
黄牛党拿到了第4张票
老师拿到了第5张票
老师拿到了第3张票
小明拿到了第1张票
黄牛党拿到了第2张票
案例 龟兔赛跑
-
需要赛道 , 越跑距离终点越近
-
判断比赛是否结束
-
打印出胜利者
-
龟兔赛跑开始
-
乌龟赢,兔子需要睡觉
-
乌龟赢了比赛
package Thread;
public class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//判断比赛是否结束
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();
}
}
输出结果:
兔子-->跑了68步
兔子-->跑了69步
兔子-->跑了70步
兔子-->跑了71步
兔子-->跑了72步
兔子-->跑了73步
兔子-->跑了74步
乌龟-->跑了91步
兔子-->跑了75步
兔子-->跑了76步
兔子-->跑了77步
兔子-->跑了78步
兔子-->跑了79步
兔子-->跑了80步
乌龟-->跑了92步
乌龟-->跑了93步
乌龟-->跑了94步
乌龟-->跑了95步
乌龟-->跑了96步
乌龟-->跑了97步
乌龟-->跑了98步
乌龟-->跑了99步
Winner is 乌龟
兔子-->跑了81步
静态代理
举办 婚礼
- 人 婚礼的主体
- 婚庆公司 代理承办婚礼
- 婚礼 去实现婚礼
package Thread;
//静态代理模式总结
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色
//好吃 :
//代理对象可也做很多代理对象做不了的事情
//真实对象做自己的事情
public class StaticProxy {
public static void main(String[] args) {
WeddingComany weddingComany = new WeddingComany(new You());
weddingComany.HappyMarry();
}
}
interface Marry{
void HappyMarry();
}
//真实角色 结婚的人
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("我要结婚了,超开心");
}
}
//代理角色 承办结婚的机构
class WeddingComany implements Marry{
//真实目标角色
private Marry target;
public WeddingComany(Marry You) {
this.target = You;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();//这就是真实对象。
after();
}
public void before(){
System.out.println("布置现场");
}
public void after(){
System.out.println("打扫干净");
}
}
输出结果:
布置现场
我要结婚了,超开心
打扫干净
Lamda 表达式
函数式接口定义
-
任何接口 , 如果只含唯一一个抽象方法,那么它就是一个函数式接口
-
Public interface Runnable{ public abstract void run(); }
-
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。
无参数
package Thread;
//推导Lambda 表达式
public class Lambda {
//3 静态内部类
static class Like2 implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda2");
}
}
public static void main(String[] args) {
Ilike like = new Like();
like.lambda();
//静态内部类
like = new Like2();
like.lambda();
//4 局部内部类
class Like3 implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda3");
}
}
like = new Like3();
like.lambda();
//5匿名内部类,没有 类的名称 ,必须借助 接口 或者 父类
like = new Ilike() {
@Override
public void lambda() {
System.out.println("I like Lambda4");
}
};
like.lambda();
//6 用 lamdba 简化 只有一个 接口 和 一个 方法 所有 中 lambda 默认 找到方法
like = ()-> {System.out.println("I like Lambda5");};
like.lambda();
}
}
//1定义一个函数式接口 只有一个接口
interface Ilike{
void lambda();
}
//2实现 接口 实现 他的方法
class Like implements Ilike{
@Override
public void lambda() {
System.out.println("I like Lambda");
}
}
输出
I like Lambda
I like Lambda2
I like Lambda3
I like Lambda4
I like Lambda5
有参数
package Thread;
public class Lambda_CanShu {
public static void main(String[] args) {
ILove love = (int a)-> {
System.out.println("i love"+a);
};
//1Lambda 表达式简化 去掉 int 多个参数 也可以都去掉 但是必须加上括号。
love = (int a)-> {
System.out.println("i love"+a);
};
//2Lambda简化 去掉括号
love =a -> {
System.out.println("i love"+a);
};
//3 Lambda 简化 去掉花括号
love = a-> System.out.println("i Love u ");
love.love(1);
}
}
//接口必须为函数式接口 : 接口只有一个方法
interface ILove{
void love(int a);
}
线程的五大状态
停止进程
- 不推荐使用JDK停供stop(),destory() 方法【已废弃】
- 推荐线程自己停止下来
- 建议使用一个标志位终止变量 当Flag = false 则终止程序运行
package Thread;
//测试 stop
//1 建议 线程正常 停止, 利用次数,不建议死循环
//2 建议使用标志位 设置 一个 标志位
//3 不要使用 stop 或者 destory 等 过时或者 JDK不建议使用的方法
public class TestStop implements Runnable{
// 1.设置一个标志位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run----Thread"+ i++);
}
}
// 2. 设置一个公开的方法停止进程,转换标志位
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main" + i);
if(i == 500){
//调用stop()方法
testStop.stop();
System.out.println("该线程停止");
}
}
}
}
线程休眠
-
sleep(毫秒)
-
存在 InterruptException 中断异常
-
sleep时间到达后 线程进入就绪状态
-
sleep可以模拟网络延时 倒计时等
-
每个对象都有一个锁,sleep不会释放锁
-
放大问题的发生性
package Thread;
import java.text.SimpleDateFormat;
import java.util.Date;
//模拟网络 延时 : 放大问题的发生性
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
//tenDown();倒计时
//打印当前时间
Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
while (true){
Thread.sleep(1000);
startTime = new Date(System.currentTimeMillis());//刷新当前时间
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
}
}
//模拟倒计时
public static void tenDown() throws InterruptedException{
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num <= 0){
break;
}
}
}
}
Join
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
- 可以想象成插队
- 少用 join 会让线程堵塞
package Thread;
import javax.sound.midi.Soundbank;
//测试Join方法 想象成 插队
public class Testjoin implements Runnable{
public static void main(String[] args) throws InterruptedException {
//启动我们的线程
Testjoin testjoin = new Testjoin();
Thread thread = new Thread(testjoin);
thread.start();
//主线程
for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();//插队
}
System.out.println("main"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程VIP来了"+i);
}
}
}
观测线程状态
- new
- 新生
- runnable
- 就绪(虚拟机)
- blocked
- 阻塞
- waitting
- 等待另一个线程的特定动作
- Timed_watting
- 等待另一个线程执行完
- terminated
- 已退出的线程处于此状态 不能被再次 start()
观测线程状态
package Thread;
//观测线程状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);//睡觉5秒 线程一直等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("睡醒了");
});
//观测状态
Thread.State state = thread.getState();
System.out.println(state); // 新生
//观测启动后
thread.start();
state = thread.getState();
System.out.println(state);// Run 跑起来了
//只要线程不中止就一直输出状态
while (state != Thread.State.TERMINATED){
Thread.sleep(100);
state = thread.getState();//更新线程的状态
System.out.println(state);//输出状态
}
thread.start();//会报错 ,停止后的线程 不能继续开始
}
}
输出结果:
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
///////
TERMINATED
线程优先级
-
Java提供一个 线程调度器来监控程序中启动后进去就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
-
线程优先级 用数字表示 范围 1~~10
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 1;
- Thread.NORM_PRIORITY = 1;
-
使用以下方法改变或获取优先级
- getPriority()
- setPriority()
package Thread;
//测试线程优先级
public class TestPriority extends Thread{
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority);
Thread t2 = new Thread(myPriority);
Thread t3 = new Thread(myPriority);
Thread t4 = new Thread(myPriority);
//先设置优先级再启动
//t1 给个默认优先级
t1.start();
t2.setPriority(1);
t2.start();
t3.setPriority(8);
t3.start();
t4.setPriority(9);
t4.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
//线程名字+线程优先级
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
输出
main-->5
Thread-0-->5
Thread-1-->1
Thread-3-->9
Thread-2-->8
守护(deamon)线程
- 线程分为 用户线程 和 守护线程
- 虚拟机 必须 确保 用户线程 执行完毕
- 虚拟机 不用 等待守护线程执行完毕
- 如 后台操作日志 , 监控内存,垃圾回收