• Java多线程系列---“基础篇”02之 常用的实现多线程的两种方式


    转自:https://www.cnblogs.com/skywang12345/p/3479063.html (含部分修改)

    概要

    本章,我们学习“常用的实现多线程的2种方式”:Thread 和 Runnable(比如还有Callable来实现)
    之所以说是常用的,是因为还可以通过java.util.concurrent包中的线程池来实现多线程。关于线程池的内容,我们以后会详细介绍;现在,先对Thread和Runnable进行了解。本章内容包括:

    • Thread和Runnable的简介
    • Thread和Runnable的异同点
    • Thread和Runnable的多线程的示例

    一. Thread和Runnable简介

    Runnable 是一个接口,该接口中只包含了一个run()方法。它的定义如下:

    public interface Runnable {
        public abstract void run();
    }

    Runnable的作用,实现多线程。我们可以定义一个类A实现Runnable接口;然后,通过new Thread(new A())等方式新建线程

    Thread 是一个类。Thread本身就实现了Runnable接口。它的声明如下:

    public class Thread implements Runnable {}

    Thread的作用,实现多线程。

    二. Thread和Runnable的异同点

    Thread是类,Runnable是接口。网传 ”Runnable可以实现资源共享“ 是不对的。具体分析看下面示例。

    至于用类还是用接口,取决于继承上的实际需要。(note:看了网上大牛https://blog.csdn.net/mayp1/article/details/69950530说用

    Java这门语言发展到今天,在语言层面提供的多线程机制已经比较丰富且高级,完全不用在线程层面操作。直接使用Thread和Runnable这样的“裸线程”元素比较容易出错,还需要额外关注线程数等问题。建议:

           简单的多线程程序,使用Executor。

           简单的多线程,但不想关注线程层面因素,又熟悉Java8的:使用Java8的并行流,它底层基于ForkJoinPool,还能享受函数式编程的快捷。

           复杂的多线程程序,使用一个Actor库,首推Akka。“)

    三. Thread和Runnable的多线程示例

    1. Thread的多线程示例

    下面通过示例更好的理解Thread和Runnable,借鉴网上一个比较具有说服性的例子。

    // ThreadTest.java 源码
    class MyThread extends Thread{  
        private int ticket=10;  
        public void run(){
            for(int i=0;i<20;i++){ 
                if(this.ticket>0){
                    System.out.println(this.getName()+" 卖票:ticket"+this.ticket--);//this.getName是调用得Thread中得getName方法
                }
            }
        } 
    };
    
    public class ThreadTest {  
        public static void main(String[] args) {  
            // 启动3个线程t1,t2,t3;每个线程各卖10张票!
            MyThread t1=new MyThread();
            MyThread t2=new MyThread();
            MyThread t3=new MyThread();
            t1.start();
            t2.start();
            t3.start();
        }  
    }

    其中的一种运行结果

    复制代码
    Thread-0 卖票:ticket10
    Thread-1 卖票:ticket10
    Thread-2 卖票:ticket10
    Thread-1 卖票:ticket9
    Thread-0 卖票:ticket9
    Thread-1 卖票:ticket8
    Thread-2 卖票:ticket9
    Thread-1 卖票:ticket7
    Thread-0 卖票:ticket8
    Thread-1 卖票:ticket6
    Thread-2 卖票:ticket8
    Thread-1 卖票:ticket5
    Thread-0 卖票:ticket7
    Thread-1 卖票:ticket4
    Thread-2 卖票:ticket7
    Thread-1 卖票:ticket3
    Thread-0 卖票:ticket6
    Thread-1 卖票:ticket2
    Thread-2 卖票:ticket6
    Thread-2 卖票:ticket5
    Thread-2 卖票:ticket4
    Thread-1 卖票:ticket1
    Thread-0 卖票:ticket5
    Thread-2 卖票:ticket3
    Thread-0 卖票:ticket4
    Thread-2 卖票:ticket2
    Thread-0 卖票:ticket3
    Thread-2 卖票:ticket1
    Thread-0 卖票:ticket2
    Thread-0 卖票:ticket1
    复制代码

    结果说明
    (01) MyThread继承于Thread,它是自定义个线程。每个MyThread都会卖出10张票。
    (02) 主线程main创建并启动3个MyThread子线程。每个子线程都各自卖出了10张票。(mynote:这3个myThread线程的执行结果是随机的,并发的,没有先后顺序,谁先抢到CUP谁先执行)

    2. Runnable的多线程示例

    下面,我们对上面的程序进行修改。通过Runnable实现一个接口,从而实现多线程。

    // RunnableTest.java 源码
    class MyThread implements Runnable{  
        private int ticket=10;  
        public void run(){
            for(int i=0;i<20;i++){ 
                if(this.ticket>0){ 
                    System.out.println(Thread.currentThread().getName()+" 卖票:ticket"+this.ticket--);//Runnable接口没有getName方法
                }
            }
        } 
    }; 
    
    public class RunnableTest {  
        public static void main(String[] args) {  
            MyThread mt=new MyThread();
    
            // 启动3个线程t1,t2,t3(它们共用一个Runnable对象)
            Thread t1=new Thread(mt);
            Thread t2=new Thread(mt);
            Thread t3=new Thread(mt);
            t1.start();
            t2.start();
            t3.start();
        }  
    }

    运行结果

    复制代码

    Thread-0 卖票:ticket10
    Thread-0 卖票:ticket8
    Thread-0 卖票:ticket7
    Thread-2 卖票:ticket10
    Thread-2 卖票:ticket5
    Thread-1 卖票:ticket9
    Thread-1 卖票:ticket3
    Thread-1 卖票:ticket2
    Thread-1 卖票:ticket1
    Thread-2 卖票:ticket4
    Thread-0 卖票:ticket6

    复制代码

    结果说明
    (01) 和上面“MyThread继承于Thread”不同;这里的MyThread实现了Runnable接口。
    (02) 主线程main创建并启动3个子线程,而且这3个子线程都是基于“mt这个Runnable对象”而创建的。和资源共享没有关系。发现票数居然大于10了

    问题分析:是的,编号10的票被卖了2次。为什么会这样,相信对多线程有了解的程序员都应该知道,操作符内部的实现并不是原子的。解决方法很简单,一是用synchronized这种内置锁,二是用AtomicInteger这样的concurrent包里封装好的元素。

    解决办法1:

    package com.test.a;
    
    public class MyThread implements Runnable {
        int ticket = 10; 
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                //添加synchronized。保证多个线程对是同一个ByRunnable对象的run()是互斥操作的
                synchronized (this) {
                    if (ticket > 0) {
                        System.out.println(Thread.currentThread().getName() + "卖票:" + (ticket--));
                    }
                }
            }
        }
    }
    package com.test.a;
    
    public class Test {
         public static void main(String[] args) {  
                    MyThread mt=new MyThread();
                    //主线程main创建并启动3个子线程,而且这3个子线程都是基于“thread这个Runnable对象”而创建的。
                    Thread thread1 = new Thread(mt);
                    Thread thread2 = new Thread(mt);
                    Thread thread3 = new Thread(mt);
                    //运行结果是这3个子线程一共卖出了10张票。说明它们是共享了Runnable接口的资源
                    thread1.start();
                    thread2.start();
                    thread3.start();
            }  
    
    }
    Thread-0卖票:10
    Thread-0卖票:9
    Thread-0卖票:8
    Thread-0卖票:7
    Thread-0卖票:6
    Thread-0卖票:5
    Thread-0卖票:4
    Thread-0卖票:3
    Thread-0卖票:2
    Thread-0卖票:1
    
    
    或者
    
    Thread-2卖票:10
    Thread-2卖票:9
    Thread-0卖票:8
    Thread-0卖票:7
    Thread-0卖票:6
    Thread-0卖票:5
    Thread-0卖票:4
    Thread-0卖票:3
    Thread-0卖票:2
    Thread-0卖票:1

    解决办法2:

    package com.test.a;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyThread implements Runnable {
        private AtomicInteger tickets = new AtomicInteger(10);
         
        @Override
        public void run() {
            while (true) {
                int ticketnum;
                if ((ticketnum = tickets.getAndDecrement()) > 0) {
                    System.out.println(Thread.currentThread().getName() + " is saling ticket: "
                            + ticketnum);
                } else {
                    break;
                }
            }
        }
    }
    Thread-1 is saling ticket: 10
    Thread-0 is saling ticket: 9
    Thread-2 is saling ticket: 8
    Thread-0 is saling ticket: 6
    Thread-0 is saling ticket: 4
    Thread-0 is saling ticket: 3
    Thread-0 is saling ticket: 2
    Thread-0 is saling ticket: 1
    Thread-1 is saling ticket: 7
    Thread-2 is saling ticket: 5

    总结:Thread和Runnable的区别就是前者是类,后者是接口。并且用法差不多。没有Runnable比Thread多了资源共享这一说法,因为都可以通过一些手段来实现的。

  • 相关阅读:
    Maven版本问题导致的 unable to import maven project, see logs for details. 问题
    Java时间的转换
    idea中添加类和方法注释以及codeCheck
    使用Java语言递归删除目录下面产生的临时文件
    Oracle VirtualBox添加虚拟机
    java使用JMail通过QQ邮件服务器实现自动发送邮件
    linux下&、nohup与screen的比较
    InputStream流无法重复读取的解决办法
    使用Java POI来选择提取Word文档中的表格信息
    Java实现压缩文件与解压缩文件
  • 原文地址:https://www.cnblogs.com/Hermioner/p/9839295.html
Copyright © 2020-2023  润新知