Thread与Runnable比较

来源:互联网 发布:企业微信数据备份 编辑:程序博客网 时间:2024/06/07 01:23

一、线程创建的两种方式

1.1继承Thread

class MyThread extends Thread{        @Override        public void run() {        }    }//创建线程MyThread myThread = new MyThread();//启动线程myThread.start();

1.2实现Runnable接口

class MyThread implements Runnable{        @Override        public void run() {        }    }MyThread mt = new MyThread();Thread td = new Thread(mt);td.start();

1.3比较

  1. Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
  2. Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况

二、卖火车票

2.1 Thread实现

public class TicketsThread {    public static void main(String[] args) {        MyThread myThread1 = new MyThread("一号窗口");        MyThread myThread2 = new MyThread("二号窗口");        MyThread myThread3 = new MyThread("三号窗口");        myThread1.start();        myThread2.start();        myThread3.start();    }}class MyThread extends Thread{    private int ticketsCont = 5 ;    private String name;    public MyThread(String name) {        this.name = name;    }    @Override    public void run() {        while (ticketsCont > 0) {            //有票就卖一张            ticketsCont--;            System.out.println(name+"卖了一张票,剩余票数为"+ ticketsCont);        }    }}

运行结果

二号窗口卖了一张票,剩余票数为4二号窗口卖了一张票,剩余票数为3二号窗口卖了一张票,剩余票数为2二号窗口卖了一张票,剩余票数为1二号窗口卖了一张票,剩余票数为0三号窗口卖了一张票,剩余票数为4三号窗口卖了一张票,剩余票数为3三号窗口卖了一张票,剩余票数为2三号窗口卖了一张票,剩余票数为1三号窗口卖了一张票,剩余票数为0一号窗口卖了一张票,剩余票数为4一号窗口卖了一张票,剩余票数为3一号窗口卖了一张票,剩余票数为2一号窗口卖了一张票,剩余票数为1一号窗口卖了一张票,剩余票数为0

结果并不能满意。没有对票数这个多线程共同访问的数据进行同步,使得每一个线程都有自己的一个数据源。

2.2Runnable实现

public class TicketsRunnable {    public static void main(String[] args) {        MyThread mThread = new MyThread();        Thread thread1 = new Thread(mThread,"窗口1");        Thread thread2 = new Thread(mThread,"窗口2");        Thread thread3 = new Thread(mThread,"窗口3");        thread1.start();        thread2.start();        thread3.start();    }}class MyThread implements Runnable{    private int ticketsCont = 5;    @Override    public void run() {        while (ticketsCont > 0) {            //有票就卖一张            ticketsCont--;            System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数为"+ ticketsCont);        }    }}

运行结果

窗口1卖了一张票,剩余票数为4窗口1卖了一张票,剩余票数为3窗口3卖了一张票,剩余票数为1窗口2卖了一张票,剩余票数为0窗口1卖了一张票,剩余票数为2

仍然不能满足要求,这种不符合常理的结果,没有达到预想中的4 3 2 1 0
这就是线程的交互执行导致的;举个例子:
线程1先执行卖了1张票,也即是票–1,现在票为4,但是这个线程还没没有来得及在控制台打印出剩余多少票,线程又抢到了CPU资源执行,线程2又把票–1;此时票为3,线程2输出票就为3,线程2执行完了后,线程1又再次获得CPU资源,继续把刚刚没有输出的话输出,但是此时票已经为3了,于是又输出了3。
看过多线程的小伙伴应该不难理解,这就是线程不安全,想要保证输出结果,可以使用synchronized关键字来解决

2.3 实现

public class TicketsRunnable {    public static void main(String[] args) {        MyThread mThread = new MyThread();        Thread thread1 = new Thread(mThread,"窗口1");        Thread thread2 = new Thread(mThread,"窗口2");        Thread thread3 = new Thread(mThread,"窗口3");        thread1.start();        thread2.start();        thread3.start();    }}class MyThread implements Runnable {    private int ticketsCont = 10;    @Override    public void run() {        while (ticketsCont > 0) {            //有票就卖一张            synchronized (this) {                if (ticketsCont > 0) {                    ticketsCont--;                    System.out.println(Thread.currentThread().getName()+"卖了一张票,剩余票数为"+ ticketsCont);                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }    }}

用同步锁来实现,注意在锁里也加入判断语句,不然可能会出现线程1进入了while循环,然后被抢去了线程,最后卖出了51张票。

运行结果

窗口1卖了一张票,剩余票数为9窗口3卖了一张票,剩余票数为8窗口3卖了一张票,剩余票数为7窗口2卖了一张票,剩余票数为6窗口2卖了一张票,剩余票数为5窗口2卖了一张票,剩余票数为4窗口3卖了一张票,剩余票数为3窗口3卖了一张票,剩余票数为2窗口1卖了一张票,剩余票数为1窗口3卖了一张票,剩余票数为0

三、线程的生命周期

3.1 创建

Thread thd = new Thread();

3.2 就绪

start()被调用即进入就绪状态,线程被加入到了线程队列中,等待CPU服务,具备了运行的条件,但不一定已经开始运行了

3.3 运行

获取到了CPU服务,执行run()逻辑

3.4 阻塞

受到阻塞事件的影响,由于某种原因,让出cpu资源,如sleep()方法

3.5 终止

run()执行完毕

四、守护线程

4.1 线程分类

  1. 用户线程:
    看得到的,主线程、连接网络的子线程等。
  2. 守护线程:
    运行在后台,为用户线程服务。
    特点:一旦所有用户线程结束运行,守护线程会随着JVM一起结束工作。
    应用:数据库连接池中的监测线程 & JVM虚拟机启动后的监测线程 & 垃圾回收线程

4.2注意

  1. 在start()前调用方法 setDaemon(true)
  2. 守护线程中产生的新线程也是守护线程
  3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作、逻辑运算。

4.3守护线程中进行读写操作

public class DaemonThreadDemo  {    public static void main(String[] args) {        System.out.println("进入主线程" + Thread.currentThread().getName());        DaemonThread daemonThread = new DaemonThread();        Thread thread = new Thread(daemonThread);        thread.setDaemon(true);        thread.start();        Scanner scanner  = new Scanner(System.in);        scanner.next();        System.out.println("退出主线程" + Thread.currentThread().getName());    }}class DaemonThread implements  Runnable {    public void run() {        System.out.println("程序进入了守护线程" + Thread.currentThread().getName());        try {            writeToFile();        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("程序退出了守护线程" + Thread.currentThread().getName());    }    private void writeToFile() throws Exception {        File file = new File("G:"+ File.separator+ "demo.txt");        //true 追加操作,不是覆盖操作        OutputStream os = new FileOutputStream(file,true);        int count = 0;        while (count < 999) {            os.write(("\r\nword"+count).getBytes());            System.out.println("守护线程"+ Thread.currentThread().getName()+"向文件中写入了word"+ count++);            Thread.sleep(1000);        }        os.close();    }}

运行结果

进入主线程main程序进入了守护线程Thread-0守护线程Thread-0向文件中写入了word0守护线程Thread-0向文件中写入了word1守护线程Thread-0向文件中写入了word28退出主线程main

守护线程未正常退出

原文链接:Thread与Runnable比较