精华内容
下载资源
问答
  • Qt创建线程接收惯导UDP数据
    千次阅读
    2018-01-04 16:31:09

    0 背景

    项目需求,要用Qt接收惯导数据,数据采用UDP传输,在Qt中提供了QUdpSocket类来进行UDP数据报(datagrams)的发送和接收。这里我们还要了解一个名词Socket,也就是常说的“套接字”。 Socket简单地说,就是一个IP地址加一个port端口。因为我们要传输数据,就要知道往哪个机子上传送,而IP地址确定了一台主机,但是这台机子上可能运行着各种各样的网络程序,我们要往哪个程序中发送呢?这时就要使用一个端口来指定UDP程序。所以说,Socket指明了数据报传输的路径。

    为了使惯导数据的接收不影响主页面的响应,创建新线程来不停地读取数据。下面创建一个UDP的接收端程序。新建一个mainwindow工程,在pro文件中添加QT += network,然后执行以下qmake,确保没有问题。新建gnss类,继承QThread,包括gnss.h和gnss.cpp文件。话不多说,直接上代码!

    1 gnss.h

    #ifndef GNSS_H
    #define GNSS_H
    #include <QThread>
    #include <QtCore>
    #include <QObject>
    #include <QMutex>
    #include <iostream>
    #include <QtNetwork>
    
    class GNSS: public QThread
    {
    public:    
        Q_OBJECT
    public:
        GNSS();
        void stop();
    private slots:
        void run();
        void processPendingDatagram();
    private:
        bool stopped;
        QMutex m_lock;
        QUdpSocket *receiver;
        QFile f();
    };
    
    #endif // GNSS_H

    2 gnss.cpp

    #include "gnss.h"
    #include <QTimer>
    #include <QMutexLocker>
    #include "mainwindow.h"
    
    GNSS::GNSS()
    {
    
        stopped = false;//标记可以运行
        //创建一个QUdpSocket类对象,该类提供了Udp的许多相关操作
        receiver = new QUdpSocket(this);
        int port =3000;//设置UDP的端口号参数,指定在此端口上监听数据
        //此处的bind是个重载函数,连接本机的port端口,采用ShareAddress模式(即允许其它的服务连接到相同的地址和端口,特别是
        //用在多客户端监听同一个服务器端口等时特别有效),和ReuseAddressHint模式(重新连接服务器)
        int receive = receiver->bind(QHostAddress("195.0.0.230"),port);
        qDebug() << "receive: " <<receive << endl;
        if(receive == 0)
        {
            qDebug() << "UDP Connected Succeed ! " << endl;
        }
        else
        {
            qDebug() << "UDP Connected Faild ! " << endl;
        }
    }
    void GNSS::stop()
    {
        QMutexLocker locker(&m_lock);
        stopped = true;
        receiver->close();
        delete receiver;
    
    }
    
    void GNSS::run()
    {
        //获得系统时间并输出
        QString min = QDateTime::currentDateTime().toString("yyyyMMddhhmm");
        //打开文本 以时间命名文件名字
        QString fileName = "C:\\SensorsData\\BASLER\\code\\build-opencv-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug\\GNSS_" + min + ".txt";//假设指定文件夹路径为D盘根目录
        QFile f(fileName);
        LONGLONG time_now=0;
        QString s = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
        QString a = QDateTime::currentDateTime().toString("yyyyMMddhhmm");
        QString b = QDateTime::currentDateTime().toString("mmss");
    
        while(!stopped)
        {
            while(receiver->hasPendingDatagrams())  //拥有等待的数据报
            {
               //qDebug() << "receive succeed ! " << endl;
                QByteArray datagram; //拥于存放接收的数据报
               //pendingDatagramSize为返回第一个在等待读取报文的size,
               //resize函数是把datagram的size归一化到参数size的大小一样
               datagram.resize(receiver->pendingDatagramSize());
               //接收数据报,将其存放到datagram中
               //将读取到的不大于datagram.size()大小数据输入到datagram.data()中,
               //datagram.data()返回的是一个字节数组中存储数据位置的指针
               receiver->readDatagram(datagram.data(),datagram.size());
               //将数据报内容显示出来
               QString HexData = datagram.toHex();
               //判断数据是否完整
               if(HexData.length() == 144)
               {
                   //解析Hex数据
                   QString Status = HexData.mid(42,2);
                   qDebug() << "Status :" << Status  << endl;
                   QString Latitude = HexData.mid(46,16);
                   qDebug() << "Latitude :" << Latitude  << endl;
                   QString Longitude = HexData.mid(62,16);
                   qDebug() << "Longitude :" << Longitude  << endl;
                   QString Altitude = HexData.mid(78,8);
                   qDebug() << "Altitude :" << Altitude  << endl;
                   QString North_Velocity = HexData.mid(86,6);
                   qDebug() << "North_Velocity :" << North_Velocity  << endl;
                   QString East_Velocity = HexData.mid(92,6);
                   qDebug() << "East_Velocity :" << East_Velocity  << endl;
                   QString Down_Velocity = HexData.mid(98,6);
                   qDebug() << "Down_Velocity :" << Down_Velocity  << endl;
                   QString Heading = HexData.mid(104,6);
                   qDebug() << "Heading :" << Heading  << endl;
                   QString Pitch = HexData.mid(110,6);
                   qDebug() << "Pitch :" << Pitch  << endl;
                   QString Roll = HexData.mid(116,6);
                   qDebug() << "Roll :" << Roll  << endl;
    
                   //获得系统时间并输出
                   s = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
                   a = QDateTime::currentDateTime().toString("yyyyMMddhhmm");
                   b = QDateTime::currentDateTime().toString("mmss");
                   qDebug() << "min : " <<min<< endl;
                   time_now = b.toLongLong();
                   qDebug() << "time_now: " <<time_now<< endl;
                   //每10分钟保存一次
                   if(time_now % 1000 == 0)
                   {
                       qDebug() << "write again ! " <<time_now<< endl;
                       f.fileName() = "C:\\SensorsData\\BASLER\\code\\build-opencv-Desktop_Qt_5_8_0_MSVC2015_64bit-Debug\\GNSS_" + a + ".txt";
                   }
    
                   qDebug() << "time" << s;
    
                   if(!f.open( QIODevice::Append))
                   {
                       cout << "Open failed." << endl;
                   }
                   QTextStream txtOutput(&f);
                   txtOutput << s <<"$$"<< HexData<< "&&" << endl;
    
                   f.close();
    
               }
               else
               {
                   qDebug() << "The data is not complete !" << endl;
               }
            }
        }
    
    
    }
    /***********GNSS数据处理***********/
    void GNSS::processPendingDatagram()
    {
    
    }
    

    3 mainwindow.cpp

    在ui界面新建两个button,分别是开始接收和停止接收,分别命名为StartGNSSBtn和StopGNSSBtn,然后在mainwindow.cpp中添加对应的槽函数

    /***********开始GNSS数据接收***********/
    void MainWindow::on_StartGNSSBtn_clicked()
    {
        if(!gnss.isRunning())
        {
            gnss.start();
            ui->StartGNSSBtn->setEnabled(false);
            ui->StopGNSSBtn->setEnabled(true);
        }
        else
        {
            qDebug() << "GNSS receive has started !!!" << endl;
    
        }
    
    }
    
    /***********停止GNSS数据接收***********/
    void MainWindow::on_StopGNSSBtn_clicked()
    {
        if(gnss.isRunning())
        {
            gnss.stop();
        }
        ui->StartGNSSBtn->setEnabled(true);
        ui->StopGNSSBtn->setEnabled(false);
    }

    同时要记得在mainwindow.h中添加相关的变量和函数,添加头文件#include "gnss.h"

    private:
        //GNSS相关对象
        GNSS gnss;
        QPushButton *StartGNSSBtn;
        QPushButton *StopGNSSBtn;
    private slots:
        //GNSS相关的槽函数
        void on_StartGNSSBtn_clicked();
        void on_StopGNSSBtn_clicked();

    至此完成在新的线程里接收GNSS数据并初步的分割保存工作,下一步进行解析及显示

    更多相关内容
  • 学会使用函数创建线程 学会使用类创建线程线程:必学函数讲解 经过总结,Python创建线程主要有如下两种方法: 函数 类 接下来,我们就来揭开多线程的神秘面纱。 . 学会使用函数创建线程 在Python3...
  • C# UDP多线程发送接收

    2018-01-23 15:05:45
    通过多线程的方式实现了UDP收发数据 可以快速学习UDP通讯原理应进行实际应用
  • java创建线程的四种方式及其区别

    千次阅读 2019-04-26 18:35:25
    java创建线程有如下四种方式: 继承Thread类创建线程 实现Runnable接口创建线程 使用Callable和Future创建线程 使用线程池创建(使用java.util.concurrent.Executor接口) 下面分别介绍这四种创建线程的方式...

     java创建多线程有如下四种方式:

    1. 继承Thread类创建线程
    2. 实现Runnable接口创建线程
    3. 使用Callable和Future创建线程
    4. 使用线程池创建(使用java.util.concurrent.Executor接口)

    下面分别介绍这四种创建多线程的方式

    1. ----------------------继承Thread类创建线程--------------------------------

    通过继承Thread类来创建并启动多线程的一般步骤如下:

    1】定义Tread类的子类MyThread,并重写run()方法.run()方法的方法体(线程执行体)就是线程要执行的任务。

    2】创建MyThread类的实例

    3】调用子类实例的start()方法来启动线程

    示例代码:

    public class Mythread extends Thread {
    	
    	private int i;
    	public void run(){//run()是线程类的核心方法
    		for(int i=0;i<10;i++){
    			System.out.println(this.getName()+":"+i);
    		}
    	}
    	public static void main(String[] args) {
    		Mythread t1=new Mythread();
    		Mythread t2=new Mythread();
    		Mythread t3=new Mythread();
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    2.--------------------------------实现 Runnable接口创建线程---------------------------------

    通过实现Runnable接口创建并启动线程的一般步骤如下:

    1】定义Runnable接口的实现类,必须重写run()方法,这个run()方法和Thread中的run()方法一样,是线程的执行体

    2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

    3】调用start()方法

    实例代码:

    public class MyThread implements Runnable{
    
    	@Override
    	public void run() {
    		for(int i=0;i<10;i++){
    			System.out.println(Thread.currentThread().getName()+" : "+i);
    		}
    		
    	}
    	public static void main(String[] args) {
    		MyThread myThread=new MyThread();
    		Thread thread1=new Thread(myThread,"线程1");
    		Thread thread2=new Thread(myThread,"线程2");
    		Thread thread3=new Thread(myThread,"线程3");
    		thread1.start();
    		thread2.start();
    		thread3.start();
    	}
    }

    继承Tread和实现Runnable接口的区别:

    a.实现Runnable接口避免单继承局限

    b.当子类实现Runnable接口,此时子类和Thread的代理模式(子类负责真实业务的操作(调用start()方法),thread负责资源调度与线程创建辅助真实业务。

    3--------------------------覆写Callable接口实现多线程------------------------------------

    和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:

    1>call方法可以有返回值

    2>call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

    >boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

    >V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

    >V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

    >boolean isDone():若Callable任务完成,返回True

    >boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

    介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

    1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

    2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

    3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

    4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
     

    实例代码:

    public class MyThread implements Callable<String>{//Callable是一个泛型接口
    
    	@Override
    	public String call() throws Exception {//返回的类型就是传递过来的V类型
    		for(int i=0;i<10;i++){
    			System.out.println(Thread.currentThread().getName()+" : "+i);
    		}
    		
    		return "Hello Tom";
    	}
    	public static void main(String[] args) throws Exception {
    		MyThread myThread=new MyThread();
    		FutureTask<String> futureTask=new FutureTask<>(myThread);
    		Thread t1=new Thread(futureTask,"线程1");
    		Thread t2=new Thread(futureTask,"线程2");
    		Thread t3=new Thread(futureTask,"线程3");
    		t1.start();
    		t2.start();
    		t3.start();
    		System.out.println(futureTask.get());
    		
    	}
    }

    FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。 FutureTask可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消FutureTask的执行等。

     

    思考?

    同时实现Runnable和Callable接口会怎么样?

    public class MyThread implements Callable<String>,Runnable{//Callable是一个泛型接口
    
    	@Override
    	public String call() throws Exception {//返回的类型就是传递过来的V类型
    		for(int i=0;i<10;i++){
    			System.out.println(Thread.currentThread().getName()+" : "+i);
    		}
    		
    		return "Hello Tom";
    	}
    	public static void main(String[] args) throws Exception {
    		MyThread myThread=new MyThread();
    		FutureTask<String> futureTask=new FutureTask<>(myThread);
    		Thread t0=new Thread(myThread,"线程0");
    		Thread t1=new Thread(futureTask,"线程1");
    		Thread t2=new Thread(futureTask,"线程2");
    		Thread t3=new Thread(futureTask,"线程3");
    		t0.start();
    		t1.start();
    		t2.start();
    		t3.start();
    		System.out.println(futureTask.get());
    		
    	}
    	@Override
    	public void run() {
    		for(int i=0;i<10;i++){
    			System.out.println(Thread.currentThread().getName()+" : "+i);
    		}
    		
    	}
    }

    使用Runnable接口实现类做target创建的的线程和FutureTask的线程交叉着运行,但是由于FutureTask只执行一次Callable任务,在一次运行结束后就终止了程序。

     

    4.-------------------使用线程池例如用Executor框架----------------------------------------------

    1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。

     

        Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

     

        Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

     

        ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

     

     

        Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。   

        public static ExecutorService newFixedThreadPool(int nThreads)

        创建固定数目线程的线程池。

        public static ExecutorService newCachedThreadPool()

        创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线   程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

        public static ExecutorService newSingleThreadExecutor()

        创建一个单线程化的Executor。

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

        创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

     

        这四种方法都是用的Executors中的ThreadFactory建立的线程,下面就以上四个方法做个比较:   
     

    newCachedThreadPool()        -缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中
    -缓存型池子通常用于执行一些生存期很短的异步型任务
     因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是Executor的首选。
    -能reuse的线程,必须是timeout IDLE内的池中线程,缺省     timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
      注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
     
    newFixedThreadPool(int)  -newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
    -其独特之处:任意时间点,最多只能有固定数目的活动线程存在
    ,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
    -和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
    -从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:
    fixed池线程数固定,并且是0秒IDLE(无IDLE)    
    cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE  
    newScheduledThreadPool(int)     
    -调度型线程池
    -这个池子里的线程可以按schedule依次delay执行,或周期执行
    SingleThreadExecutor()
    -单例线程,任意时间池中只能有一个线程https://blog.csdn.net/baihualindesu/article/details/89555758
    -用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)

        一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。(该段话摘自《Thinking in Java》第四版)

    常用的七种线程池:https://blog.csdn.net/baihualindesu/article/details/89555758

     

    下面是java中作为线程池Executor底层实现类的ThredPoolExecutor的构造函数:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
           ...
        }
    

    其中各个参数含义如下:
    corePoolSize- 池中所保存的线程数,包括空闲线程。需要注意的是在初创建线程池时线程不会立即启动,直到有任务提交才开始启动线程并逐渐时线程数目达到corePoolSize。若想一开始就创建所有核心线程需调用prestartAllCoreThreads方法。
    maximumPoolSize-池中允许的最大线程数。需要注意的是当核心线程满且阻塞队列也满时才会判断当前线程数是否小于最大线程数,并决定是否创建新线程。
    keepAliveTime - 当线程数大于核心时,多于的空闲线程最多存活时间
    unit - keepAliveTime 参数的时间单位。
    workQueue - 当线程数目超过核心线程数时用于保存任务的队列。主要有3种类型的BlockingQueue可供选择:无界队列,有界队列和同步移交。将在下文中详细阐述。从参数中可以看到,此队列仅保存实现Runnable接口的任务。
    threadFactory - 执行程序创建新线程时使用的工厂。
    handler - 阻塞队列已满且线程数达到最大值时所采取的饱和策略。java默认提供了4种饱和策略的实现方式:中止、抛弃、抛弃最旧的、调用者运行。

    Executor执行Runnable任务

        通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上
     

    import java.util.concurrent.ExecutorService;   
    import java.util.concurrent.Executors;   
      
    public class TestCachedThreadPool{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
            for (int i = 0; i < 3; i++){  //执行三个任务,那么线程池中最多创建三个线程
                executorService.execute(new TestRunnable());   
                System.out.println("************* a" + i + " *************");   
            }   
            executorService.shutdown();   
        }   
    }   
      
    class TestRunnable implements Runnable{   
        public void run(){   
        	for(int i=0;i<5;i++){
        		 System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
        	}
        }   
    }  

    上面是某次执行后的结果,可以看出,当有任务需要执行时,首先选择线程池中一个执行完任务的线程复用执行,如果线程池中没有可利用的线程,就创建一个新的线程。

    Executor执行Callable任务

        在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。

     

        Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

     

        当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

    示例代码如下:

    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.FutureTask;
    
    public class CachedThreadPool{   
        public static void main(String[] args) throws Exception{   
            ExecutorService executorService = Executors.newCachedThreadPool();   
    //      ExecutorService executorService = Executors.newFixedThreadPool(5);  
    //      ExecutorService executorService = Executors.newSingleThreadExecutor();  
            for (int i = 0; i < 2; i++){  
            	TestCallable callable=new TestCallable();
            	FutureTask<String>futureTask=new FutureTask<>(callable);
                executorService.execute(futureTask);   
                System.out.println(futureTask.get());
                System.out.println("************* a" + i + " *************");   
            }   
            executorService.shutdown();   
        }   
    }   
      
    class TestCallable implements Callable{
    
    	@Override
    	public Object call() throws Exception {
    		
    		for(int i=0;i<5;i++){
    			System.out.println(Thread.currentThread().getName()+" :"+i);
    		}
    		return "Callable is Used!";
    	}   
        
    }  
    

    运行结果:

    ************* a0 *************
    pool-1-thread-1 :0
    pool-1-thread-1 :1
    pool-1-thread-1 :2
    pool-1-thread-1 :3
    pool-1-thread-1 :4
    Callable is Used!
    ************* a1 *************
    pool-1-thread-1 :0
    pool-1-thread-1 :1
    pool-1-thread-1 :2
    pool-1-thread-1 :3
    pool-1-thread-1 :4
    Callable is Used!

    当通过实现Callable接口创建线程时,调用了Future接口的实现类FutureTask,该类还实现了Runnable接口,所以可以让实例化对象作为Thread对象的Target。并且每次只执行一个线程的任务,当执行完线程的主任务后,返回值。

     

    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
     
    public class RunNewFixedThreadPool {
        public static void main(String[] args) {
        	RunNewFixedThreadPool run = new RunNewFixedThreadPool();
            ExecutorService executorService = Executors.newFixedThreadPool(6);
            for (int i = 0; i < 5; i++) {//创建五个线程
                executorService.execute(run.new MyRunnable(" "+(i+1)));
            }
            for (int i = 5; i < 10; i++) {//再创建五个线程,如果当前线程池中有线程可以使用,则复用,否则创建新的线程,但是线程池中最多只能有6个线程
                executorService.execute(run.new MyRunnable(" "+(i+1)));
            }
        }
        
        public class MyRunnable implements Runnable{
            private String username;
            
            public MyRunnable(String username) {
                this.username = username;
            }
     
     
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" username="+username+" begin "+System.currentTimeMillis());
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+" username="+username+" end   "+System.currentTimeMillis());
            }
            
        }
    }
    

     

    这里面每次创建的线程数可以设定,上面设为了5,所以一次性开启了五个线程。如果设为3,那么一次最多开三个进程,并且只有当一些进程关闭时,才会开启相同数目的进程。

     

    --------------------------------------四种创建线程方法对比--------------------------------------

    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,并且如果使用FutureTask类的话,只执行一次Callable任务。因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

    1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

    2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    3.继承Thread类只需要this就能获取当前线程。不需要使用Thread.currentThread()方法

    4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

    5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池的方式创建多个线程。

    6.实现接口的创建线程的方式必须实现方法(run()  call())。


     

    展开全文
  • 在主线程开启子线程初始化,读写串口数据,有数据就读,提取有效数据,并用信号将数据传送给主线程,创建界面对象,主线程通过开关按钮槽函数控制串口的打开关闭,图形的显示以及暂停,实时刷新波形。串口读取数据...
  • Linux——线程创建

    千次阅读 2020-12-04 22:30:24
    线程是进程内部的一条执行序列,一个进程至少有一条线程,称之为主线程(main方法代表的执行序列),可以通过线程创建其他线程(给线程制定一个它要执行的函数),将创建线程称之为函数线程线程的实现方式 ...

    线程的概念与实现方式

    线程是进程内部的一条执行序列或执行路径,一个进程可以包含多条线程。

    • 从资源分配的角度来看,进程是操作系统进行资源分配的基本单位。
    • 从资源调度的角度来看,线程是资源调度的最小单位,是程序执行的最小单位

    执行序列就是一组有序指令的集合——函数。

    线程是进程内部的一条执行序列,一个进程至少有一条线程,称之为主线程(main方法代表的执行序列),可以通过线程库创建其他线程(给线程制定一个它要执行的函数),将创建的线程称之为函数线程。
    在这里插入图片描述

    线程的实现方式

    1. 内核级线程(由内核直接创建和管理线程,虽然创建开销较大,但是可以利用多处理器的资源)
    2. 用户级线程(由线程库创建和管理多个线程,线程的实现都是在用户态,内核无法感知,创建开销较小,无法使用多处理器的资源)
    3. 混合级线程(结合以上两种方式实现,可以利用多处理器的资源,从而在用户空间中创建更多的线程,从而映射到内核空间的线程中,多对多,N:M(N>>M))

    在这里插入图片描述

    Linux系统实现多线程的方式

    Linux 实现线程的机制非常独特。从内核的角度来说,它并没有线程这个概念。 Linux 把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯 一隶属于自己的task_struct,所以在内核中,它看起来就像是一个普通的进程(只是线程和 其他一些进程共享某些资源,如地址空间)

    线程和进程的区别

    1. 进程是资源分配最小单位,线程是程序执行的最小单位;
    2. 线程间的切换效率相比进程间的切换要高
    3. 进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
    4. 创建一个线程比进程开销小;
    5. 线程占用的资源要⽐进程少很多。
    6. 线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
    7. 多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
    8. 进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

    线程库的使用

    1.创建线程

    #include<phread.h>
    
    int pthread_create(pthread_t *id , pthread_attr_t *attr, void(*fun)(void*), void *arg);
    
    • id :传递一个pthread_t类型的变量的地址,创建成功后,用来获取新创建的线程的TID
    • attr:指定线程的属性 默认使用NULL
    • fun:线程函数的地址
    • arg:传递给线程函数的参数
    • 返回值,成功返回0,失败返回错误码

    多线程代码示例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    //声明一个线程函数
    void *fun(void *);
    
    int main()
    {
    	printf("main start\n");
    
    	pthread_t id;
    	//创建函数线程,并且指定函数线程要执行的函数
    	int res = pthread_create(&id,NULL,fun,NULL);
    	assert(res == 0);
    
    	//之后并发运行
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    
    	printf("main over\n");
    	exit(0);
    }
    
    //定义线程函数
    void* fun(void *arg)
    {
    	printf("fun start\n");
    
    	int i = 0;
    	for(; i < 3;i++)
    	{
    		printf("fun running\n");
    		sleep(1);
    	}
    
    	printf("fun over\n");
    }
    

    gcc编译代码时报`undifined reference to xxxxx错误,都是因为程序中调用了一些方法,但是没有连接该方法所在的文件,例如下面的情况:

    在这里插入图片描述
    连接库文件编译成功并执行,这一点在帮助手册中也有提示:Compile and link with -pthread
    在这里插入图片描述
    比较两次运行的结果发现前三条执行语句时一样的

    在这里插入图片描述
    结论

    1. 创建线程并执行线程函数,和调用函数是完全不同的概念。
    2. 主线程和函数线程是并发执行的。
    3. 线程提前于主线程结束时,不会影响主线程的运行
    4. 主线程提前于线程结束时,整个进程都会结束,其他线程也会结束
    5. 创建函数线程后,哪个线程先被执行是有操作系统的调度算法和机器环境决定。

    在这里插入图片描述
    函数线程在主线程结束后也随之退出,原因:主线程结束时使用的是exit方法,这个方法结束的是进程。
    然而修改代码为:pthread_exit(NULL);此时主线程结束,函数线程会继续执行直至完成。即便如此,我们还是不推荐大家手动结束主线程,我们更喜欢让主线程等待一会。

    给线程函数传参
    ①值传递
    将变量的值直接转成void*类型进行传递

    因为线程函数接受的是一个void*类型的指针,只要是指针,32位系统上都是4个字节,值传递就只能传递小于或等于4字节的值。
    代码示例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    void *fun(void *);
    
    int main()
    {
    	printf("main start\n");
    
    	int a = 10;
    	
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,(void*)a);
    	assert(res == 0);
    
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    
    	printf("main over\n");
    	exit(0);
    }
    
    
    void* fun(void *arg)
    {
    	int b = (int)arg;
    	printf("b == %d\n",b);
    }
    

    在这里插入图片描述
    ②地址传递
    将变量(所有类型)的地址强转成void*类型进行传递,就和在普通函数调用传递变量的地址相似。
    主线程和函数线程通过这个地址就可以共享地址所指向的空间。
    一个进程内的所有线程是共享这个进程的地址空间。

    多线程下进程的4G虚拟地址空间

    在这里插入图片描述
    一个进程内的所有线程对于全局数据,静态数据,堆区空间都是共享的。
    线程之间传递数据很简单,但是随之带来的问题就是线程并发运行时无法保证线程安全。
    代码示例

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    int gdata = 10; //.data
    
    void *fun(void *);
    
    int main()
    {
    	int *ptr = (int *)malloc(4);//.heap
        *ptr = 10;
    	
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,(void*)ptr);
    	assert(res == 0);
    
        sleep(2);//等待两秒,保证函数线程已经讲数据修改
    
    	printf("main : gdata == %d\n",gdata);
        printf("main : *ptr = %d\n",*ptr);
    
    	exit(0);
    }
    
    
    void *fun(void *arg)
    {
    	int *p = (int*)arg;
    
        gdata = 20000;
        *p = 20;
    
    	printf("fun over\n");
    }
    

    在这里插入图片描述

    线程库中的其他方法

    结束某个线程

    int pthread_exit(void *result);
    

    子线程调用时需要传入一个指针,这个指针的内容会被返回给主线程,不需要返回时,就写NULL;
    哪个线程调用,哪个线程结束。

    等待指定的子线程结束

    • 等待thread()指定的线程退出,线程未退出时,该方法阻塞
    • result接收thread线程退出时,指定退出信息
    int pthread_join(pthread_t id,void **result)//调用这个方法的线程会阻塞,直到等待线程结束
    

    代码演示:

    #include<stdio.h>
    #include<stdlib.h>
    #include<assert.h>
    #include<string.h>
    #include<unistd.h>
    
    #include<pthread.h>
    
    int main()
    {
    	printf("main start\n");
    
    	pthread_t id;
    	int res = pthread_create(&id,NULL,fun,NULL);
    	assert(res == 0);
    
    	//之后并发运行
    	int i = 0;	
    	for(; i < 5; i++)
    	{
    		printf("main running\n");
    		sleep(1);
    	}
    	
    	char *s = NULL;
    	pthread_join(id,(void **)&s);
    	printf("join : s = %s\n",s);
    	
    	exit(0);
    }
    
    //定义线程函数
    void* fun(void *arg)
    {
    	printf("fun start\n");
    
    	int i = 0;
    	for(; i < 10;i++)
    	{
    		printf("fun running\n");
    		sleep(1);
    	}
    
    	printf("fun over\n");
    
    	pthread_exit("fun over");//将该字符常量返回给主线程
    }
    

    此时,主线程完成五次输出,就会等待子线程结束,阻塞等待,子线程结束后,最后,主线程打印join:s = fun over

    展开全文
  • 线程创建常用的四种方式

    千次阅读 2021-03-24 16:28:17
    java中创建线程的四种方法以及区别 Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示: 1)继承Thread类创建线程 2)实现Runnable接口创建线程 ...

    java中创建线程的四种方法以及区别
    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:

    1)继承Thread类创建线程

    2)实现Runnable接口创建线程

    3)使用Callable和Future创建线程

    4)使用线程池例如用Executor框架

    下面让我们分别来看看这四种创建线程的方法。

     

    1.继承Thread类创建线程

     

    通过继承Thread类来创建并启动多线程的一般步骤如下

    1】d定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。

    2】创建Thread子类的实例,也就是创建了线程对象

    3】启动线程,即调用线程的start()方法

    代码实例

    public class MyThread extends Thread{//继承Thread类
    
      public void run(){
      //重写run方法
    
      }
    
    }
    
    public class Main {
      public static void main(String[] args){
        new MyThread().start();//创建并启动线程
    
      }
    
    }

     

    2.实现Runnable接口创建线程

    通过实现Runnable接口创建并启动线程一般步骤如下:

    1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体

    2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象

    3】第三部依然是通过调用线程对象的start()方法来启动线程

    代码实例:
     

    public class MyThread2 implements Runnable {//实现Runnable接口
    
      public void run(){
      //重写run方法
    
      }
    
    }
    
    public class Main {
      public static void main(String[] args){
        //创建并启动线程
    
        MyThread2 myThread=new MyThread2();
    
        Thread thread=new Thread(myThread);
    
        thread().start();
    
        //或者    new Thread(new MyThread2()).start();
    
      }
    
    }

     

     

    3.使用Callable和Future创建线程

    和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

    》call()方法可以有返回值

    》call()方法可以声明抛出异常

    Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

    >boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

    >V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

    >V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

    >boolean isDone():若Callable任务完成,返回True

    >boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

    介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

    1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

    2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

    3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

    4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    代码实例:
     

    public class Main {
      public static void main(String[] args){
       MyThread3 th=new MyThread3();
    
       //使用Lambda表达式创建Callable对象
    
         //使用FutureTask类来包装Callable对象
    
       FutureTask<Integer> future=new FutureTask<Integer>(
    
        (Callable<Integer>)()->{
          return 5;
    
        }
    
        );
    
       new Thread(task,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
    
        try{
        System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
    
        }catch(Exception e){
        ex.printStackTrace();
    
       }
    
      }
    
    }

     

    4.使用线程池例如用Executor框架

    1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象用Executor在构造器中。

     

        Executor框架包括:线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。

     

        Executor接口中之定义了一个方法execute(Runnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。

     

        ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。

     

     

        Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。   

        public static ExecutorService newFixedThreadPool(int nThreads)

        创建固定数目线程的线程池。

        public static ExecutorService newCachedThreadPool()

        创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线   程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

        public static ExecutorService newSingleThreadExecutor()

        创建一个单线程化的Executor。

        public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)

        创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

     

        这四种方法都是用的Executors中的ThreadFactory建立的线程,下面就以上四个方法做个比较

     

     


    newCachedThreadPool()                                                                                                                                             
    -缓存型池子,先查看池中有没有以前建立的线程,如果有,就 reuse.如果没有,就建一个新的线程加入池中
    -缓存型池子通常用于执行一些生存期很短的异步型任务
     因此在一些面向连接的daemon型SERVER中用得不多。但对于生存期短的异步任务,它是Executor的首选。
    -能reuse的线程,必须是timeout IDLE内的池中线程,缺省     timeout是60s,超过这个IDLE时长,线程实例将被终止及移出池。
      注意,放入CachedThreadPool的线程不必担心其结束,超过TIMEOUT不活动,其会自动被终止。
     


    newFixedThreadPool(int)                                                          
    -newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
    -其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
    -和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
    -从方法的源代码看,cache池和fixed 池调用的是同一个底层 池,只不过参数不同:
    fixed池线程数固定,并且是0秒IDLE(无IDLE)    
    cache池线程数支持0-Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE  
     
    newScheduledThreadPool(int)    
    -调度型线程池
    -这个池子里的线程可以按schedule依次delay执行,或周期执行
     
    SingleThreadExecutor()    
    -单例线程,任意时间池中只能有一个线程
    -用的是和cache池和fixed池相同的底层池,但线程数目是1-1,0秒IDLE(无IDLE)
     
     

     

        一般来说,CachedTheadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程,因此它是合理的Executor的首选,只有当这种方式会引发问题时(比如需要大量长时间面向连接的线程时),才需要考虑用FixedThreadPool。(该段话摘自《Thinking in Java》第四版)

     

                             

    Executor执行Runnable任务
        通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上

     

     

     
    import java.util.concurrent.ExecutorService;   
    import java.util.concurrent.Executors;   
      
    public class TestCachedThreadPool{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
    //      ExecutorService executorService = Executors.newFixedThreadPool(5);  
    //      ExecutorService executorService = Executors.newSingleThreadExecutor();  
            for (int i = 0; i < 5; i++){   
                executorService.execute(new TestRunnable());   
                System.out.println("************* a" + i + " *************");   
            }   
            executorService.shutdown();   
        }   
    }   
      
    class TestRunnable implements Runnable{   
        public void run(){   
            System.out.println(Thread.currentThread().getName() + "线程被调用了。");   
        }   
    }  
    


     

     

     

     

       某次执行后的结果如下:

     

     

       从结果中可以看出,pool-1-thread-1和pool-1-thread-2均被调用了两次,这是随机的,execute会首先在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程,它便会创建一个新的线程来执行任务。

     

     

    Executor执行Callable任务
        在Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,但是Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。

     

        Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。

     

        当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。

     

        下面给出一个Executor执行Callable任务的示例代码:

     

    
    import java.util.ArrayList;   
    import java.util.List;   
    import java.util.concurrent.*;   
      
    public class CallableDemo{   
        public static void main(String[] args){   
            ExecutorService executorService = Executors.newCachedThreadPool();   
            List<Future<String>> resultList = new ArrayList<Future<String>>();   
      
            //创建10个任务并执行   
            for (int i = 0; i < 10; i++){   
                //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中   
                Future<String> future = executorService.submit(new TaskWithResult(i));   
                //将任务执行结果存储到List中   
                resultList.add(future);   
            }   
      
            //遍历任务的结果   
            for (Future<String> fs : resultList){   
                    try{   
                        while(!fs.isDone);//Future返回如果没有完成,则一直循环等待,直到Future返回完成  
                        System.out.println(fs.get());     //打印各个线程(任务)执行的结果   
                    }catch(InterruptedException e){   
                        e.printStackTrace();   
                    }catch(ExecutionException e){   
                        e.printStackTrace();   
                    }finally{   
                        //启动一次顺序关闭,执行以前提交的任务,但不接受新任务  
                        executorService.shutdown();   
                    }   
            }   
        }   
    }   
      
      
    class TaskWithResult implements Callable<String>{   
        private int id;   
      
        public TaskWithResult(int id){   
            this.id = id;   
        }   
      
        /**  
         * 任务的具体过程,一旦任务传给ExecutorService的submit方法, 
         * 则该方法自动在一个线程上执行 
         */   
        public String call() throws Exception {  
            System.out.println("call()方法被自动调用!!!    " + Thread.currentThread().getName());   
            //该返回结果将被Future的get方法得到  
            return "call()方法被自动调用,任务返回的结果是:" + id + "    " + Thread.currentThread().getName();   
        }   
    }  


     

     

     

        某次执行结果如下:

       

     

        从结果中可以同样可以看出,submit也是首先选择空闲线程来执行任务,如果没有,才会创建新的线程来执行任务。另外,需要注意:如果Future的返回尚未完成,则get()方法会阻塞等待,直到Future完成返回,可以通过调用isDone()方法判断Future是否完成了返回。

     

     

     

    自定义线程池
        自定义线程池,可以用ThreadPoolExecutor类创建,它有多个构造方法来创建线程池,用该类很容易实现自定义的线程池,这里先贴上示例程序:

    import java.util.concurrent.ArrayBlockingQueue;   
    import java.util.concurrent.BlockingQueue;   
    import java.util.concurrent.ThreadPoolExecutor;   
    import java.util.concurrent.TimeUnit;   
      
    public class ThreadPoolTest{   
        public static void main(String[] args){   
            //创建等待队列   
            BlockingQueue<Runnable> bqueue = new ArrayBlockingQueue<Runnable>(20);   
            //创建线程池,池中保存的线程数为3,允许的最大线程数为5  
            ThreadPoolExecutor pool = new ThreadPoolExecutor(3,5,50,TimeUnit.MILLISECONDS,bqueue);   
            //创建七个任务   
            Runnable t1 = new MyThread();   
            Runnable t2 = new MyThread();   
            Runnable t3 = new MyThread();   
            Runnable t4 = new MyThread();   
            Runnable t5 = new MyThread();   
            Runnable t6 = new MyThread();   
            Runnable t7 = new MyThread();   
            //每个任务会在一个线程上执行  
            pool.execute(t1);   
            pool.execute(t2);   
            pool.execute(t3);   
            pool.execute(t4);   
            pool.execute(t5);   
            pool.execute(t6);   
            pool.execute(t7);   
            //关闭线程池   
            pool.shutdown();   
        }   
    }   
      
    class MyThread implements Runnable{   
        @Override   
        public void run(){   
            System.out.println(Thread.currentThread().getName() + "正在执行。。。");   
            try{   
                Thread.sleep(100);   
            }catch(InterruptedException e){   
                e.printStackTrace();   
            }   
        }   
    }  


      运行结果如下:

     

        从结果中可以看出,七个任务是在线程池的三个线程上执行的。这里简要说明下用到的ThreadPoolExecuror类的构造方法中各个参数的含义。   

     

    public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long         keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)

    corePoolSize:线程池中所保存的线程数,包括空闲线程。

    maximumPoolSize:池中允许的最大线程数。

    keepAliveTime:当线程数大于核心数时,该参数为所有的任务终止前,多余的空闲线程等待新任务的最长时间。

    unit:等待时间的单位。

    workQueue:任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。

     

    总结 四种创建线程方法对比

    实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:

    1、线程只是实现Runnable或实现Callable接口,还可以继承其他类。

    2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。

    3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法。

    4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

    5、前三种的线程如果创建关闭频繁会消耗系统资源影响性能,而使用线程池可以不用线程的时候放回线程池,用的时候再从线程池取,项目开发中主要使用线程池

    注:在前三种中一般推荐采用实现接口的方式来创建多线程
     


     

    展开全文
  • 创建线程的四种方式

    千次阅读 2019-11-21 00:29:29
    1. 线程创建有四种方式 2. 继承Thread类 3. 实现Runnable接口 4.实现Callable接口 5.线程池创建线程 1. 线程创建有四种方式,分别为: 继承Thread类 实现Runnable接口 实现Callable接口 使用Executor...
  • 1.该类创建了一个新线程进行数据接收,解决了官方SerialPort类在数据量大时,通过Textbox等控件显示会卡顿的问题。 2.数据接收通过Action回调函数的方式进行封装,增强了代码的复用性。在需要同时使用多个COM口时变...
  • 众所周知,我们在创建线程时有四种方法可以用,分别是: 1、继承Thread类创建线程 2、实现Runnable接口创建线程 3、使用Callable和Future创建线程 4、使用线程池创建(使用java.util.concurrent.Executor接口) 其中...
  • 用Python创建线程的几种方法

    千次阅读 2019-06-01 16:40:48
    经过总结,Python创建线程主要有如下两种方法: 函数 类 接下来,我们就来揭开多线程的神秘面纱。 学会使用函数创建线程 在Python3中,Python提供了一个内置模块threading.Thread,可以很方便地让我们创建...
  • VC创建线程

    千次阅读 2018-11-07 20:42:29
    VC创建线程
  • 上位机的串口通信例子,定时发送接收都没问题。 使用CWinthread实现多线程发送和接收。 刚接触MFC多线程学习的新手会进一步理解多线程
  • Linux线程创建与同步

    千次阅读 2021-11-29 20:33:14
    线程的概念、实现;线程使用;线程安全;线程同步等只是概念
  • 线程,我自己看到的两篇讲解比较生动形象的易于理解的博文,如下。 线程与进程之间的关系: http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html 对线程的解(一篇比较生动形象的讲解): ...
  • } } 主线程和线程间的通讯 根据串口打开状态,设置复选框的状态 如果想实现,串口接收线程中,如果打开串口成功,则在主线程中的复选框变为不可选的状态这个功能。就要利用信号和槽的机制,在串口线程中发射信号,...
  • 线程创建的四种方式

    万次阅读 多人点赞 2018-03-30 14:34:14
    java中创建线程的四种方法以及区别 Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示: 1)继承Thread类创建线程 2)实现Runnable接口创建线程...
  • 1.创建一个TCP服务器接收服务端发送的信息并给予答复 2.以多线程方式实现接收客户端信息 3.间客户端发送的信息解码输出 思路分析 1.创建一个TCP客户端套接字 2.写一个接收消息的方法,可以接收客户端消息并解码...
  • 1. 什么是线程   线程是进程执行内部的一个执行分支,...PCB1所代表的进程通过vfork创建一个子进程,子进程再vfork一个新的子进程,以此类推产生两个新的子进程; 此时PCB1、PCB2、PCB3都指向同一块虚拟地址空间...
  • 通过Callable和FutureTask创建线程

    千次阅读 2020-08-30 09:22:05
    创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象
  • 本日志介绍一个很简单的串口接收数据线程,其基本思想是每隔100ms从串行口读取数据,如果串行口有数据则将其读出,然后将读出的二进制数据转换为字符串,压入队列中。 代码如下:import threading import time ...
  • 函数:_endthread() 和_endthreadex() ...终止线程:_endthread终止由_beginthread创建线程,并_endthreadex终止由_beginthreadex创建线程。 可以显式调用_endthread或_endthreadex以终止线...
  • 使用 Callable 和 Future 创建线程任务描述相关知识Callable和Future编程要求测试说明实现代码 任务描述 本关任务:通过 Callable 和 Future 来创建线程。 相关知识 从Java1.5版本开始,就提供了 Callable 和 Future...
  • nodejs中使用worker_threads来创建新的线程

    万次阅读 热门讨论 2021-01-21 20:27:05
    之前的文章中提到了,nodejs中有两种线程,一种是event loop用来相应用户的请求和处理各种callback。另一种就是worker pool用来处理各种耗时操作。 nodejs的官网提到了一个能够使用nodejs本地woker pool的lib叫做...
  • Java多线程创建与运行

    千次阅读 2021-11-10 14:06:37
    进程是系统进行资源分配和调度的基本单位。而线程是程序运行的最小单位。...进入到Thread源码中可以看到源码提供了两种创建线程的方式 方式一:继承Thread类 class PrimeThread extends Thread { long minP
  • 线程的4种创建方式及区别

    千次阅读 2020-08-04 16:32:51
    从 1、继承Thread类 2、实现Runnable接口 3、实现Callable接口 4、创建线程池
  • C++:类中创建线程

    千次阅读 2019-02-20 21:40:57
    按照C语言创建线程的方式,在C++类中创建线程会有报错: /usr/include/pthread.h:244:12: error: initializing argument 3 of ‘int pthread_create(pthread_t*, const pthread_attr_t*, void* (*)(void*), void*)...
  • 创建线程的几种方式

    千次阅读 2018-08-27 15:48:35
    编写多线程程序是为了实现多任务的并发执行,从而能够更好地与用户交互。一般有四种方法,Thread,Runnable,Callable,使用Executor框架来创建线程池。 Runnable和Callable的区别是, (1)Callable规定的方法是call(),...
  • 采用moveToThread方式实现QUdpSocket实现在新线程接收数据 其中对于线程对象的资源释放经验证ok
  • 如何在子线程创建并使用Handler

    千次阅读 2019-02-20 17:45:42
    上一篇文章我们从源代码的角度分析了Handler的实现,这篇文章我们说下如何在子线程创建Handler,在子线程创建Handler只需2步: 创建looper:Looper.prepare() 启动looper:Looper.loop() 为了创建当前...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 423,279
精华内容 169,311
关键字:

创建接收线程