1. 首页
  2. 网络编程教程

001-一、网络编程之多线程Runnable

Runnable

JDK说的:Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。实现类必须实现一个名称为 run 的无参数方法。
也有其它的说法是:如果让一个线程实现Runnable接口,那么当调用这个线程的对象开辟多个线程时,可以让这些线程调用同一个变量;若这个线程是由继承Thread类而来,则要通过内部类来实现上述功能,利用的就是内部类可任意访问外部变量这一特性。
个人理解主要是解决多继承问题。

Runnable优点

实现多线程,有两种方式, 一种是继承Thread类,一种是实现Runnable接口。前面已经有了Thread类可以实现线程了为何还要使用Runnable接口呢?

Runnable只是一个接口,并不能启动一个线程,因为它没有start方法,只有一个run方法,所以并不能代表一个线程。

Runnable接口和线程是两个不同的概念!因为Thread是一个能开启线程的类,并且它继承了Runnable接口重写了run方法,如果现在创建一个类的继承了Thread类了,那么这个时候还需要继承另一个类怎么办呢,java是单根继承所以这个时候就可以直接实现Runnable接口就可以了。那么这样该怎么操作呢?


class MyRunnable implements Runnable { @Override public void run() { System.out.println("线程名称:"+Thread.currentThread().getName()); } } MyRunnable myRunnable = new MyRunnable(); Thread thread1 = new Thread(myRunnable); thread1.setName("A.."); thread1.start(); System.out.println("程序结束!!!"); 结果:程序结束!!! 线程名称:A..

数据共享

先看一段继承Thread类的线程,然后在来讨论数据共享。


class MyRunnable extends Thread { int count = 5; @Override public void run() { while (count > 0) { System.out.println("线程名称:"+Thread.currentThread().getName() + ",还剩票数为:" + count--); } System.out.println("=============================="); } } 运行代码 MyRunnable myRunnable1 = new MyRunnable(); myRunnable1.setName("A.."); MyRunnable myRunnable2 = new MyRunnable(); myRunnable2.setName("B.."); MyRunnable myRunnable3 = new MyRunnable(); myRunnable3.setName("C.."); myRunnable3.start(); myRunnable2.start(); myRunnable1.start(); System.out.println("程序结束!!!"); 输出结果: 程序结束!!! 线程名称:C..,还剩票数为:5 线程名称:C..,还剩票数为:4 线程名称:C..,还剩票数为:3 线程名称:B..,还剩票数为:5 线程名称:C..,还剩票数为:2 线程名称:A..,还剩票数为:5 线程名称:B..,还剩票数为:4 线程名称:A..,还剩票数为:4 线程名称:C..,还剩票数为:1 ============================== 线程名称:A..,还剩票数为:3 线程名称:A..,还剩票数为:2 线程名称:A..,还剩票数为:1 ============================== 线程名称:B..,还剩票数为:3 线程名称:B..,还剩票数为:2 线程名称:B..,还剩票数为:1 ==============================

上面输出的是每个线程都打印出了5,4,3,2,1。现在我们想实现一个创建多个线程执行的打印并且每个线程打印的票数不允许重复,当然也可以使用这种继承的方式实现,相对来说麻烦一点,现在我们使用实现Runnable的结果来实现一下。也就是实现一个简单的打印票据系统!!!

打印票据系统

需求就是客运站有多个窗口卖票,那么票数为30张,一共有5个窗口买票。出票肯定不能重复。这个时候就可以使用Thread(Runnable target)构造方法了,这个构造方法支持一个Runnable的接口。


