1.4 Runnable&&初识并发问题
1.4.1 实现 Runnable 接口
其实 Thread 这个类 就是 Runnable 接口的 实现,所以我们 建议 直接 用 Runnable 接口,而不是 去用 二次封装的 Thread 是有道理的。
需要注意的是,Runnable 接口 在 开启线程之前,需要 建立 一个 Thread 对象,作为 代理,用来 开启 线程!
package www.muquanyu.lesson01;
public class RunnableDemo implements Runnable {
@Override
public void run() {
for(int i = 0;i<200;++i)
{
System.out.println("线程:" + i);
}
}
public static void main(String[] args) {
//需要 建立一个 Thread 对象
Thread thread = new Thread(new RunnableDemo());
thread.start();
for(int i = 0;i<200;++i)
{
System.out.println(i);
}
}
}
- 两者的区别
- 继承 Thread 类
- 子类继承 Thread 类 具备 多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP 单继承的局限性
- 实现 Runnable 接口
- 实现接口 Runnable 具有多线程能力
- 启动线程:传入目标对象 + Thread 对象.start()
- 推荐使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用
比如说 三个线程 同时 跑 一个 事务!如果你继承 Thread 是无法实现的。只有 实现 Runnable 接口 才可以。
1.4.2 初识并发问题
比如我们 三个人 去 抢 十张票,那么 这三个 人 就相当于 三个线程,并且 在做同一个事情,那就是 抢票。也就相当于 在处理 同一个事务下的 资源。这种情况,就会 出现 “并发” 问题。
package www.muquanyu.lesson01;
import sun.misc.PostVMInitHook;
public class 多线程同时处理一个事务 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true)
{
if(ticketNums <= 0)
{
break;
}
//模拟延迟
try {
Thread.sleep(200);
}catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"票");
}
}
public static void main(String[] args) {
多线程同时处理一个事务 test = new 多线程同时处理一个事务();
new Thread(test,"小明").start();
new Thread(test,"老师").start();
new Thread(test,"黄牛党").start();
}
}
我们可以 清晰地 看到 我们 开了 三个 线程,在 处理 一个 事务。分别是 小明、老师、黄牛党,在 同一个事务里面,抢去 资源(票)。但是 票数 是有限的,所以 肯定会 出现 乱抢的情况。因为数据量 过小,我们 就加上了 sleep 延迟,这样就会 更加可观的看到 结果。
这个时候,我们发现 可能会有人 抢到 -1 这个票,也可能会有人 抢到 的票 是 同一个。这就叫 并发问题。
当多个 线程对象,操作同一个 资源的情况下,线程是不安全的。最明显的 现象 就是 数据紊乱。。。
1.4.3 并发问题(龟兔赛跑)
我们还有一个 典型的并发问题。————> 龟兔赛跑-Race
乌龟和兔子同时都在一条跑道上赛跑,肯定要争先抢后的去跑每一步,道路上的地方就那么大。但是他俩 确实 就有 其中 一个 可能会 后抢到 道路的那块资源,或 先抢到 道路上的那块资源,导致最后 谁赢(谁先抢到 资源,或抢的多,肯定就赢了。)
- 首先来个赛道距离,然后要离终点越来越近。(可以设定跑了多少米)
- 判断比赛是否结束(距离为 0 不就结束了吗)
- 打印出 胜利者
- 龟兔赛跑开始
- 故事中乌龟赢了,兔子是在睡觉的,所以我们来模拟 一下 兔子睡觉就行了。
- 终于,乌龟赢得了 比赛。
package www.muquanyu.lesson01;
//模拟龟兔赛跑
public class Race 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 gameOvera(int i) {
return false;
}
//判断是否完成比赛
private boolean gameOver(int steps)
{
if(winner != null)
{
return true;
}
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();
Thread 兔子 = new Thread(race, "兔子");
Thread 乌龟 = new Thread(race, "乌龟");
兔子.start();
乌龟.start();
}
}
在模拟这个 过程的时候,我们 让 兔子 在 10米的地方 睡觉了,但是 你发现,兔子 还没等睡觉呢,乌龟可能就跑到了。这是因为 当代 CPU 处理的 太快了,它会 自动的 帮你调度。这也是为什么 我们当初 说 这个 所谓的 调度 是你控制不了的,CPU 和 系统 让你怎么来 你就得 怎么来(当然 人家都是 往 最优的 策略上 去 调度的)。