Java——Thread VS Runnable

一、线程创建的两种方式

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向文件中写入了word2
8
退出主线程main

守护线程未正常退出

原文链接:Thread与Runnable比较

你可能感兴趣的:(Java——Thread VS Runnable)