实现runnable的多线程怎么调试?
摘要:本篇文章主要讲解java多线程,包括线程,锁,各种并发问题等
0、Intro
0.1 进程与线程
program:程序(源码)
Process:进程:程序的一次执行过程,是系统资源分配的单位
Thread:线程:CPU调度和执行的单位
常见的基本线程:
main():主线程——系统入口,用于执行整个程序
gc():垃圾回收线程
这两个线程是所有进程不创建也会有的线程
注:
只有多核CPU才是并行多线程,单核只能做到并发多线程
在一个进程中,如果开辟了多个线程,线程的运行由操作系统安排,操作系统的先后顺序是不能人为干预的
同一份资源会存在资源抢夺的问题,需要加入并发控制
线程会带来额外的开销,如CPU调度时间,并发控制开销
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
1、!线程实现
1.1 方法一、继承Thread类
1.1.1 步骤
自定义线程类继承Thread类
重写run()方法:所有线程需要做的事都放到run方法中
创建线程对象,调用start()方法启动线程
1.1.2 run与start
调用run方法并没创建线程,而是主线程转而去执行run方法而已
调用start才是创建了线程,start方法开启线程后不一定立即执行,而是和主线程并发,由OS调度
1 | /** |
注:一个类只能创建一个线程,要创建多个线程,就要创建多个类
注:Thread类也实现了runnable接口
1.2方法二、实现Runnable接口
1.2.1 步骤
- 定义线程类,实现Runnable接口
- 实现run方法,编写方法执行体
- 创建线程对象,创建Thread类的对象作为代理,通过代理的start方法启动线程
1 | public class ThreadARunnable implements Runnable{ |
1.2.2 代理
Runnable方式相比于继承Thread方式,最主要的区别就是线程启动方面:
1 | //ClassA extends Thread |
代理方式可以这么理解:
Runnable对象仅仅作为Thread对象的Target,Runnable实现类里面的run方法仅作为线程执行体,而实际的线程对象依然是Thread实例
值得注意的是,Runnable对象整体作为线程的target,所以多个线程既共享了Runnable对象的方法又共享了Runnable的变量
1.3 对比
继承法
- 启动线程:start
实现法
- 启动线程:传入目标对象+Thread对象.start
- 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
1.4 小实验:龟兔赛跑问题
1 | package com.mars.javaThread.concurrent; |
1.5 方法三:实现Callable接口(了解)
1 | public class ThreadACallable implements Callable<Object> { |
1.6 静态代理模式(——Thread中的设计模式)
接口变量指向接口的实现对象,调用方法时调用的是实现对象的重写方法
1 | package com.mars.DesignPattern; |
静态代理模式总结:
1、真实对象和代理对象都要实现同一个接口
2、代理对象要代理真实角色(即必须把角色传给代理对象)
3、好处
- 真实对象只需要专注于自己的核心工作
- 其他前仆后继的工作由代理对象来完成即可
静态代理模式的简化写法:
1 | new WedingCompany(new You()).happyMarry(); |
1.7 lamda表达式
1.7.1 介绍
1.7.2 逐步优化过程
1 | package com.mars.javaThread; |
听到再次简化
2、线程状态
3、!线程同步
初始线程同步问题
看以下一段代码的执行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 public class TicketsIntro implements Runnable{
private int tickets = 10;
public void run() {
while(true){
if(tickets<=0) break;
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第" + tickets-- + "张票");
}
}
}
public static void main(String[] args) {
TicketsIntro ticketsIntro = new TicketsIntro();
new Thread(ticketsIntro,"小明").start();
new Thread(ticketsIntro,"老湿").start();
new Thread(ticketsIntro,"黄牛").start();
}
/*
黄牛拿到了第9张票
小明拿到了第10张票
老湿拿到了第8张票
黄牛拿到了第7张票
小明拿到了第6张票
老湿拿到了第5张票
老湿拿到了第4张票
黄牛拿到了第3张票
小明拿到了第2张票
黄牛拿到了第1张票👈
老湿拿到了第1张票👈
小明拿到了第0张票
*/以上代码的运行结果,甚至出现了两个人拿同一张票的情况,这就是由于不加限制时进程并发访问资源造成的混乱情况。
因为只有一个进程,所以只有一套数据???