class MyRunnable implements Runnable { /** * 一共有30张票 */ int count = 30; @Override public void run() { try { while (count > 0) { int time = (int) (Math.random()*1000); Thread.sleep(time); System.out.println("窗口名称:"+Thread.currentThread().getName() + ",还剩票数为:" + count--); } } catch (InterruptedException e) { e.printStackTrace(); } } } 为了方便看到效果,加了一个随机数的休眠。 MyRunnable myRunnable = new MyRunnable(); //设置5个窗口卖票 Thread thread1 = new Thread(myRunnable); thread1.setName("1窗口"); Thread thread2 = new Thread(myRunnable); thread2.setName("2窗口"); Thread thread3 = new Thread(myRunnable); thread3.setName("3窗口"); Thread thread4 = new Thread(myRunnable); thread4.setName("4窗口"); Thread thread5 = new Thread(myRunnable); thread5.setName("5窗口"); //开始卖票 thread1.start(); thread2.start(); thread3.start(); thread4.start(); thread5.start(); 运行的结果已经测试出来count数据在所有线程都可以共享了. 窗口名称:3窗口,还剩票数为:28 窗口名称:2窗口,还剩票数为:30 窗口名称:1窗口,还剩票数为:26 窗口名称:4窗口,还剩票数为:29 窗口名称:5窗口,还剩票数为:27 窗口名称:3窗口,还剩票数为:25 窗口名称:1窗口,还剩票数为:24 窗口名称:3窗口,还剩票数为:23 窗口名称:5窗口,还剩票数为:22 窗口名称:5窗口,还剩票数为:21 窗口名称:4窗口,还剩票数为:20 窗口名称:1窗口,还剩票数为:19 窗口名称:2窗口,还剩票数为:18 窗口名称:4窗口,还剩票数为:17 窗口名称:3窗口,还剩票数为:16 窗口名称:2窗口,还剩票数为:15 窗口名称:5窗口,还剩票数为:14 窗口名称:3窗口,还剩票数为:13 窗口名称:3窗口,还剩票数为:12 窗口名称:1窗口,还剩票数为:11 窗口名称:4窗口,还剩票数为:10 窗口名称:2窗口,还剩票数为:9 窗口名称:5窗口,还剩票数为:8 窗口名称:3窗口,还剩票数为:7 窗口名称:4窗口,还剩票数为:6 窗口名称:2窗口,还剩票数为:5 //这里重复 窗口名称:4窗口,还剩票数为:5 //这里重复 窗口名称:4窗口,还剩票数为:4 窗口名称:5窗口,还剩票数为:3 窗口名称:1窗口,还剩票数为:2 窗口名称:5窗口,还剩票数为:1 窗口名称:4窗口,还剩票数为:0 窗口名称:3窗口,还剩票数为:-1//负数 窗口名称:1窗口,还剩票数为:-2//负数 窗口名称:2窗口,还剩票数为:-3//负数

从结果中还是有重复的数据并且还有负数出现了,不可能卖票的还有负数出现这个就是线程不安全,现在该怎么办呢?这个时候就需要使用锁(synchronized)来处理了同步了。

synchronized(锁)

synchronized表示锁,是一个关键字,只要synchronized的代码块都是同步的,其它线程要执行这段代码必须要先拿到锁(synchronized)之后才能执行这段代码的。
那么怎么锁才能让共享的数据打印出来没有重复并且没有负数呢?我们只需要改造一下 MyRunnable 类的run方法即可。


@Override public void run() { try { while (count > 0) { int time = (int) (Math.random()*10); Thread.sleep(time); synchronized (Object.class) {//加锁 if (count > 0) { System.out.println("窗口名称:"+Thread.currentThread().getName() + ",还剩票数为:" + count--); } } } } catch (InterruptedException e) { e.printStackTrace(); } }

解释一下上面代码,为什么在 synchronized 之后还需要再次判断一下 count 是否大于0呢?因为所有的线程都只是停留在 synchronized 代码块外面,而不是在 while 外面,所以这里只是解决了相同数据出现,有可能到这里本来是目前有3个线程到达了 synchronized 这段代码块的时候,目前 count 是1,当前面的线程执行完毕了就会 count--,这个时候就变成0了,然后再进去一个线程又会关闭锁,然后打印的是0,完毕之后又 count--,这个时候就变成了-1,以此内推。

结果打印


窗口名称:3窗口,还剩票数为:30 窗口名称:1窗口,还剩票数为:29 窗口名称:2窗口,还剩票数为:28 窗口名称:5窗口,还剩票数为:27 窗口名称:4窗口,还剩票数为:26 窗口名称:4窗口,还剩票数为:25 窗口名称:4窗口,还剩票数为:24 窗口名称:2窗口,还剩票数为:23 窗口名称:1窗口,还剩票数为:22 窗口名称:3窗口,还剩票数为:21 窗口名称:3窗口,还剩票数为:20 窗口名称:2窗口,还剩票数为:19 窗口名称:4窗口,还剩票数为:18 窗口名称:5窗口,还剩票数为:17 窗口名称:3窗口,还剩票数为:16 窗口名称:5窗口,还剩票数为:15 窗口名称:1窗口,还剩票数为:14 窗口名称:2窗口,还剩票数为:13 窗口名称:1窗口,还剩票数为:12 窗口名称:4窗口,还剩票数为:11 窗口名称:3窗口,还剩票数为:10 窗口名称:5窗口,还剩票数为:9 窗口名称:4窗口,还剩票数为:8 窗口名称:2窗口,还剩票数为:7 窗口名称:1窗口,还剩票数为:6 窗口名称:3窗口,还剩票数为:5 窗口名称:2窗口,还剩票数为:4 窗口名称:1窗口,还剩票数为:3 窗口名称:1窗口,还剩票数为:2 窗口名称:3窗口,还剩票数为:1

写完了如果写得有什么问题,希望读者能够给小编留言,也可以点击[此处扫下面二维码关注微信公众号](https://www.ycbbs.vip/?p=28 "此处扫下面二维码关注微信公众号")

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:001-一、网络编程之多线程Runnable

    链接:https://www.javajike.com/article/1224.html

« 002-二、网络编程之线程停止详解
15-Java多线程(十五)– 阻塞栈»

相关推荐

QR code