第一章 多线程
1 理解
- 多线程: 多任务执行,多路径执行
优点:
提高性能
提高效率 - 三高:高并发、高性能、高可用
2 进程与线程之间的区别
- 进程 : 系统中的程序,一个进程之间可以包含1~n个线程,系统中资源分配的最小单位,每个进程都有自己的代码与数据空间,进程之间的切换开销较大。
- 线程 : 程序中的顺序流,线程是cpu调度与执行的最小单位,多个线程之间共享进程的代码和数据空间,每一个线程都有自己的程序计数器运行栈,线程之间切换开销较小,一个cpu同一时刻只能调度一个线程。
3 线程学习内容与重点
- 线程的概念,优缺点
- 创建线程的方式 *
- 线程的状态
- 线程安全问题 *
- 线程通信
第二章 创建线程
1 创建线程的方式
- 继承Thread,重写run方法 + start开启线程
- 实现Runnable接口,重写run方法 + start开启线程
- 实现Callable接口,重写call方法 + 线程池
- 内部类
2 继承Thread,重写run方法 + start开启线程
public class Class001_Thread extends Thread{
/*
run方法中定义线程体
*/
@Override
public void run() {
for(int i = 1;i<=20;i++){
System.out.println("一边喝水");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
//创建一个线程对象
Class001_Thread th = new Class001_Thread();
//开启线程
th.start();
for(int i = 1;i<=20;i++){
System.out.println("一边讲课....");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3 实现Runnable接口,重写run方法 + start开启线程
3.1 理解
- 优点:
- 接口多实现,类的单继承
- 实现资源共享
- 重写方法对异常抛出的要求:
重写方法上抛出的异常类型<=被重写方法上异常的抛出类型 - 创建一个线程,Thread静态代理,Class002_Thread()真实角色
Thread th = new Thread(new Class002_Thread());
3.2 代码
public class Class002_Thread implements Runnable{
public static void main(String[] args) {
//创建一个线程,Thread静态代理,Class002_Thread()真实角色
Thread th = new Thread(new Class002_Thread());
//开启线程
th.start();
for(int i = 1;i<=20;i++){
System.out.println("吃饭...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
定义线程体
*/
@Override
public void run() {
for(int i = 1;i<=20;i++){
System.out.println("看电视...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.3 练习 12306购票
3.3.1 注意
- 12306购票网站
- 3个人共享100张票
- 有线程不安全问题
3.3.2 代码
public class Class003_Web12306 implements Runnable{
//共享资源 100张票
int tickets = 100;
/*
线程体 : 购票
*/
@Override
public void run() {
while(true){
if(tickets<=0){
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
}
}
public static void main(String[] args) {
Class003_Web12306 web = new Class003_Web12306();
//创建线程
Thread th1 = new Thread(web,"张三");
Thread th2 = new Thread(web,"李四");
Thread th3 = new Thread(web,"王五");
//线程开启
th1.start();
th2.start();
th3.start();
}
}
3.4 练习 龟兔赛跑
3.4.1 要求
模拟龟兔赛跑
- 兔子每跑十步休息一下 100毫秒
- 乌龟正常跑
- 只要有参赛者跑了100步就结束
3.4.2 代码
public class Class004_Racer implements Runnable{
//记录赢的人的名字
private String winner = null; //控制游戏结束,线程的结束
@Override
public void run() {
for(int i=1;i<=100;i++){
try {
Thread.sleep(2); //模拟跑步所耗时长
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步....");
if("兔子".equals(Thread.currentThread().getName()) && i%10==0){
try {
Thread.sleep(10); //如果是兔子,修改10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断是否还要继续
if(!checkOver(i)){
break;
}
}
}
//判断是否进行下一次步
//返回值: false比赛结束 true比赛继续
public boolean checkOver(int step){
if(winner!=null){
return false;
}
if(step==100){
winner = Thread.currentThread().getName();
return false;
}
return true;
}
public static void main(String[] args) {
//一场比赛
Class004_Racer racer = new Class004_Racer();
//两个参赛者
Thread th1 = new Thread(racer,"乌龟");
Thread th2 = new Thread(racer,"兔子");
//准备就绪
th1.start();
th2.start();
}
}
4 实现Callable接口,重写call方法 + 线程池
4.1 理解
- 开启线程第三种方式 : (了解)
- 实现juc包下的Callable接口,重写call方法 + 线程池
- 优点: call方法可以抛出异常,可以定义返回值,run方法不可以
4.2 代码
public class Class005_Racer implements Callable<Integer> {
//记录赢的人的名字
private String winner = null; //控制游戏结束,线程的结束
/*
返回值: 参赛者的总步数
*/
@Override
public Integer call() {
for(int i=1;i<=100;i++){
try {
Thread.sleep(2); //模拟跑步所耗时长
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在跑第"+i+"步....");
if("pool-1-thread-1".equals(Thread.currentThread().getName()) && i%10==0){
try {
Thread.sleep(10); //如果是兔子,修改10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//判断是否还要继续
if(!checkOver(i)){
return i;
}
}
return null;
}
//判断是否进行下一次步
//返回值: false比赛结束 true比赛继续
public boolean checkOver(int step){
if(winner!=null){
return false;
}
if(step==100){
winner = Thread.currentThread().getName();
return false;
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//一场比赛
Class005_Racer racer = new Class005_Racer();
//两个参赛者
//1)创建一个固定大小2个线程的线程池--> 得到一个线程池提供的执行服务
ExecutorService server = Executors.newFixedThreadPool(2);
//2)提交任务 submit(Callable<T> task)
Future<Integer> result1 = server.submit(racer);
Future<Integer> result2 =server.submit(racer);
//3)得到结果 V get()
Integer num1 = result1.get();
Integer num2 = result2.get();
System.out.println(num1);
System.out.println(num2);
//4)关闭服务
server.shutdown();
}
}
5 内部类
5.1 要求
- 静态内部类
- 局部内部类
- 匿名内部类
- lambda表达式
5.2 代码
public class Class006_Thread {
//静态内部类
static class Inner implements Runnable{
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.println("一边喝水...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
//局部内部类
class Inner2 implements Runnable{
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.println("一边看电影...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
new Thread(new Inner()).start();
new Thread(new Inner2()).start();
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=20;i++){
System.out.println("一边看吃零食...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
//lambda表达式
new Thread(()->{
for(int i=1;i<=20;i++){
System.out.println("一边吸烟...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
第三章 线程状态
1 线程的状态
- 新生状态 : new
- 就绪状态
- 运行状态 : cpu调度
- 阻塞状态
- 终止状态
2 注意
- 一个线程如果一旦进入终止状态,不可恢复
- 一个线程如果进入到阻塞状态,阻塞解除之后,不能直接恢复到运行,会直接恢复到就绪状态,等待cpu的调度
3 状态切换
3.1 如何让线程进入到就绪状态
- start()
- 阻塞解除
- cpu的调度切换
- yield 礼让线程
3.2 如何让线程进入阻塞状态
- sleep()
- join() 插队线程
- wait() 等待
- IO操作
3.3 如何让线程进入终止状态:
- 正常执行完毕
- stop() 过时–> 不推荐使用
- 通过标识判断
4 sleep 线程休眠
4.1 理解
- static void sleep(long millis) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static void sleep(long millis, int nanos) 执行ms+ns - 当一个线程调度sleep进入睡眠状态,让出cpu的资源
- 抱着资源睡觉: 这个资源不是cpu的资源, 值的是对象的锁资源
4.2 作用
- 放大问题出现的可能性
- 模拟网络延迟
4.3 练习 模拟倒计时 10 9 8 7…
/**
* 倒计时程序
* 程序启动后要求用户输入一个数字
* 然后每秒递减1并输入该数字,递减至0时程序停止
*/
public class Class001_ThreadState {
public static void main(String[] args) {
System.out.println("倒计时开始了!");
System.out.println("请设置倒计时时间!");
Scanner scanner =new Scanner(System.in);
int num =scanner.nextInt();
System.out.println("倒计时开始!");
//while循环
while(num>=0) {
System.out.println(num--);
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("倒计时结束!!!");
}
}
5 礼让线程
5.1 理解
- yield 礼让线程
- 当线程执行到yield方法,会让出cpu的资源,同时线程会恢复就绪状态
5.2 代码
public class Class002_Yield implements Runnable{
public static void main(String[] args) {
Class002_Yield cy = new Class002_Yield();
new Thread(cy,"A").start();
new Thread(cy,"B").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始了");
Thread.yield(); //礼让
System.out.println(Thread.currentThread().getName()+"结束了");
}
}
6 插队线程
6.1 理解
- join() 插队线程
- A线程执行过程中,如果B线程插队了,A线程就会进入到阻塞状态,等待插队线程执行完毕|等待执行的时间,A线程会恢复到就绪状态
void join() 等待这个线程死亡。
void join(long millis) 此线程最多等待 millis毫秒。
void join(long millis, int nanos) 此线程最多等待 millis毫秒加上 nanos纳秒。
6.2 代码
public class Class003_Join{
public static void main(String[] args) {
new Thread(new Father()).start();
}
}
class Father implements Runnable{
@Override
public void run() {
System.out.println("想抽烟了....");
System.out.println("给他钱,让儿子去买烟....");
//开启儿子线程,让儿子线程插队,去买烟
Thread th = new Thread(new Son());
//先就绪后插队
th.start();
try {
th.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("接过烟...吸一口....");
}
}
class Son implements Runnable{
@Override
public void run() {
System.out.println("接过钱去买烟...");
System.out.println("路上路过一个一家游戏厅....进去玩10s钟...");
for(int i=1;i<=10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+"s过去了");
}
System.out.println("赶紧去买烟...");
System.out.println("把烟给老爸....");
}
}
7 中断线程
7.1 理解
- void interrupt() 为线程添加一个中断标识
- boolean isInterrupted() 测试此线程是否已被中断,是否曾经调用过interrupt方法添加了中断标识
- static boolean interrupted() 测试当前线程是否已被中断,是否曾经调用过interrupt方法添加了中断标识,同时复位标识
7.2 注意
- 当调用sleep方法线程睡眠时 : InterruptedException - 如果有任何线程中断了当前线程。 抛出此异常时,将清除当前线程的中断状态 。
7.3 代码
public class Class004_Interrupt implements Runnable{
@Override
public void run() {
for(int i=1;i<=50;i++){
System.out.println(Thread.currentThread().isInterrupted());
if(Thread.interrupted()){
System.out.println("当前线程已被中断....");
System.out.println(Thread.currentThread().isInterrupted());
break;
}
System.out.println(i);
/*try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
public static void main(String[] args) throws InterruptedException {
Thread th = new Thread(new Class004_Interrupt());
System.out.println(th.isInterrupted());
//开启线程
th.start();
Thread.sleep(1);
//为th线程添加中断标识
th.interrupt();
//System.out.println(th.isInterrupted());
}
}
8 获取线程状态
8.1 理解
- Thread.State getState() 获取线程状态
- Thread.State 线程状态。 线程可以处于以下状态之一:
- NEW : new Thread()
- 尚未启动的线程处于此状态。
- RUNNABLE : 就绪|运行,
- 在Java虚拟机中执行的线程处于此状态。
- BLOCKED : 等待对象锁的阻塞状态
- 被阻塞等待监视器锁定的线程处于此状态。
- WAITING : wait(),join()等
- 无限期等待另一个线程执行特定操作的线程处于此状态。
- TIMED_WAITING : 与时间相关的等待,sleep(ms),join(ms),wait(ms)…
- 正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
- TERMINATED : 终止
- 已退出的线程处于此状态。
- NEW : new Thread()
8.2 代码
public class Class005_getState implements Runnable{
@Override
public void run() {
for (int i=1;i<=20;i++){
System.out.println("i = "+i);
if(i==10){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
Thread th = new Thread(new Class005_getState());
System.out.println(th.getState()); //NEW
th.start();
System.out.println(th.getState());
while(true){
System.out.println("循环判断中 : "+th.getState());
//当th线程终止,循环结束
if(Thread.State.TERMINATED==th.getState()){
break;
}
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
9 线程优先级
9.1 理解
- 方法线程优先执行的可能性
- 1~10 设置值
1最小 ->static int MIN_PRIORITY 线程可以拥有的最低优先级。
10最大->static int MAX_PRIORITY 线程可以拥有的最大优先级。
5默认值 ->static int NORM_PRIORITY 分配给线程的默认优先级。 - 优先级高不一定先执行,只是概率问题
9.2 方法
- int getPriority() 返回此线程的优先级。
- void setPriority(int newPriority) 更改此线程的优先级。
9.3 代码
public class Class006_Priority implements Runnable{
public static void main(String[] args) {
Class006_Priority cp = new Class006_Priority();
Thread th1 = new Thread(cp,"A");
Thread th2 = new Thread(cp,"B");
Thread th3 = new Thread(cp,"C");
th1.setPriority(1);
th2.setPriority(10);
th3.setPriority(Thread.MAX_PRIORITY);
//获取优先级
System.out.println(th1.getPriority());
System.out.println(th2.getPriority());
System.out.println(th3.getPriority());
th1.start();
th2.start();
th3.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了.....");
}
}
10 守护线程
10.1线程的分类
- 用户线程 : 默认线程为用户线程
- 守护线程
10.2 理解
- 守护线程 : 守护用户线程的
当所有的用户线程全都执行完毕,守护线程直接结束 - 垃圾回收机制典型守护线程
void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程(false)。
10.3 代码
public class Class007_Daemon implements Runnable{
public static void main(String[] args){
Thread th = new Thread(new Class007_Daemon());
//设置守护线程
th.setDaemon(true);
th.start();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程->用户线程结束");
}
@Override
public void run() {
int i = 1;
while(true){
System.out.println("守护线程............."+i);
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
第四章 线程安全
1 理解
- 线程安全 :
当多个线程同时操作同一份资源,才有可能出现线程不安全问题 - 同步锁 synchronized : 有可能出现数据不安全的代码段,让多个线程排队执行
2 同步的使用
2.1 理解
- 同步条件 : 协调多个线程排队执行的条件 -->对象的锁
- 同步的代码 : 需要多个线程排队执行的代码
2.2 synchronized用法
-
修饰方法 : 同步方法
- 同步实例方法
条件 : 调用成员方法的对象
代码范围 : 整个方法体 - 同步静态方法
条件 : 锁类->锁的类的Class对象->一个类只有一个,加载到内存中究存在Class对象,不变,唯一
代码范围 : 整个方法体
- 同步实例方法
-
修饰块 : 同步块
synchronized(条件){
排队执行的代码;
} 条件 : 类名.class | this | 资源
2.3 注意
- 同步的代码范围太大,效率太低
- 同步的代码范围太小,锁不住,不安全
3 同步方法
3.1 理解
- 同步方法控制案例的数据安全
在方法上通过synchronized关键字修饰方法
成员方法
锁的是调用成员方法的对象
静态方法
锁的是类的Class对象
3.2 优缺点
- 优点: 简单,直接同步整个方法体
- 缺点: 代码范围太大,效率较低
3.3 代码
public class Class002_Web12306 implements Runnable{
//共享资源 100张票
int tickets = 100;
@Override
public void run() {
while(true){
if(!buyTicket()){
break;
}
//方法cpu切换调度的可能性
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//一次购票的过程,每一个线程排队购票
//返回值 : true-->继续购买 false->结束购买
public synchronized boolean buyTicket(){
if(tickets<=0){
return false;
}
/*模拟一次购票的时间*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
return true;
}
public static void main(String[] args) {
Class002_Web12306 web = new Class002_Web12306();
//创建线程
Thread th1 = new Thread(web,"张三");
Thread th2 = new Thread(web,"李四");
Thread th3 = new Thread(web,"王五");
//线程开启
th1.start();
th2.start();
th3.start();
}
}
4 同步块
- 同步块 :
synchronized(条件){
排队执行的代码;
} - 条件 : 类名.class | this | 资源
4.1 锁类
4.1.1 理解
- 锁类: 锁类相当于锁住了这个类的所有对象,如果只想要锁当前类的某一个对象,建议锁那一个具体的对象this
4.1.2 代码
public class Class003_Web12306 implements Runnable{
//共享资源 100张票
int tickets = 100;
@Override
public void run() {
while(true){
//同步块--> 锁类
synchronized (Class003_Web12306.class){
if(tickets<=0){
break;
}
/*模拟一次购票的时间*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
}
//方法cpu切换调度的概率
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Class003_Web12306 web = new Class003_Web12306();
//创建线程
Thread th1 = new Thread(web,"张三");
Thread th2 = new Thread(web,"李四");
Thread th3 = new Thread(web,"王五");
//线程开启
th1.start();
th2.start();
th3.start();
}
}
4.2 锁this
4.2.1 理解
- 锁this: 成员方法中this默认指代当前调用成员方法的对象
- 如果锁this,就是锁一个对象,相当于锁住了这个对象的所有资源(成员),如果指向锁一个对象的某一个资源,建议可以直接锁资源
4.2…2 代码
public class Class004_Web12306 implements Runnable{
//共享资源 100张票
int tickets = 100;
@Override
public void run() {
while(true){
//同步块--> 锁类
synchronized (this){
if(tickets<=0){
break;
}
/*模拟一次购票的时间*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
}
//方法cpu切换调度的概率
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Class004_Web12306 web = new Class004_Web12306();
//创建线程
Thread th1 = new Thread(web,"张三");
Thread th2 = new Thread(web,"李四");
Thread th3 = new Thread(web,"王五");
//线程开启
th1.start();
th2.start();
th3.start();
}
}
4.3 锁资源
4.3.1 理解
- 锁资源 : 锁对象,锁不变的内容
自定义引用数据类型的对象地址永远不变
4.3.2 代码
public class Class005_Web12306 implements Runnable{
//共享资源 100张票
Tickets tickets = new Tickets();
@Override
public void run() {
while(true){
//同步块--> 锁资源
synchronized (tickets){
if(tickets.num<=0){
break;
}
/*模拟一次购票的时间*/
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num--);
}
//方法cpu切换调度的概率
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Class005_Web12306 web = new Class005_Web12306();
//创建线程
Thread th1 = new Thread(new Class005_Web12306(),"张三");
Thread th2 = new Thread(new Class005_Web12306(),"李四");
Thread th3 = new Thread(new Class005_Web12306(),"王五");
//线程开启
th1.start();
th2.start();
th3.start();
}
}
//票
class Tickets{
int num = 100;
}
第五章 线程通信
1 理解
- 线程通信
生产者消费者模式 : 生产者用来生产商品,消费者就是消费产品
Object类中wait()等待,notify()唤醒,notifyAll()唤醒全部
2 方法
- wait() 等待,通过对象调用wait()方法让当前进入等待阻塞状态,会进入到调用wait方法对象的等待池中进行等待,等待被唤醒,让出cpu的资源,同时释放对象的锁
- notify() 唤醒 : 唤醒对象等待池中正在等待的线程,被唤醒的线程进入到就绪队列,需要被cpu调度同时获取对象锁才能执行
- wait()与notify()必须使用在同步环境下,用来协调多线程对共享数据存储的问题,否则会抛出异常
- IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
3 练习 人车公用街道
3.1 要求
- 街道 信号灯boolean: true绿灯 false红灯 ns南北 we东西
- 人 : 走绿灯true 人走南北ns
- 车 : 走红灯false 车走东西走向 we
- 练习 : 使用多线程实现,输出AB1CD2…YZ13
3.2 代码
public class Class007_Street {
public static void main(String[] args) {
//共享街道
Street street = new Street();
new Thread(new Person(street)).start();
new Thread(new Car(street)).start();
}
}
//街道
class Street{
//信号灯
private boolean flag = false;
public Street() {
}
public Street(boolean flag) {
this.flag = flag;
}
//ns -> 人
public synchronized void ns(){
if(flag){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("人走");
//信号灯变灯
flag = false;
//唤醒车线程
this.notify();
//自己等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//we -> 车
public synchronized void we(){
if(!flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("车走");
//信号灯变灯
flag = true;
//唤醒人线程
this.notify();
//自己等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//人线程
class Person implements Runnable{
private Street street = null;
public Person(Street street) {
this.street = street;
}
@Override
public void run() {
while(true){
street.ns();
}
}
}
//车线程
class Car implements Runnable{
private Street street = null;
public Car(Street street) {
this.street = street;
}
@Override
public void run() {
while(true){
street.we();
}
}
}