JAVA自学教程之(多线程的创建方式二 :实现Runnable接口(常用))。当一个类有父亲,但是其中的功能还希望实现线程,那么就不能采用继承Thread的方式创建线程 那么就可以通过接口的方式完成
准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行 实现Runnable接口,Runnable接口中只有一个方法run
一、创建线程的第二种方法
Runnable的出现仅仅是将线程的任务进行了对象的封装
/* * 创建线程的第二种方法 * 1.定义类实现Runnable接口 * 2.覆盖接口中的fun方法,将线程的任务代码封装到run方法中 * 3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为构造函数的参数进行传递 * 为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中,所以要在线程对象创建 * 时就要明确要运行的任务 * 4.调用线程的start方法开启线程
*/
- class Demo implements Runnable
- {
- public void run()
- {
- show();
- }
- public void show()
- {
- for(int i = 0;i<10;i++)
- {
- System.out.println(Thread.currentThread().getName()+”i = “+i);
- }
- }
- }
- public class Main
- {
- public static void main(String[] args)
- {
- //定义线程的方式
- Demo jo = new Demo();
- //Thread中有一个Thread(Runnable t)的方法
- Thread aThread = new Thread(jo);
- //如果不传递,start只会执行自己的方法
- Thread bThread = new Thread(jo);
- aThread.start();
- bThread.start();
- }
- }
class Demo implements Runnable { public void run() { show(); } public void show() { for(int i = 0;i<10;i++) { System.out.println(Thread.currentThread().getName()+"i = "+i); } } } public class Main { public static void main(String[] args) { //定义线程的方式 Demo jo = new Demo(); //Thread中有一个Thread(Runnable t)的方法 Thread aThread = new Thread(jo); //如果不传递,start只会执行自己的方法 Thread bThread = new Thread(jo); aThread.start(); bThread.start(); } }
二、实现Runnable接口和继承Thread类的区别
实现Runnable接口的好处:
1.将线程的任务同线程的子类中分离出来,进行了单独的封装,也就是将任务封装成了对象
2.避免了单继承的局限性
所以,创建线程的第二种方式较为常用。
三、代码实例:
第一种线程的创建方式
- /*
- * 需求:4个窗口进行买票,票数100张,编号1-100
- *
- */
- class Ticket extends Thread
- {
- private int num = 100;
- //private static int num = 100;
- public void run()
- {
- while(true)
- {
- if(num>0)
- {
- System.out.println(Thread.currentThread().getName()+”..sale..”+num–);
- }
- }
- }
- }
- public class Main
- {
- public static void main(String[] args)
- {
- Ticket j1 = new Ticket();
- Ticket j2 = new Ticket();
- Ticket j3 = new Ticket();
- Ticket j4 = new Ticket();
- j1.start(); j2.start();
- j3.start(); j4.start();
- }
- }
/* * 需求:4个窗口进行买票,票数100张,编号1-100 * */ class Ticket extends Thread { private int num = 100; //private static int num = 100; public void run() { while(true) { if(num>0) { System.out.println(Thread.currentThread().getName()+"..sale.."+num--); } } } } public class Main { public static void main(String[] args) { Ticket j1 = new Ticket(); Ticket j2 = new Ticket(); Ticket j3 = new Ticket(); Ticket j4 = new Ticket(); j1.start(); j2.start(); j3.start(); j4.start(); } }
PS:用第一种方式创建线程,可能出现有多个窗口卖同一号票的情况(1号窗口卖10号票,3号窗口也卖10号票)当然可以将票定义为静态的,但是仅限于是一种票,如果多种票就不适用了
第二种创建线程的方式
- /*
- * 需求:4个窗口进行买票,票数100张,编号1-100
- *
- */
- class Ticket implements Runnable
- {
- private int num = 100;
- //private static int num = 100;
- public void run()
- {
- while(true)
- {
- if(num>0)
- {
- System.out.println(Thread.currentThread().getName()+”..sale..”+num–);
- }
- }
- }
- }
- public class Main
- {
- public static void main(String[] args)
- {
- /*
- Ticket t = new Ticket();//将 卖票这一行为封装成对象
- Thread j1 = new Thread(t);
- Thread j2 = new Thread(t);
- Thread j3 = new Thread(t);
- Thread j4 = new Thread(t);
- j1.start(); j2.start();
- j3.start(); j4.start();
- */
- //两种票,站票、坐票
- Ticket zhanpiao = new Ticket();
- Ticket zuopiao = new Ticket();
- //1 2窗口卖站票,3 4 窗口卖坐票
- Thread j1 = new Thread(zhanpiao);
- Thread j2 = new Thread(zhanpiao);
- Thread j3 = new Thread(zuopiao);
- Thread j4 = new Thread(zuopiao);
- j1.start(); j2.start();
- j3.start(); j4.start();
- }
- }
/* * 需求:4个窗口进行买票,票数100张,编号1-100 * */ class Ticket implements Runnable { private int num = 100; //private static int num = 100; public void run() { while(true) { if(num>0) { System.out.println(Thread.currentThread().getName()+"..sale.."+num--); } } } } public class Main { public static void main(String[] args) { /* Ticket t = new Ticket();//将 卖票这一行为封装成对象 Thread j1 = new Thread(t); Thread j2 = new Thread(t); Thread j3 = new Thread(t); Thread j4 = new Thread(t); j1.start(); j2.start(); j3.start(); j4.start(); */ //两种票,站票、坐票 Ticket zhanpiao = new Ticket(); Ticket zuopiao = new Ticket(); //1 2窗口卖站票,3 4 窗口卖坐票 Thread j1 = new Thread(zhanpiao); Thread j2 = new Thread(zhanpiao); Thread j3 = new Thread(zuopiao); Thread j4 = new Thread(zuopiao); j1.start(); j2.start(); j3.start(); j4.start(); } }
为什么继承Thread类 和 实现RUnnable接口会出现不同的结果?
上述的卖票行为:
继承Thread类:4个窗口100票的行为,可以理解为:每个窗口都有卖100张票的任务,自然会出现1号窗口卖10号票,3号窗口也卖10号票的行为
实现Runnable接口:可以理解为4个窗口同时卖100张票的任务,那么就不会出现同时卖同号票的问题:
如图:
四、线程的安全问题
- public void run()
- {
- while(true)
- {
- if(num>0)
- {
- System.out.println(Thread.currentThread().getName()+”..sale..”+num–);
- }
- }
- }
public void run() { while(true) { if(num>0) { System.out.println(Thread.currentThread().getName()+"..sale.."+num--); } } }
这段代码是存在安全隐患的,理想状态下不会出现卖0号票的可能,但是一旦出现,进程就会出事
- class Ticket implements Runnable
- {
- private int num = 100;
- //private static int num = 100;
- public void run()//此处不能throws,因为Runnable没有声明异常
- {
- while(true)
- {
- if(num>0)
- {
- //睡10毫秒,sleep可能存在异常
- //所以只能try,catch,不能抛
- try
- {
- Thread.sleep(10);
- }
- catch (InterruptedException e)
- {
- // TODO: handle exception
- }
- System.out.println(Thread.currentThread().getName()+”..sale..”+num–);
- }
- }
- }
- }
- public class Main
- {
- public static void main(String[] args)
- {
- Ticket t = new Ticket();
- Thread j1 = new Thread(t);
- Thread j2 = new Thread(t);
- Thread j3 = new Thread(t);
- Thread j4 = new Thread(t);
- j1.start(); j2.start();
- j3.start(); j4.start();
- }
- }
class Ticket implements Runnable { private int num = 100; //private static int num = 100; public void run()//此处不能throws,因为Runnable没有声明异常 { while(true) { if(num>0) { //睡10毫秒,sleep可能存在异常 //所以只能try,catch,不能抛 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO: handle exception } System.out.println(Thread.currentThread().getName()+"..sale.."+num--); } } } } public class Main { public static void main(String[] args) { Ticket t = new Ticket(); Thread j1 = new Thread(t); Thread j2 = new Thread(t); Thread j3 = new Thread(t); Thread j4 = new Thread(t); j1.start(); j2.start(); j3.start(); j4.start(); } }
这就出现了安全隐患,所以在写多线程时,必须考虑安全问题
五、线程安全问题产生的原因:
1.多个线程在操作共享的数据(4个窗口操作共享的num)
2.操作共享数据的线程代码有多条
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与运算,就会导致线程安全问题的产生
(举个简单的例子就是,1号窗口在卖1号票的时,还没卖完,2号窗口就把1号票卖完了,这就会出现卖0号票的情况,如果不try。。直接卖一般不会出事)