精华内容
下载资源
问答
  • kafka 一个topic 被个group.id 自动创建
    万次阅读
    2019-01-02 10:59:39

            多遍看关于kafka 的原理,group与partition 与topic 的关系,自我感觉理解没问题了,写了一个netty结合kafka 5秒发送一个心跳,拉取一次消息,得到相应的record, 读取记录提交offset。

           我用的是 两个group.id 按照想法是应该没问题的就是 这两个 consumer 两个groupid  拉取同一个topic 和partition什么的无所谓没关系了,因为是一个partiton所以他的消息顺序也是一定没问题的,

           然后就开始了极其扯淡的一次次测试,找问题,改代码,测试找问题。。。。。

          百度过后发现大部分都不合我的问题,搜出来的博客都是一大堆和这个问题无关的,百度真是呵呵呵。

          终于发现有一篇是问题的答案了,   https://blog.csdn.net/piqianming/article/details/81875837

          可惜高人讲解的不是太详细,但是大概知道该怎么做了,

          我用的是eclipse 在main 方法中启动两个端口    再然后连接测试,看了之后我把他打成两个jar 包分别启动之后终于成功自动创建了groupid ,且可以接收到两次一样的消息,消费两次了,高人说的还是有道理的,jar启动时两个进程了,在eclipse下始终是一个进程两个线程。   

           其实怀疑自己也是不对的,一开始我的想法原理的理解是没问题的,只是对于测试有些不正确方法。

         

    更多相关内容
  • Qt创建多线程的两种方法

    万次阅读 多人点赞 2017-12-25 15:33:54
    Qt有两种线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法...

    来源:https://github.com/czyt1988/czyBlog/tree/master/tech/QtThread

    1.摘要

    Qt有两种多线程的方法,其中一种是继承QThread的run函数,另外一种是把一个继承于QObject的类转移到一个Thread里。 Qt4.8之前都是使用继承QThread的run这种方法,但是Qt4.8之后,Qt官方建议使用第二种方法。两种方法区别不大,用起来都比较方便,但继承QObject的方法更加灵活。这里要记录的是如何正确的创建一个线程,特别是如何正确的退出一个线程。

    本文先介绍QThread的普通用法,这个用法可能网上很多文章都介绍过,如果已经了解大可跳过此节,本文重点介绍线程退出的几种方法,根据需求正确的创建和退出线程等问题。

    2.Qt多线程方法1 继承QThread

    在使用继承QThreadrun方法之前需要了解一条规则:

    QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里

    QThread只有run函数是在新线程里的 

    QThread只有run函数是在新线程里的 

    QThread只有run函数是在新线程里的

    重要的事情说3遍!!!

    如果QThread是在ui所在的线程里生成,那么QThread的其他非run函数都是和ui线程一样的,所以,QThread的继承类的其他函数尽量别要有太耗时的操作,要确保所有耗时的操作都在run函数里。 在UI线程下调用QThread的非run函数(其实也不应该直接调用run函数,而应该使用start函数),和执行普通函数无区别,这时,如果这个函数要对QThread的某个变量进行变更,而这个变量在run函数里也会被用到,这时就需要注意加锁的问题,因为可能这个变量前几毫秒刚刚在run中调用,再调用时已经被另外的线程修改了。

    2.1写一个继承于QThread的线程

    本文的重点不是教会你继承run写一个多线程,任何有编程基础的5分钟就能学会使用QThread的方法,本文真正要讲的是后面那几节,如如何安全的退出一个线程,如何开启一个临时线程,运行结束马上关闭等问题。如果你对QThread有初步了解,那么可以略过这节,但你最好看看这节后面提出的几个问题。

    任何继承于QThread的线程都是通过继承QThreadrun函数来实现多线程的,因此,必须重写QThreadrun函数,把复杂逻辑写在QThreadrun函数中。

    看看一个普通继承QThread的例子: 头文件:

    #ifndef THREADFROMQTHREAD_H
    #define THREADFROMQTHREAD_H
    #include <QThread>
    
    class ThreadFromQThread : public QThread
    {
        Q_OBJECT
    signals:
        void message(const QString& info);
        void progress(int present);
    public:
        ThreadFromQThread(QObject* par);
        ~ThreadFromQThread();
        void setSomething();
        void getSomething();
        void setRunCount(int count);
        void run();
        void doSomething();
    private:
        int m_runCount;
    };
    
    #endif // THREADFROMQTHREAD_H

    cpp文件:

    #include "ThreadFromQThread.h"
    #include <QDebug>
    ThreadFromQThread::ThreadFromQThread(QObject* par) : QThread(par)
    ,m_runCount(20)
    {
    
    }
    
    ThreadFromQThread::~ThreadFromQThread()
    {
        qDebug() << "ThreadFromQThread::~ThreadFromQThread()";
    }
    
    void ThreadFromQThread::setSomething()
    {
        msleep(500);
        QString str = QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId());
        emit message(str);
    }
    
    void ThreadFromQThread::getSomething()
    {
        msleep(500);
        emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
    }
    
    void ThreadFromQThread::setRunCount(int count)
    {
        m_runCount = count;
        emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
    }
    
    void ThreadFromQThread::run()
    {
        int count = 0;
        QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
        emit message(str);
        while(1)
        {
            sleep(1);
            ++count;
            emit progress(((float)count / m_runCount) * 100);
            emit message(QString("ThreadFromQThread::run times:%1").arg(count));
            doSomething();
            if(m_runCount == count)
            {
                break;
            }
        }
    }
    
    void ThreadFromQThread::doSomething()
    {
        msleep(500);
        emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));    
    }

    这个简单的例子有一个Qt类常见的内容,包含了普通方法,信号槽,和一个run函数。这里函数setSomething();进行了500ms的延迟,getSomething同理。这是为了验证在QThread::run()之外调用QThread成员函数不会运行在新线程里。

    上面代码用到了QThread::currentThreadId()这是一个静态函数,用于返回当前线程句柄,这个值除了区分以外没有别的用处。

    为了验证这个线程,编写一个简单的界面,这个界面主要用于验证如下几个问题:、

    • 在UI线程调用setSomething();函数和getSomething();函数会不会卡顿?
    • 在UI线程调用QThread::quit()QThread::exit()函数会不会停止线程?
    • 在UI线程调用QThread::terminate函数会不会停止线程?
    • 如何正确的退出线程?

    2.2 QThread的几个函数quit、exit、terminate函数

    为了验证上面这些,编写一个简单的界面如下图所示:

    #include "Widget.h"
    #include "ui_Widget.h"
    #include "ThreadFromQThread.h"
    #include <QDebug>
    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
    {
        ui->setupUi(this);
        //控件初始化
        ui->progressBar->setRange(0,100);
        ui->progressBar->setValue(0);
        ui->progressBar_heart->setRange(0,100);
        ui->progressBar_heart->setValue(0);
        //按钮的信号槽关联
        connect(ui->pushButton_qthread1,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadClicked);
        connect(ui->pushButton_qthread1_setSomething,&QPushButton::clicked
                ,this,&Widget::onButtonQthread1SetSomethingClicked);
        connect(ui->pushButton_qthread1_getSomething,&QPushButton::clicked
                ,this,&Widget::onButtonQthread1GetSomethingClicked);
        connect(ui->pushButton_qthreadQuit,&QPushButton::clicked
                ,this,&Widget::onButtonQthreadQuitClicked);
        connect(ui->pushButton_qthreadTerminate,&QPushButton::clicked
                ,this,&Widget::onButtonQthreadTerminateClicked);
        connect(ui->pushButton_qthreadExit,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadExitClicked);
        connect(ui->pushButton_doSomthing,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadDoSomthingClicked);
        connect(ui->pushButton_qthreadRunLocal,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadRunLoaclClicked);
        //
        connect(ui->pushButton_qobjectStart,&QPushButton::clicked
                ,this,&Widget::onButtonObjectMove2ThreadClicked);
        connect(ui->pushButton_objQuit,&QPushButton::clicked
                ,this,&Widget::onButtonObjectQuitClicked);
        //
        connect(&m_heart,&QTimer::timeout,this,&Widget::heartTimeOut);
        m_heart.setInterval(100);
        //全局线程的创建
        m_thread = new ThreadFromQThread(this);
        connect(m_thread,&ThreadFromQThread::message
                ,this,&Widget::receiveMessage);
        connect(m_thread,&ThreadFromQThread::progress
                ,this,&Widget::progress);
        connect(m_thread,&QThread::finished
                ,this,&Widget::onQThreadFinished);
    
        m_heart.start();
    }
    
    
    
    Widget::~Widget()
    {
        qDebug() << "start destroy widget";
        m_thread->stopImmediately();//由于此线程的父对象是Widget,因此退出时需要进行判断
        m_thread->wait();
        delete ui;
        qDebug() << "end destroy widget";
    }
    
    void Widget::onButtonQThreadClicked()
    {
        ui->progressBar->setValue(0);
        if(m_thread->isRunning())
        {
            return;
        }
        m_thread->start();
    }
    
    void Widget::progress(int val)
    {
        ui->progressBar->setValue(val);
    }
    
    void Widget::receiveMessage(const QString &str)
    {
        ui->textBrowser->append(str);
    }
    
    void Widget::heartTimeOut()
    {
        static int s_heartCount = 0;
        ++s_heartCount;
        if(s_heartCount > 100)
        {
            s_heartCount = 0;
        }
        ui->progressBar_heart->setValue(s_heartCount);
    }
    
    void Widget::onButtonQthread1SetSomethingClicked()
    {
        m_thread->setSomething();
    }
    
    void Widget::onButtonQthread1GetSomethingClicked()
    {
        m_thread->getSomething();
    }
    
    void Widget::onButtonQthreadQuitClicked()
    {
        ui->textBrowser->append("m_thread->quit() but not work");
        m_thread->quit();
    }
    
    void Widget::onButtonQthreadTerminateClicked()
    {
        m_thread->terminate();
    }
    
    void Widget::onButtonQThreadDoSomthingClicked()
    {
        m_thread->doSomething();
    }
    
    void Widget::onButtonQThreadExitClicked()
    {
        m_thread->exit();
    }
    
    void Widget::onQThreadFinished()
    {
        ui->textBrowser->append("ThreadFromQThread finish");
    }

    界面为上面提到的几个问题提供了按钮, 界面有一个心跳进度条,它是主程序的定时器控制,每100ms触发用于证明主程序的ui线程没有卡死。第二个进度条由线程控制。

    点击"QThread run"按钮,触发onButtonQThreadClicked槽,子线程会运行,子线程运行起来后,会打印

    ../QtThreadTest/ThreadFromQThread.cpp->run,thread id:2900388672

    可以确定线程运行的id是2900388672 子线程是个循环,每次循环都会有打印信息:

    ThreadFromQThread::run times:1 doSomething->../QtThreadTest/ThreadFromQThread.cpp,thread id:2900388672 ThreadFromQThread::run times:2 doSomething->../QtThreadTest/ThreadFromQThread.cpp,thread id:2900388672

    doSomething是在run函数里调用,其线程id是2900388672,可见这时doSomething函数是运行在子线程里的。

    这时,我在界面点击getSomething,setSomething,doSomething会打印:

    getSomething->../QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784 setSomething->../QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784 doSomething->../QtThreadTest/ThreadFromQThread.cpp,thread id:3021526784

    说明在非run函数里调用QThread的成员函数,并不是在线程里运行(3021526784是widget所在线程)

    这时我点击quit,thread并没进行任何处理,QThread在不调用exec()情况下是exit函数和quit函数是没有作用的。

    m_thread->quit() but not work

    点击terminate按钮,线程马上终止,打印:

    ThreadFromQThread finish

    动态图如下图所示:

    因此可以看出quitexit函数都不会中途终端线程,要马上终止一个线程可以使用terminate函数,但这个函数存在非常不安定因素,不推荐使用。那么如何安全的终止一个线程呢?

    2.3 正确的终止一个线程

    最简单的方法是添加一个bool变量,通过主线程修改这个bool变量来进行终止,但这样有可能引起访问冲突,需要加锁 我们需要在原来的头文件加上如下语句:

    #include <QMutex>
    
    class ThreadFromQThread : public QThread
    {
    ...........
    public slots:
        void stopImmediately();
    private:
        QMutex m_lock;
        bool m_isCanRun;
    .........
    };

    run函数需要进行修改:

    void ThreadFromQThread::stopImmediately()
    {
        QMutexLocker locker(&m_lock);
        m_isCanRun = false;
    }
    
    void ThreadFromQThread::run()
    {
        int count = 0;
        m_isCanRun = true;//标记可以运行
        QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((unsigned int)QThread::currentThreadId());
        emit message(str);
        while(1)
        {
            sleep(1);
            ++count;
            emit progress(((float)count / m_runCount) * 100);
            emit message(QString("ThreadFromQThread::run times:%1").arg(count));
            doSomething();
            if(m_runCount == count)
            {
                break;
            }
    
            {
                QMutexLocker locker(&m_lock);
                if(!m_isCanRun)//在每次循环判断是否可以运行,如果不行就退出循环
                {
                    return;
                }
            }
        }
    }

    QMutexLocker可以安全的使用QMutex,以免忘记解锁(有点类似std::unique_ptr),这样每次循环都会看看是否要马上终止。 在线程需要马上退出时,可以在外部调用stopImmediately()函数终止线程,之前的例子可以知道,由于在主线程调用QThread非run()函数的函数都是在主线程运行,因此,在主线程调用类似m_thread->stopImmediately()会几乎马上把线程的成员变量m_isCanRun设置为false(面对多线程问题要用面向过程的思维思考),因此在子线程的run函数的循环中遇到m_isCanRun的判断后就会退出run函数,继承QThread的函数在运行完run函数后就视为线程完成,会发射finish信号。

    2.4 如何正确启动一个线程

    线程的启动有几种方法,这几种方法设计到它的父对象归属问题,和如何删除他的问题。首先要搞清楚这个线程是否和UI的生命周期一致,直到UI结束线程才结束,还是这个线程只是临时生成,等计算完成就销毁。

    第一种情况的线程在创建时会把生成线程的窗体作为它的父对象,这样窗体结束时会自动析构线程的对象。但这时候要注意一个问题,就是窗体结束时线程还未结束如何处理,如果没有处理这种问题,你会发现关闭窗口时会导致程序崩溃。往往这种线程是一个监控线程,如监控某个端口的线程。为了好区分,暂时叫这种叫全局线程,它在UI的生命周期中都存在。

    第二种情况是一种临时线程,这种线程一般是突然要处理一个大计算,为了不让UI假死需要触发的线程,这时需要注意一个问题,就是在线程还没计算完成,用户突然终止或变更时如何处理,这种线程往往更多见且更容易出错,如打开一个大文件,显示一个大图片,用户可能看一个大图片还没等图片处理完成又切换到下一个图片,这时绘图线程要如何处理才能顺利解决?为了好区分,暂时叫这种叫局部线程,它在UI的生命周期中仅仅是某时刻才会触发,然后销毁。

    这就涉及到如何终止正在执行的线程这个问题!

    2.4.1正确的启动一个全局线程(和UI一直存在的线程)

    我发现大部分网上的教程都是教你创建一个全局的线程,但往往这种线程用的不多,也比较好管理,需要注意的是程序退出时对线程的处理问题。 在ui的头文件中声明一个线程的指针

    widget.h:

    ThreadFromQThread* m_thread;

    wodget.cpp:

    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit Widget(QWidget *parent = 0);
        ~Widget();
    private slots:
        void onButtonQThreadClicked();
        void onButtonQthread1SetSomethingClicked();
        void onButtonQthread1GetSomethingClicked();
        void onButtonQthreadQuitClicked();
        void onButtonQthreadTerminateClicked();
        void onButtonQThreadDoSomthingClicked();
        void onQThreadFinished();
    ......
        void progress(int val);
        void receiveMessage(const QString& str);
        void heartTimeOut();
    private:
        Ui::Widget *ui;
        ThreadFromQThread* m_thread;
        QTimer m_heart;
    ......
    };

    先看窗体生成的构造函数

    Widget::Widget(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::Widget)
      ,m_objThread(NULL)
    {
       
       ui->setupUi(this);
        //控件初始化
        ui->progressBar->setRange(0,100);
        ui->progressBar->setValue(0);
        ui->progressBar_heart->setRange(0,100);
        ui->progressBar_heart->setValue(0);
        //按钮的信号槽关联
        connect(ui->pushButton_qthread1,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadClicked);
        connect(ui->pushButton_qthread1_setSomething,&QPushButton::clicked
                ,this,&Widget::onButtonQthread1SetSomethingClicked);
        connect(ui->pushButton_qthread1_getSomething,&QPushButton::clicked
                ,this,&Widget::onButtonQthread1GetSomethingClicked);
        connect(ui->pushButton_qthreadQuit,&QPushButton::clicked
                ,this,&Widget::onButtonQthreadQuitClicked);
        connect(ui->pushButton_qthreadTerminate,&QPushButton::clicked
                ,this,&Widget::onButtonQthreadTerminateClicked);
        connect(ui->pushButton_doSomthing,&QPushButton::clicked
                ,this,&Widget::onButtonQThreadDoSomthingClicked);
        //心跳的关联
        connect(&m_heart,&QTimer::timeout,this,&Widget::heartTimeOut);
        m_heart.setInterval(100);
        //全局线程的创建
        //全局线程创建时可以把窗体指针作为父对象
        m_thread = new ThreadFromQThread(this);
        //关联线程的信号和槽
        connect(m_thread,&ThreadFromQThread::message
                ,this,&Widget::receiveMessage);//
        connect(m_thread,&ThreadFromQThread::progress
                ,this,&Widget::progress);
        connect(m_thread,&QThread::finished
                ,this,&Widget::onQThreadFinished);
        //UI心跳开始
        m_heart.start();
    }

    由于是全局存在的线程,因此在窗体创建时就创建线程,可以把线程的父对象设置为窗体,这时需要注意,别手动delete线程指针。用于你的QThread是在Qt的事件循环里面,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete,如果没有多线程,手动delete可能不会发生问题,但是多线程情况下delete非常容易出问题,那是因为有可能你要删除的这个对象在Qt的事件循环里还排队,但你却已经在外面删除了它,这样程序会发生崩溃。

    如果你确实要删除,请参阅void QObject::deleteLater () [slot]这个槽,这个槽非常有用,尤其是对局部线程来说。后面会经常用到它用于安全的结束线程。

    在需要启动线程的地方调用start函数即可启动线程。

    void Widget::onButtonQThreadClicked()
    {
        ui->progressBar->setValue(0);
        if(m_thread->isRunning())
        {
            return;
        }
        m_thread->start();
    }

    如果线程已经运行,你重复调用start其实是不会进行任何处理。

    一个全局线程就那么简单,要用的时候start一下就行。真正要注意的是如何在ui结束时把线程安全退出。

    在widget的析构函数应该这样写:

    Widget::~Widget()
    {
        qDebug() << "start destroy widget";
        m_thread->stopImmediately();
        m_thread->wait();
        delete ui;
        qDebug() << "end destroy widget";
    }

    这里要注意的是m_thread->wait();这一句,这一句是主线程等待子线程结束才能继续往下执行,这样能确保过程是单一往下进行的,也就是不会说子线程还没结束完,主线程就destrioy掉了(m_thread的父类是主线程窗口,主线程窗口如果没等子线程结束就destroy的话,会顺手把m_threaddelete这时就会奔溃了),因此wait的作用就是挂起,一直等到子线程结束。

    还有一种方法是让QThread自己删除自己,就是在new线程时,不指定父对象,通过绑定**void QObject::deleteLater () [slot]**槽让它自动释放。这样在widget析构时可以免去m_thread->wait();这句。

    2.4.2 如何启动一个局部线程(用完即释放的线程)

    启动一个局部线程(就是运行完自动删除的线程)方法和启动全局线程差不多,但要关联多一个槽函数,就是之前提到的**void QObject::deleteLater () [slot]**,这个槽函数是能安全释放线程资源的关键(直接delete thread指针不安全)。

    简单的例子如下:

    void Widget::onButtonQThreadRunLoaclClicked()
    {
        //局部线程的创建的创建
        ThreadFromQThread* thread = new ThreadFromQThread(NULL);//这里父对象指定为NULL
        connect(thread,&ThreadFromQThread::message
                ,this,&Widget::receiveMessage);
        connect(thread,&ThreadFromQThread::progress
                ,this,&Widget::progress);
        connect(thread,&QThread::finished
                ,this,&Widget::onQThreadFinished);
        connect(thread,&QThread::finished
                ,thread,&QObject::deleteLater);//线程结束后调用deleteLater来销毁分配的内存
        thread->start();
    }

    这个例子还是启动之前的线程,但不同的是:

    • new ThreadFromQThread(NULL);并没有给他指定父对象
    • connect(thread,&QThread::finished ,thread,&QObject::deleteLater);线程结束后调用deleteLater来销毁分配的内存。 再线程运行完成,发射finished信号后会调用deleteLater函数,在确认消息循环中没有这个线程的对象后会销毁。

    但是要注意避免重复点按钮重复调用线程的情况,对于一些需求,线程开启后再点击按钮不会再重新生成线程,一直等到当前线程执行完才能再次点击按钮,这种情况很好处理,加个标记就可以实现,也一般比较少用。

    另外更多见的需求是,再次点击按钮,需要终结上次未执行完的线程,重新执行一个新线程。这种情况非常多见,例如一个普通的图片浏览器,都会有下一张图和上一张图这种按钮,浏览器加载图片一般都在线程里执行(否则点击超大图片时图片浏览器会类似卡死的状态),用户点击下一张图片时需要终止正在加载的当前图片,加载下一张图片。你不能要求客户要当前图片加载完才能加载下一张图片,这就几乎沦为单线程了。这时候,就需要终止当前线程,开辟新线程加载下一个图片。

    这时,上面的函数将会是大概这个样子的

    UI的头文件需要一个成员变量记录正在运行的线程

    private slots:
       void onLocalThreadDestroy(QObject* obj);
    private:
       QThread* m_currentRunLoaclThread;

    运行生成临时线程的函数将变为

    void Widget::onButtonQThreadRunLoaclClicked()
    {
        //局部线程的创建的创建
        if(m_currentRunLoaclThread)
        {
             m_currentRunLoaclThread->stopImmediately();
        }
        ThreadFromQThread* thread = new ThreadFromQThread(NULL);
        connect(thread,&ThreadFromQThread::message
                ,this,&Widget::receiveMessage);
        connect(thread,&ThreadFromQThread::progress
                ,this,&Widget::progress);
        connect(thread,&QThread::finished
                ,this,&Widget::onQThreadFinished);
        connect(thread,&QThread::finished
                ,thread,&QObject::deleteLater);//线程结束后调用deleteLater来销毁分配的内存
        connect(thread,&QObject::destroyed,this,&Widget::onLocalThreadDestroy);
        thread->start();
        m_currentRunLoaclThread = thread;
    }
    
    void Widget::onLocalThreadDestroy(QObject *obj)
    {
        if(qobject_cast<QObject*>(m_currentRunLoaclThread) == obj)
        {
            m_currentRunLoaclThread = NULL;
        }
    }

    这里用一个临时变量记录当前正在运行的局部线程,由于线程结束时会销毁自己,因此要通知主线程把这个保存线程指针的临时变量设置为NULL 因此用到了QObject::destroyed信号,在线程对象析构时通知UI把m_currentRunLoaclThread设置为nullptr;

    2.5 继承QThread的一些总结

    • QThread执行start函数之后,run函数还未运行完毕,再次start会出现什么后果?

    答案是:不会发生任何结果,QThread还是继续执行它的run函数,run函数不会被重新调用。虽然在线程未结束时调用start不会出现什么结果,但为了谨慎起见,还是建议在start之前进行判断:

    void Widget::onButtonQThreadClicked()
    {
        ui->progressBar->setValue(0);
        if(m_thread->isRunning())
        {
            return;
        }
        m_thread->start();
    }

    这种调用方法估计了解过QThread的都知道

    • 在线程运行过程调用quit函数有什么效果

    答案是:不会发生任何效果,QThread不会因为你调用quit函数而退出正在运行到一半的run,正确退出线程的方法上面有介绍。那quit到底有什么用的呢,这要到下篇才能看出它的作用。使用moveToThread方法执行多线程时,这个函数将有大作用。

    • 程序在退出时要判断各线程是否已经退出,没退出的应该让它终止 如果不进行判断,很可能程序退出时会崩溃。如果线程的父对象是窗口对象,那么在窗体的析构函数中,还需要调用wait函数等待线程完全结束再进行下面的析构。

    • 善用QObject::deleteLater 和 QObject::destroyed来进行内存管理 由于多线程环境你不可预料下一步是哪个语句执行,因此,加锁和自动删除是很有用的工具,加锁是通过效率换取安全,用Qt的信号槽系统可以更有效的处理这些问题。

    示例代:

    --> 见 github

    #Qt使用多线程的一些心得——2.继承QObject的多线程使用方法

    现在Qt官方并不是很推荐继承QThread来实现多线程方法,而是极力推崇继承QObject的方法来实现,当然用哪个方法实现要视情况而定,别弄错了就行,估计Qt如此推崇继承QObject的方法可能是QThread太容易用错的原因。

    #前言

    上一篇介绍了传统的多线程使用方法——继承QThread来实现多线程,这也是很多框架的做法(MFC),但Qt还有一种多线程的实现方法,比直接继承QThread更为灵活,就是直接继承QObject实现多线程。

    QObject是Qt框架的基本类,但凡涉及到信号槽有关的类都是继承于QObjectQObject是一个功能异常强大的类,它提供了Qt关键技术信号和槽的支持以及事件系统的支持,同时它提供了线程操作的接口,也就是QObject是可以选择不同的线程里执行的。

    QObject的线程转移函数是:void moveToThread(QThread * targetThread) ,通过此函数可以把一个**顶层Object(就是没有父级)**转移到一个新的线程里。

    QThread非常容易被新手误用,主要是QThread自身并不生存在它run函数所在的线程,而是生存在旧的线程中,此问题在上一篇重点描述了。由于QThread的这个特性,导致在调用QThread的非run函数容易在旧线程中执行,因此人们发现了一个新的魔改QThread的方法: 人们发现,咦,QThread也继承QObjectQObject有个函数void moveToThread(QThread * targetThread)可以把Object的运行线程转移,那么:(下面是非常不推荐的魔改做法,别用此方法):

    class MyThread : public QThread{
    public:
        MyThread ()
       {
            moveToThread(this);
       }
    ……
    };

    直接把MyThread整个转移到MyThread的新线程中,MyThread不仅run,其它函数也在新线程里了。这样的确可以运行正常,但这并不是QThread设计的初衷,Qt还专门发过一篇文章来吐槽这个做法。

    在Qt4.8之后,Qt多线程的写法最好还是通过QObject来实现,和线程的交互通过信号和槽(实际上其实是通过事件)联系。

    #继承QObject的多线程实现

    QObject来实现多线程有个非常好的优点,就是默认就支持事件循环(Qt的许多非GUI类也需要事件循环支持,如QTimerQTcpSocket),QThread要支持事件循环需要在QThread::run()中调用QThread::exec()来提供对消息循环的支持,否则那些需要事件循环支持的类都不能正常发送信号,因此如果要使用信号和槽,那就直接使用QObject来实现多线程。

    看看Qt官方文档的例子:

    class Worker : public QObject
    {
        Q_OBJECT
    
    public slots:
        void doWork(const QString &parameter) {
            QString result;
            /* ... here is the expensive or blocking operation ... */
            emit resultReady(result);
        }
    
    signals:
        void resultReady(const QString &result);
    };
    
    class Controller : public QObject
    {
        Q_OBJECT
        QThread workerThread;
    public:
        Controller() {
            Worker *worker = new Worker;
            worker->moveToThread(&workerThread);
            connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
            connect(this, &Controller::operate, worker, &Worker::doWork);
            connect(worker, &Worker::resultReady, this, &Controller::handleResults);
            workerThread.start();
        }
        ~Controller() {
            workerThread.quit();
            workerThread.wait();
        }
    public slots:
        void handleResults(const QString &);
    signals:
        void operate(const QString &);
    };

    使用QObject创建多线程的方法如下:

    • 写一个继承QObject的类,对需要进行复杂耗时逻辑的入口函数声明为槽函数
    • 此类在旧线程new出来,不能给它设置任何父对象
    • 同时声明一个QThread对象,在官方例子里,QThread并没有new出来,这样在析构时就需要调用QThread::wait(),如果是堆分配的话, 可以通过deleteLater来让线程自杀
    • 把obj通过moveToThread方法转移到新线程中,此时object已经是在线程中了
    • 把线程的finished信号和object的deleteLater槽连接,这个信号槽必须连接,否则会内存泄漏
    • 正常连接其他信号和槽(在连接信号槽之前调用moveToThread,不需要处理connect的第五个参数,否则就显示声明用Qt::QueuedConnection来连接)
    • 初始化完后调用'QThread::start()'来启动线程
    • 在逻辑结束后,调用QThread::quit退出线程的事件循环

    使用QObject来实现多线程比用继承QThread的方法更加灵活,整个类都是在新的线程中,通过信号槽和主线程传递数据,前篇文章的例子用继承QObject的方法实现的话,代码如下: 头文件(ThreadObject.h):

    #include <QObject>
    #include <QMutex>
    class ThreadObject : public QObject
    {
        Q_OBJECT
    public:
        ThreadObject(QObject* parent = NULL);
        ~ThreadObject();
        void setRunCount(int count);
        void stop();
    signals:
        void message(const QString& info);
        void progress(int present);
    public slots:
        void runSomeBigWork1();
        void runSomeBigWork2();
    private:
        int m_runCount;
        int m_runCount2;
        bool m_isStop;
    
        QMutex m_stopMutex;
    };

    cpp文件(ThreadObject.cpp):

    #include "ThreadObject.h"
    #include <QThread>
    #include <QDebug>
    #include <QMutexLocker>
    #include <QElapsedTimer>
    #include <limits>
    ThreadObject::ThreadObject(QObject *parent):QObject(parent)
      ,m_runCount(10)
      ,m_runCount2(std::numeric_limits<int>::max())
      ,m_isStop(true)
    {
    
    }
    
    ThreadObject::~ThreadObject()
    {
        qDebug() << "ThreadObject destroy";
        emit message(QString("Destroy %1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
    }
    
    void ThreadObject::setRunCount(int count)
    {
        m_runCount = count;
        emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
    }
    
    void ThreadObject::runSomeBigWork1()
    {
        {
            QMutexLocker locker(&m_stopMutex);
            m_isStop = false;
        }
        int count = 0;
        QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
        emit message(str);
        int process = 0;
        while(1)
        {
            {
                QMutexLocker locker(&m_stopMutex);
                if(m_isStop)
                    return;
            }
            if(m_runCount == count)
            {
                break;
            }
            sleep(1);
            int pro = ((float)count / m_runCount) * 100;
            if(pro != process)
            {
                process = pro;
                emit progress(((float)count / m_runCount) * 100);
                emit message(QString("Object::run times:%1,m_runCount:%2").arg(count).arg(m_runCount2));
            }
            ++count;
        }
    }
    
    void ThreadObject::runSomeBigWork2()
    {
        {
            QMutexLocker locker(&m_stopMutex);
            m_isStop = false;
        }
        int count = 0;
        QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
        emit message(str);
        int process = 0;
        QElapsedTimer timer;
        timer.start();
        while(1)
        {
            {
                QMutexLocker locker(&m_stopMutex);
                if(m_isStop)
                    return;
            }
            if(m_runCount2 == count)
            {
                break;
            }
            int pro = ((float)count / m_runCount2) * 100;
            if(pro != process)
            {
                process = pro;
                emit progress(pro);
                emit message(QString("%1,%2,%3,%4")
                             .arg(count)
                             .arg(m_runCount2)
                             .arg(pro)
                             .arg(timer.elapsed()));
                timer.restart();
            }
            ++count;
        }
    }
    
    void ThreadObject::stop()
    {
        QMutexLocker locker(&m_stopMutex);
        emit message(QString("%1->%2,thread id:%3").arg(__FUNCTION__).arg(__FILE__).arg((int)QThread::currentThreadId()));
        m_isStop = true;
    }

    这个Object有两个耗时函数work1和work2,这两个耗时函数的调用都是通过槽函数触发,同时为了能及时打断线程,添加了一个stop函数,stop函数不是通过信号槽触发,因此需要对数据进行保护,这里用了互斥锁对一个bool变量进行了保护处理,当然会失去一些性能。

    主界面的头文件(截取部分代码):

    #include <QWidget>
    #include <QTimer>
    class ThreadFromQThread;
    class ThreadObject;
    namespace Ui {
    class Widget;
    }
    
    class Widget : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit Widget(QWidget *parent = 0);
        ~Widget();
    signals:
        void startObjThreadWork1();
        void startObjThreadWork2();
    private slots:
    ……
        void onButtonObjectMove2ThreadClicked();
        void onButtonObjectMove2Thread2Clicked();
        void onButtonObjectQuitClicked();
        void onButtonObjectThreadStopClicked();
        
    
    
        void progress(int val);
        void receiveMessage(const QString& str);
        void heartTimeOut();
    
    private:
        void startObjThread(); 
    private:
        Ui::Widget *ui;
        ……
        ThreadObject* m_obj;
        QThread* m_objThread;
    
    };

    cpp文件

    Widget::~Widget()
    {
        qDebug() << "start destroy widget";
       
        if(m_objThread)
        {
            m_objThread->quit();
        }
        m_objThread->wait();
        qDebug() << "end destroy widget";
    }
    
    //创建线程
    void Widget::startObjThread()
    {
        if(m_objThread)
        {
            return;
        }
        m_objThread= new QThread();
        m_obj = new ThreadObject();
        m_obj->moveToThread(m_objThread);
        connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);
        connect(m_objThread,&QThread::finished,m_obj,&QObject::deleteLater);
        connect(this,&Widget::startObjThreadWork1,m_obj,&ThreadObject::runSomeBigWork1);
        connect(this,&Widget::startObjThreadWork2,m_obj,&ThreadObject::runSomeBigWork2);
        connect(m_obj,&ThreadObject::progress,this,&Widget::progress);
        connect(m_obj,&ThreadObject::message,this,&Widget::receiveMessage);
    
        m_objThread->start();
    }
    
    //调用线程的runSomeBigWork1
    void Widget::onButtonObjectMove2ThreadClicked()
    {
        if(!m_objThread)
        {
            startObjThread();
        }
    
        emit startObjThreadWork1();//主线程通过信号换起子线程的槽函数
    
        ui->textBrowser->append("start Obj Thread work 1");
    }
    //调用线程的runSomeBigWork2
    void Widget::onButtonObjectMove2Thread2Clicked()
    {
        if(!m_objThread)
        {
            startObjThread();
        }
        emit startObjThreadWork2();//主线程通过信号换起子线程的槽函数
    
        ui->textBrowser->append("start Obj Thread work 2");
    }
    
    //调用线程的中断
    void Widget::onButtonObjectThreadStopClicked()
    {
        if(m_objThread)
        {
            if(m_obj)
            {
                m_obj->stop();
            }
        }
    }

    创建线程和官方例子差不多,区别是QThread也是用堆分配,这样,让QThread自杀的槽就一定记得加上,否则QThread就逍遥法外了。

    connect(m_objThread,&QThread::finished,m_objThread,&QObject::deleteLater);

    #加了锁对性能有多大的影响 上例的runSomeBigWork2中,让一个int不停自加1,一直加到int的最大值,为了验证加锁和不加锁的影响,这里对加锁和不加锁运行了两次观察耗时的变化

    void ThreadObject::runSomeBigWork2()
    {
        {
            QMutexLocker locker(&m_stopMutex);
            m_isStop = false;
        }
        int count = 0;
        QString str = QString("%1->%2,thread id:%3").arg(__FILE__).arg(__FUNCTION__).arg((int)QThread::currentThreadId());
        emit message(str);
        int process = 0;
        QElapsedTimer timer;
        timer.start();
        while(1)
        {
            {
                QMutexLocker locker(&m_stopMutex);
                if(m_isStop)
                    return;
            }
            if(m_runCount2 == count)
            {
                break;
            }
            int pro = ((float)count / m_runCount2) * 100;
            if(pro != process)
            {
                process = pro;
                emit progress(pro);
                emit message(QString("%1,%2,%3,%4")
                             .arg(count)
                             .arg(m_runCount2)
                             .arg(pro)
                             .arg(timer.elapsed()));
                timer.restart();
            }
            ++count;
        }
    }

    结果如下:

    这里没个横坐标的每%1进行了21474837次循环,由统计图可见,Debug模式下使用了锁后性能下降4倍,Release模式下下降1.5倍的样子

    展开全文
  • Mysql创建多表视图view

    千次阅读 2021-04-16 10:13:46
    三个表视图 CREATE VIEW v_user_role(user_id,user_name,role_id,role_name) as SELECT user.user_id,user.user_name,user_role.role_...WHERE user.user_id = user_role.user_id AND role.role_id = user_role.role_id

    三个表视图

    CREATE VIEW v_user_role(user_id,user_name,role_id,role_name)
    as
    SELECT user.user_id,user.user_name,user_role.role_id,role.role_name
    FROM user,user_role,role
    WHERE user.user_id = user_role.user_id AND role.role_id = user_role.role_id
    
    展开全文
  • 线程(一):创建线程和线程的常用方法

    万次阅读 多人点赞 2018-09-01 19:14:23
    一:为什么要学线程 应付面试 :线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。 了解并发编程:实际工作中很少写线程的代码,这部分代码一般都被人封装起来了,在业务中使用线程的机会也...
    分享一个朋友的人工智能教程(请以“右键”->"在新标签页中打开连接”的方式访问)。比较通俗易懂,风趣幽默,感兴趣的朋友可以去看看。

    一:为什么要学多线程

    1. 应付面试 :多线程几乎是面试中必问的题,所以掌握一定的基础知识是必须的。
    2. 了解并发编程:实际工作中很少写多线程的代码,这部分代码一般都被人封装起来了,在业务中使用多线程的机会也不是很多(看具体项目),虽然代码中很少会自己去创建线程,但是实际环境中每行代码却都是并行执行的,同一时刻大量请求同一个接口,并发可能会产生一些问题,所以也需要掌握一定的并发知识

    二:进程与线程

    1. 进程

    进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

    2. 线程

    线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

    一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。

    线程是一条可以执行的路径。

    • 对于单核CPU而言:多线程就是一个CPU在来回的切换,在交替执行。
    • 对于多核CPU而言:多线程就是同时有多条执行路径在同时(并行)执行,每个核执行一个线程,多个核就有可能是一块同时执行的。

    3. 进程与线程的关系

    一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:

    • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

    • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

    • 处理机分给线程,即真正在处理机上运行的是线程。

    • 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

    如果把上课的过程比作进程,把老师比作CPU,那么可以把每个学生比作每个线程,所有学生共享这个教室(也就是所有线程共享进程的资源),上课时学生A向老师提出问题,老师对A进行解答,此时可能会有学生B对老师的解答不懂会提出B的疑问(注意:此时可能老师还没有对A同学的问题解答完毕),此时老师又向学生B解惑,解释完之后又继续回答学生A的问题,同一时刻老师只能向一个学生回答问题(即:当多个线程在运行时,同一个CPU在某一个时刻只能服务于一个线程,可能一个线程分配一点时间,时间到了就轮到其它线程执行了,这样多个线程在来回的切换)

    4. 为什么要使用多线程

    多线程可以提高程序的效率。

    实际生活案例:村长要求喜洋洋在一个小时内打100桶水,可以喜洋洋一个小时只能打25桶水,如果这样就需要4个小时才能完成任务,为了在一个小时能够完成,喜洋洋就请美洋洋、懒洋洋、沸洋洋,来帮忙,这样4只羊同时干活,在一小时内完成了任务。原本用4个小时完成的任务现在只需要1个小时就完成了,如果把每只羊看做一个线程,多只羊即多线程可以提高程序的效率。

    5. 多线程应用场景

    • 一般线程之间比较独立,互不影响
    • 一个线程发生问题,一般不影响其它线程

    三:多线程的实现方式

    1. 顺序编程

    顺序编程:程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。

    public class Main {
        // 顺序编程 吃喝示例:当吃饭吃不完的时候,是不能喝酒的,只能吃完晚才能喝酒
        public static void main(String[] args) throws Exception {
    		// 先吃饭再喝酒
            eat();
            drink();
        }
    
        private static void eat() throws Exception {
            System.out.println("开始吃饭?...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束吃饭?...\t" + new Date());
        }
    
        private static void drink() throws Exception {
            System.out.println("开始喝酒?️...\t" + new Date());
            Thread.sleep(5000);
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    这里写图片描述

    2. 并发编程

    并发编程:多个任务可以同时做,常用与任务之间比较独立,互不影响。
    线程上下文切换:

    同一个时刻一个CPU只能做一件事情,即同一时刻只能一个线程中的部分代码,假如有两个线程,Thread-0和Thread-1,刚开始CPU说Thread-0你先执行,给你3毫秒时间,Thread-0执行了3毫秒时间,但是没有执行完,此时CPU会暂停Thread-0执行并记录Thread-0执行到哪行代码了,当时的变量的值是多少,然后CPU说Thread-1你可以执行了,给你2毫秒的时间,Thread-1执行了2毫秒也没执行完,此时CPU会暂停Thread-1执行并记录Thread-1执行到哪行代码了,当时的变量的值是多少,此时CPU又说Thread-0又该你,这次我给你5毫秒时间,去执行吧,此时CPU就找出上次Thread-0线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续执行,结果用了2毫秒就Thread-0就执行完了,就终止了,然后CPU说Thread-1又轮到你,这次给你4毫秒,同样CPU也会先找出上次Thread-1线程执行到哪行代码了,当时的变量值是多少,然后接着上次继续开始执行,结果Thread-1在4毫秒内也执行结束了,Thread-1也结束了终止了。CPU在来回改变线程的执行机会称之为线程上下文切换。

    public class Main {
        public static void main(String[] args) {
    	    // 一边吃饭一边喝酒
            new EatThread().start();
            new DrinkThread().start();
        }
    }
    
    class EatThread extends Thread{
        @Override
        public void run() {
            System.out.println("开始吃饭?...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束吃饭?...\t" + new Date());
        }
    }
    
    class DrinkThread extends Thread {
        @Override
        public void run() {
            System.out.println("开始喝酒?️...\t" + new Date());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("结束喝酒?...\t" + new Date());
        }
    }
    

    并发编程,一边吃饭一边喝酒总共用时5秒,比顺序编程更快,因为并发编程可以同时运行,而不必等前面的代码运行完之后才允许后面的代码

    这里写图片描述

    本示例主要启动3个线程,一个主线程main thread、一个吃饭线程(Thread-0)和一个喝酒线程(Thread-1),共三个线程, 三个线程并发切换着执行。main线程很快执行完,吃饭线程和喝酒线程会继续执行,直到所有线程(非守护线程)执行完毕,整个程序才会结束,main线程结束并不意味着整个程序结束。
    这里写图片描述

    • 顺序:代码从上而下按照固定的顺序执行,只有上一件事情执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。

    • 并行:多个操作同时处理,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做各自的事情,互不影响

    • 并发:将一个操作分割成多个部分执行并且允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个不想做了,再去做别的,做着做着可能也不想做了,又去干其它事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人在做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其它事情,有可能一件事做了N次才做完。

    通常一台电脑只有一个cpu,多个线程属于并发执行,如果有多个cpu,多线程并发执行有可能变成并行执行。
    这里写图片描述

    3. 多线程创建方式

    • 继承 Thread
    • 实现 Runable
    • 实现 Callable
    ①:继成java.lang.Thread, 重写run()方法
    public class Main {
        public static void main(String[] args) {
            new MyThread().start();
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    Thread 类

    package java.lang;
    public class Thread implements Runnable {
    	// 构造方法
    	public Thread(Runnable target);
    	public Thread(Runnable target, String name);
    	
    	public synchronized void start();
    }
    

    Runnable 接口

    package java.lang;
    
    @FunctionalInterface
    public interface Runnable {
        pubic abstract void run();
    }
    

    ②:实现java.lang.Runnable接口,重写run()方法,然后使用Thread类来包装

    public class Main {
        public static void main(String[] args) {
        	 // 将Runnable实现类作为Thread的构造参数传递到Thread类中,然后启动Thread类
            MyRunnable runnable = new MyRunnable();
            new Thread(runnable).start();
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
        }
    }
    

    可以看到两种方式都是围绕着Thread和Runnable,继承Thread类把run()写到类中,实现Runnable接口是把run()方法写到接口中然后再用Thread类来包装, 两种方式最终都是调用Thread类的start()方法来启动线程的。
    两种方式在本质上没有明显的区别,在外观上有很大的区别,第一种方式是继承Thread类,因Java是单继承,如果一个类继承了Thread类,那么就没办法继承其它的类了,在继承上有一点受制,有一点不灵活,第二种方式就是为了解决第一种方式的单继承不灵活的问题,所以平常使用就使用第二种方式

    其它变体写法:

    public class Main {
        public static void main(String[] args) {
            // 匿名内部类
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }).start();
    
            // 尾部代码块, 是对匿名内部类形式的语法糖
            new Thread() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());
                }
            }.start();
    
            // Runnable是函数式接口,所以可以使用Lamda表达式形式
            Runnable runnable = () -> {System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId());};
            new Thread(runnable).start();
        }
    }
    

    ③:实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread

    Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕

    public class Main {
        public static void main(String[] args) throws Exception {
        	 // 将Callable包装成FutureTask,FutureTask也是一种Runnable
            MyCallable callable = new MyCallable();
            FutureTask<Integer> futureTask = new FutureTask<>(callable);
            new Thread(futureTask).start();
    
            // get方法会阻塞调用的线程
            Integer sum = futureTask.get();
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
        }
    }
    
    
    class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
    
            int sum = 0;
            for (int i = 0; i <= 100000; i++) {
                sum += i;
            }
            Thread.sleep(5000);
    
            System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
            return sum;
        }
    }
    

    Callable 也是一种函数式接口

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    

    FutureTask

    public class FutureTask<V> implements RunnableFuture<V> {
    	// 构造函数
    	public FutureTask(Callable<V> callable);
    	
    	// 取消线程
    	public boolean cancel(boolean mayInterruptIfRunning);
    	// 判断线程
    	public boolean isDone();
    	// 获取线程执行结果
    	public V get() throws InterruptedException, ExecutionException;
    }
    

    RunnableFuture

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }
    

    三种方式比较:

    • Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
    • Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
    • Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
    • 当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程
    • Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现

    四:线程的状态

    1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
    2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
    3. 运行(running)状态: 执行run()方法
    4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
    5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

    这里写图片描述

    这里写图片描述

    这里写图片描述

    五:Thread常用方法

    Thread

    public class Thread implements Runnable {
        // 线程名字
        private volatile String name;
        // 线程优先级(1~10)
        private int priority;
        // 守护线程
        private boolean daemon = false;
        // 线程id
        private long tid;
        // 线程组
        private ThreadGroup group;
        
        // 预定义3个优先级
        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
        
        
        // 构造函数
        public Thread();
        public Thread(String name);
        public Thread(Runnable target);
        public Thread(Runnable target, String name);
        // 线程组
        public Thread(ThreadGroup group, Runnable target);
        
        
        // 返回当前正在执行线程对象的引用
        public static native Thread currentThread();
        
        // 启动一个新线程
        public synchronized void start();
        // 线程的方法体,和启动线程没毛关系
        public void run();
        
        // 让线程睡眠一会,由活跃状态改为挂起状态
        public static native void sleep(long millis) throws InterruptedException;
        public static void sleep(long millis, int nanos) throws InterruptedException;
        
        // 打断线程 中断线程 用于停止线程
        // 调用该方法时并不需要获取Thread实例的锁。无论何时,任何线程都可以调用其它线程的interruptf方法
        public void interrupt();
        public boolean isInterrupted()
        
        // 线程是否处于活动状态
        public final native boolean isAlive();
        
        // 交出CPU的使用权,从运行状态改为挂起状态
        public static native void yield();
        
        public final void join() throws InterruptedException
        public final synchronized void join(long millis)
        public final synchronized void join(long millis, int nanos) throws InterruptedException
        
        
        // 设置线程优先级
        public final void setPriority(int newPriority);
        // 设置是否守护线程
        public final void setDaemon(boolean on);
        // 线程id
        public long getId() { return this.tid; }
        
        
        // 线程状态
        public enum State {
            // new 创建
            NEW,
    
            // runnable 就绪
            RUNNABLE,
    
            // blocked 阻塞
            BLOCKED,
    
            // waiting 等待
            WAITING,
    
            // timed_waiting
            TIMED_WAITING,
    
            // terminated 结束
            TERMINATED;
        }
    }
    
    public static void main(String[] args) {
        // main方法就是一个主线程
    
        // 获取当前正在运行的线程
        Thread thread = Thread.currentThread();
        // 线程名字
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否存活
        boolean alive = thread.isAlive();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
    
        // Thread[name=main, id=1 ,priority=5 ,alive=true ,daemon=false]
        System.out.println("Thread[name=" + name + ", id=" + id + " ,priority=" + priority + " ,alive=" + alive + " ,daemon=" + daemon + "]");
    }
    
    0. Thread.currentThread()
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        // 线程名称
        String name = thread.getName();
        // 线程id
        long id = thread.getId();
        // 线程已经启动且尚未终止
        // 线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的
        boolean alive = thread.isAlive();
        // 线程优先级
        int priority = thread.getPriority();
        // 是否守护线程
        boolean daemon = thread.isDaemon();
        
        // Thread[name=main,id=1,alive=true,priority=5,daemon=false]
        System.out.println("Thread[name=" + name + ",id=" + id + ",alive=" + alive + ",priority=" + priority + ",daemon=" + daemon + "]");
    }
    
    1. start() 与 run()
    public static void main(String[] args) throws Exception {
       new Thread(()-> {
           for (int i = 0; i < 5; i++) {
               System.out.println(Thread.currentThread().getName() + " " + i);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-A").start();
    
       new Thread(()-> {
           for (int j = 0; j < 5; j++) {
               System.out.println(Thread.currentThread().getName() + " " + j);
               try { Thread.sleep(200); } catch (InterruptedException e) { }
           }
       }, "Thread-B").start();
    }
    

    start(): 启动一个线程,线程之间是没有顺序的,是按CPU分配的时间片来回切换的。
    这里写图片描述

    public static void main(String[] args) throws Exception {
        new Thread(()-> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + " " + i);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-A").run();
    
        new Thread(()-> {
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName() + " " + j);
                try { Thread.sleep(200); } catch (InterruptedException e) { }
            }
        }, "Thread-B").run();
    }
    

    注意:执行结果都是main主线程
    这里写图片描述

    run(): 调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。

    有些面试官经常问一些启动一个线程是用start()方法还是run()方法,为了面试而面试。

    2. sleep() 与 interrupt()
    public static native void sleep(long millis) throws InterruptedException;
    public void interrupt();
    

    sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。

    • sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
    • interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
    public static void main(String[] args) throws Exception {
        Thread thread0 = new Thread(()-> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,让我睡10秒,中间有事叫我,zZZ。。。");
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要继续干活了");
            }
        });
        thread0.start();
    
        // 这里睡眠只是为了保证先让上面的那个线程先执行
        Thread.sleep(2000);
    
        new Thread(()-> {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,别睡了,起来干活了!!!");
            // 无需获取锁就可以调用interrupt
            thread0.interrupt();
        }).start();
    }
    

    这里写图片描述

    3. wait() 与 notify()

    wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()

    • wait(): 导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
    • notify(): 该方法只能在同步方法或同步块内部调用, 随机选择一个(注意:只会通知一个)在该对象上调用wait方法的线程,解除其阻塞状态
    • notifyAll(): 唤醒所有的wait对象

    注意:

    • Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部
    • 让哪个对象等待wait就去通知notify哪个对象,不要让A对象等待,结果却去通知B对象,要操作同一个对象

    Object

    public class Object {
    	public final void wait() throws InterruptedException;
    	public final native void wait(long timeout) throws InterruptedException;
    	public final void wait(long timeout, int nanos) throws InterruptedException;
    	
    	
    	public final native void notify();
    	public final native void notifyAll();
    }
    

    WaitNotifyTest

    public class WaitNotifyTest {
        public static void main(String[] args) throws Exception {
            WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    waitNotifyTest.printFile();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }).start();
    
            new Thread(() -> {
                try {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡觉1秒中,目的是让上面的线程先执行,即先执行wait()");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                waitNotifyTest.notifyPrint();
            }).start();
        }
    
        private synchronized void printFile() throws InterruptedException {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
            this.wait();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
        }
    
        private synchronized void notifyPrint() {
            this.notify();
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
        }
    }
    
    

    这里写图片描述

    wait():让程序暂停执行,相当于让当前,线程进入当前实例的等待队列,这个队列属于该实例对象,所以调用notify也必须使用该对象来调用,不能使用别的对象来调用。调用wait和notify必须使用同一个对象来调用。
    这里写图片描述

    this.notifyAll();
    这里写图片描述

    4. sleep() 与 wait()
    ① Thread.sleep(long millis): 睡眠时不会释放锁
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    try { Thread.sleep(1000); } catch (InterruptedException e) { }
                }
            }
        }).start();
    
        Thread.sleep(1000);
    
        new Thread(() -> {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                }
            }
        }).start();
    }
    

    因main方法中Thread.sleep(1000)所以上面的线程Thread-0先被执行,当循环第一次时就会Thread.sleep(1000)睡眠,因为sleep并不会释放锁,所以Thread-1得不到执行的机会,所以直到Thread-0执行完毕释放锁对象lock,Thread-1才能拿到锁,然后执行Thread-1;
    这里写图片描述

    5. wait() 与 interrupt()

    wait(): 方法的作用是释放锁,加入到等待队列,当调用interrupt()方法后,线程必须先获取到锁后,然后才抛出异常InterruptedException 。注意: 在获取锁之前是不会抛出异常的,只有在获取锁之后才会抛异常

    所有能抛出InterruptedException的方法都可以通过interrupt()来取消的

    public static native void sleep(long millis) throws InterruptedException;
    public final void wait() throws InterruptedException;
    public final void join() throws InterruptedException;
    public void interrupt()

    notify()和interrupt()
    从让正在wait的线程重新运行这一点来说,notify方法和intterrupt方法的作用有些类似,但仍有以下不同之处:

    • notify/notifyAll是java.lang.Object类的方法,唤醒的是该实例的等待队列中的线程,而不能直接指定某个具体的线程。notify/notifyAll唤醒的线程会继续执行wait的下一条语句,另外执行notify/notifyAll时线程必须要获取实例的锁

    • interrupte方法是java.lang.Thread类的方法,可以直接指定线程并唤醒,当被interrupt的线程处于sleep或者wait中时会抛出InterruptedException异常。执行interrupt()并不需要获取取消线程的锁。

    • 总之notify/notifyAll和interrupt的区别在于是否能直接让某个指定的线程唤醒、执行唤醒是否需要锁、方法属于的类不同

    6. interrupt()

    有人也许认为“当调用interrupt方法时,调用对象的线程就会InterruptedException异常”, 其实这是一种误解,实际上interrupt方法只是改变了线程的“中断状态”而已,所谓中断状态是一个boolean值,表示线程是否被中断的状态。

    public class Thread implements Runnable {
    	public void interrupt() {
    		中断状态 = true;
    	}
    	
    	// 检查中断状态
    	public boolean isInterrupted();
    	
    	// 检查中断状态并清除当前线程的中断状态
    	public static boolean interrupted() {
    		// 伪代码
    		boolean isInterrupted = isInterrupted();
    		中断状态 = false;
    	}
    }	
    

    假设Thread-0执行了sleep、wait、join中的一个方法而停止运行,在Thread-1中调用了interrupt方法,此时线程Thread-0的确会抛出InterruptedException异常,但这其实是sleep、wait、join中的方法内部会对线程的“中断状态”进行检查,如果中断状态为true,就会抛出InterruptedException异常。假如某个线程的中断状态为true,但线程体中却没有调用或者没有判断线程中断状态的值,那么线程则不会抛出InterruptedException异常。

    isInterrupted() 检查中断状态
    若指定线程处于中断状态则返回true,若指定线程为非中断状态,则反回false, isInterrupted() 只是获取中断状态的值,并不会改变中断状态的值。

    interrupted()
    检查中断状态并清除当前线程的中断状态。如当前线程处于中断状态返回true,若当前线程处于非中断状态则返回false, 并清除中断状态(将中断状态设置为false), 只有这个方法才可以清除中断状态,Thread.interrupted的操作对象是当前线程,所以该方法并不能用于清除其它线程的中断状态。

    interrupt()与interrupted()

    • interrupt():打断线程,将中断状态修改为true
    • interrupted(): 不打断线程,获取线程的中断状态,并将中断状态设置为false

    这里写图片描述

    public class InterrupptTest {
        public static void main(String[] args) {
            Thread thread = new Thread(new MyRunnable());
            thread.start();
            boolean interrupted = thread.isInterrupted();
            // interrupted=false
            System.out.println("interrupted=" + interrupted);
    
            thread.interrupt();
    
            boolean interrupted2 = thread.isInterrupted();
            // interrupted2=true
            System.out.println("interrupted2=" + interrupted2);
    
            boolean interrupted3 = Thread.interrupted();
            // interrupted3=false
            System.out.println("interrupted3=" + interrupted3);
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    // InterruptedException	false
                    System.out.println("InterruptedException\t" + Thread.currentThread().isInterrupted());
                }
            }
        }
    }
    
    

    这里写图片描述

    ② object.wait(long timeout): 会释放锁
    public class SleepWaitTest {
        public static void main(String[] args) throws InterruptedException {
            SleepWaitTest object = new SleepWaitTest();
    
            new Thread(() -> {
                synchronized (object) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
                    try {
                        object.wait(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印结束。。。");
                }
            }).start();
    
    		 // 先上面的线程先执行
            Thread.sleep(1000);
    
            new Thread(() -> {
                synchronized (object) {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                    }
                }
            }).start();
        }
    }
    

    因main方法中有Thread.sleep(1000)所以上面的线程Thread-0肯定会被先执行,当Thread-0被执行时就拿到了object对象锁,然后进入wait(5000)5秒钟等待,此时wait释放了锁,然后Thread-1就拿到了锁就执行线程体,Thread-1执行完后就释放了锁,当等待5秒后Thread-0就能再次获取object锁,这样就继续执行后面的代码。wait方法是释放锁的,如果wait方法不释放锁那么Thread-1是拿不到锁也就没有执行的机会的,事实是Thread-1得到了执行,所以说wait方法会释放锁

    这里写图片描述

    ③ sleep与wait的区别
    • sleep在Thread类中,wait在Object类中
    • sleep不会释放锁,wait会释放锁
    • sleep使用interrupt()来唤醒,wait需要notify或者notifyAll来通知
    5.join()

    让当前线程加入父线程,加入后父线程会一直wait,直到子线程执行完毕后父线程才能执行。当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。

    将某个线程加入到当前线程中来,一般某个线程和当前线程依赖关系比较强,必须先等待某个线程执行完毕才能执行当前线程。一般在run()方法内使用

    join() 方法:

    public final void join() throws InterruptedException {
            join(0);
    }
    
    
    public final synchronized void join(long millis) throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
        	 // 循环检查线程的状态是否还活着,如果死了就结束了,如果活着继续等到死
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    
    
    public final synchronized void join(long millis, int nanos) throws InterruptedException {
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    
        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }
    
        join(millis);
    }
    
    

    JoinTest

    public class JoinTest {
        public static void main(String[] args) {
            new Thread(new ParentRunnable()).start();
        }
    }
    
    class ParentRunnable implements Runnable {
        @Override
        public void run() {
            // 线程处于new状态
            Thread childThread = new Thread(new ChildRunable());
            // 线程处于runnable就绪状态
            childThread.start();
            try {
                // 当调用join时,parent会等待child执行完毕后再继续运行
                // 将某个线程加入到当前线程
                childThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父线程 running");
            }
        }
    }
    
    class ChildRunable implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try { Thread.sleep(1000); } catch (InterruptedException e) {}
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子线程 running");
            }
        }
    }
    
    

    程序进入主线程,运行Parent对应的线程,Parent的线程代码分两段,一段是启动一个子线程,一段是Parent线程的线程体代码,首先会将Child线程加入到Parent线程,join()方法会调用join(0)方法(join()方法是普通方法并没有加锁,join(0)会加锁),join(0)会执行while(isAlive()) { wait(0);} 循环判断线程是否处于活动状态,如果是继续wait(0)知道isAlive=false结束掉join(0), 从而结束掉join(), 最后回到Parent线程体中继续执行其它代码。

    在Parent调用child.join()后,child子线程正常运行,Parent父线程会等待child子线程结束后再继续运行。
    这里写图片描述

    • join() 和 join(long millis, int nanos) 最后都调用了 join(long millis)。

    • join(long millis, int nanos)和join(long millis)方法 都是synchronized。

    • join() 调用了join(0),从源码可以看到join(0)不断检查当前线程是否处于Active状态。

    • join() 和 sleep() 一样,都可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了wait(),会出让锁,而 sleep() 会一直保持锁。

    6. yield()

    交出CPU的执行时间,不会释放锁,让线程进入就绪状态,等待重新获取CPU执行时间,yield就像一个好人似的,当CPU轮到它了,它却说我先不急,先给其他线程执行吧, 此方法很少被使用到,

    /**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();
    

    这里写图片描述

    public static void main(String[] args) {
        new Thread(new Runnable() {
            int sum = 0;
            @Override
            public void run() {
                long beginTime=System.currentTimeMillis();
                for (int i = 0; i < 99999; i++) {
                    sum += 1;
                    // 去掉该行执行用2毫秒,加上271毫秒
                    Thread.yield();
                }
                long endTime=System.currentTimeMillis();
                System.out.println("用时:"+ (endTime - beginTime) + " 毫秒!");
            }
        }).start();
    }
    

    sleep(long millis) 与 yeid()

    • sleep(long millis): 需要指定具体睡眠的时间,不会释放锁,睡眠期间CPU会执行其它线程,睡眠时间到会立刻执行
    • yeid(): 交出CPU的执行权,不会释放锁,和sleep不同的时当再次获取到CPU的执行,不能确定是什么时候,而sleep是能确定什么时候再次执行。两者的区别就是sleep后再次执行的时间能确定,而yeid是不能确定的
    • yield会把CPU的执行权交出去,所以可以用yield来控制线程的执行速度,当一个线程执行的比较快,此时想让它执行的稍微慢一些可以使用该方法,想让线程变慢可以使用sleep和wait,但是这两个方法都需要指定具体时间,而yield不需要指定具体时间,让CPU决定什么时候能再次被执行,当放弃到下次再次被执行的中间时间就是间歇等待的时间
    7. setDaemon(boolean on)

    线程分两种:

    • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。
    • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程执行结束,子线程仍然继续执行,主线程和用户线程的生命周期各自独立。");
    }
    

    这里写图片描述

    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                IntStream.range(0, 5).forEach(i -> {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\ti=" + i);
                });
            }
        };
        thread.setDaemon(true);
        thread.start();
    
    
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "\ti=" + i);
        }
        System.out.println("主线程死亡,子线程也要陪着一块死!");
    }
    

    这里写图片描述

    六 线程组

    可以对线程分组,分组后可以统一管理某个组下的所有线程,例如统一中断所有线程

    public class ThreadGroup implements Thread.UncaughtExceptionHandler {
        private final ThreadGroup parent;
        String name;
        int maxPriority;
        
        Thread threads[];
        
        private ThreadGroup() {
            this.name = "system";
            this.maxPriority = Thread.MAX_PRIORITY;
            this.parent = null;
        }
        
        public ThreadGroup(String name) {
            this(Thread.currentThread().getThreadGroup(), name);
        }
        
        public ThreadGroup(ThreadGroup parent, String name) {
            this(checkParentAccess(parent), parent, name);
        }
        
        // 返回此线程组中活动线程的估计数。 
        public int activeGroupCount();
        
        // 中断此线程组中的所有线程。
        public final void interrupt();
    }
    
    public static void main(String[] args) {
        String mainThreadGroupName = Thread.currentThread().getThreadGroup().getName();
        System.out.println(mainThreadGroupName);
        // 如果一个线程没有指定线程组,默认为当前线程所在的线程组
        new Thread(() -> { }, "my thread1").start();
    
        ThreadGroup myGroup = new ThreadGroup("MyGroup");
        myGroup.setMaxPriority(5);
    
        Runnable runnable = () -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
            String groupName = threadGroup.getName();
            ThreadGroup parentGroup = threadGroup.getParent();
            String parentGroupName = parentGroup.getName();
            ThreadGroup grandpaThreadGroup = parentGroup.getParent();
            String grandpaThreadGroupName = grandpaThreadGroup.getName();
            int maxPriority = threadGroup.getMaxPriority();
            int activeCount = myGroup.activeCount();
    
            // system <- main <- MyGroup(1) <- my thread2
            System.out.println(MessageFormat.format("{0} <- {1} <- {2}({3}) <- {4}",
                    grandpaThreadGroupName,
                    parentGroupName,
                    groupName,
                    activeCount,
                    Thread.currentThread().getName()));
        };
    
        new Thread(myGroup, runnable, "my thread2").start();
    }
    

    线程组与线程组之间是有父子关系的,自定义线程组的父线程组是main线程组,main线程组的父线程组是system线程组。
    这里写图片描述

    展开全文
  • 【Linux】Linux进程的创建与管理

    万次阅读 多人点赞 2018-07-27 19:21:29
    在Linux系统中,除了系统启动之后的第一个进程由系统来创建,其余的进程都必须由已存在的进程来创建新创建的进程叫做子进程,而创建子进程的进程叫做父进程。那个在系统启动及完成初始化之后,Linux自动创建的进程...
  • Java对象创建过程

    千次阅读 2022-03-28 17:54:13
    java对象创建过程、对象的组成、对象头、实例数据、对齐填充、对象创建方式、new关键字、Class类的newInstance方法、Constructor类的newInstance方法、Object类的clone方法、反序列化、无父类的对象创建、有父类的...
  • 在之前的文章中对进程的一些基础概念,进程的生命周期和python进程操作的模块做了说明,本篇文章直接上代码,结束python中创建多进程的一些方法。 os.fork()(Linux) fork()函数,只在Linux系统下存在。而且它...
  • MySQL 表关联一对查询取最新的一条数据

    万次阅读 多人点赞 2019-01-23 18:10:27
    MySQL 表关联一对查询取最新的一条数据 遇到的问题 表关联一对查询取最新的一条数据,数据出现重复 由于历史原因,表结构设计不合理;产品告诉我说需要导出客户信息数据,需要导出客户的 所属行业,纳税...
  • 在启动类加上@enableasync Thread,Runable,Callable/Future ------------------------继承Thread类创建线程--------------------- 通过继承Thread类来创建并启动线程的一般步骤如下 1】定义Thread类的子类,并...
  • 如何给系统添加的用户? 如何给用户设置的密码? 如何锁定一个用户? 如何解锁一个用户? openEuler是一个用户的操作系统,所有要使用系统资源的用户需要先向系统管理员申请一个账号,之后用此账号进入系统;...
  • 一个进程最多可以创建多少个线程?

    千次阅读 多人点赞 2021-07-15 09:19:09
    因为不同的操作系统和不同位数的操作系统,虚拟内存可能是不一样。 Windows 系统我不了解,我就说说 Linux 系统。 在 Linux 操作系统中,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的系统,...
  • Access创建表/新建表

    千次阅读 2019-02-14 20:36:22
    这里我们不会去讲深,像表的结构,什么E-R图啊,关系啊,吧啦吧啦之类的,这里我们只讲创建表,相信很接触Access的人都不是专业的开发人员,讲的专业,大家看起来也累。这里我直接用Access2016的截...
  • 一、前言 本专栏主要分享测试需要掌握及面试常问的mysql语句相关知识:常用的sql语句、关键字查询、连接查询,索引等。 二、前期准备 ...5.我们需要准备一些班级数据和学生数据,在创建的数据库中新建一个查
  • 二、Linux下线程的创建

    千次阅读 2019-04-16 14:54:49
    (一)线程的创建 1.线程的创建函数 pthread_create函数 函数简介  pthread_create是UNIX环境创建线程函数 头文件  #include<pthread.h> 函数声明  int pthread_create(pthread_t *restrict tidp,const ...
  • MySQL创建表和约束条件(四)

    千次阅读 多人点赞 2019-11-15 14:09:16
    古语有云: 万恶淫为首,百善孝为先。 我们后辈当自勉。 上一章简单介绍了 MySQL的数据类型(三),如果没有看过,请观看上一章 ...在创建表时,一定要在 database 里面进行创建, 既先使用 use 数据库名来选择...
  • MySQL数据库面试题(2020最新版)

    万次阅读 多人点赞 2020-03-10 17:20:40
    记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。 mixed,一种折中的方案,普通操作使用statement记录,当...
  • 创建局域网Git服务器

    千次阅读 2018-11-06 17:09:27
    本文提供的方法适用于小团队在局域网内通过git协调...此命令执行过程中会提示输入账户的 登录密码,同时自动创建用户主目录/home/git,以及执行其他必要的工作。 该用户不一定非叫git,任意合法名称都行,例如 ...
  • SQL创建

    万次阅读 2019-06-25 17:22:05
    使用SQL创建学生管理表 随笔记录(勿喷) 01 用语句创建表 打开SQL数据库,在数据库点右键创建student库,接着新建查询(这里使用新建查询) 前面已经创建过库,在student的基础上创建表先输入use [student] go 如...
  • SQL篇-创建视图

    千次阅读 2021-05-16 17:19:57
    目录 基本语法 案例 详细作用 作用一 作用二 ...可以使用 CREATE VIEW 语句来创建视图,语法格式如下: ...:指定创建视图的 SELECT 语句,可用于查询个基础表或源视图。 案例 针对actor表创建
  • IntelliJ IDEA使用教程 (总目录篇) ...然后我就示范一下,如何使用这个IntelliJ IDEA 创建一个Java web 项目的hello world项目。 因为maven的中央仓库不一定是国产的,所以,你在跟我测试的时候,可能...
  • MySQL创建及管理数据库

    万次阅读 多人点赞 2018-08-12 12:46:09
    (1)命令行中连接mysql服务器 # mysql -u root -p ...在连接 MySQL 服务后,使用 create 命令创建数据库,语法如下: CREATE DATABASE 数据库名; 以下命令创建了一个数据名为RUNOOB的数据库: CRE...
  • iOS 开发者开证书创建流程

    万次阅读 2018-04-28 16:41:16
    在上面个的两大证书下又有很小证书如:推送证书(分为开发和发布两种,类型分别为APNs Development ios,APNs Distribution ios),该证书在appID配置中创建生成,和开发者证书一样,安装到开发电脑上; 2、appID,这...
  • MySQL索引、视图创建与管理操作实验

    万次阅读 多人点赞 2019-12-03 23:25:17
    实验4:索引、视图创建与管理操作实验 一、实验目的: 理解索引的概念与类型。 掌握创建、更改、删除索引的方法。 掌握维护索引的方法。 理解视图的概念。 掌握创建、更改、删除视图的方法。 掌握使用视图来访问...
  • JAVA生成UUID并作为数据库表的ID

    千次阅读 2019-06-30 00:13:38
    在接触UUID之前,我建表用的ID一直是用的int型,然后自动增长,这样很方便。 但是这样做却有一些问题,因为数据量大的话,不可能只用一张表,而是几张表,这样会出现id重复,于是有了UUID。 UUID 是 通用唯一识别...
  • SparkSQL,创建表,查询数据

    千次阅读 2019-11-29 10:36:55
    Spark SQL的前身是Shark,Shark是伯克利实验室Spark生态环境的组件之一,它能运行在Spark引擎上,从而使得SQL查询的速度得到10-100倍的提升,但是,随着Spark的发展,由于Shark对于Hive的太多依赖(如采用Hive的语法...
  • mysql 中创建自增的序列(Sequence)

    万次阅读 2017-10-09 09:28:56
    需求:业务开发一个时间轴功能,时间轴上展示个表的数据,时间轴滚动刷新。问题: 获取个表中的前几条数据比较麻烦,需要将个表的数据拿出来排序然后limit 取数据。这样表取数据如果数据量大很麻烦, 比如...
  • 1.Kubernetes StorageClass 介绍 Kubernetes 集群存储 PV 支持 Static ...静态配置方式,集群管理员必须手动调用云/存储服务提供商的接口来配置的固定大小的 Image 存储卷,然后创建 PV 对象以在 Kubernetes 中请...
  • 查询数量的时候也不至于报错。博主本地 sql 限制长度是 50M ,一次插 5W 条数据没问题,相应的 in 查询 5W 也不是问题哈哈。 mysql批量插入数据,一次插入多少行数据效率最高? 2、创建表和测试数据(50W) ...
  • 几乎所有的小伙伴都可以随口说几句关于创建索引的优缺点,也知道什么时候创建索引能够提高我们的查询性能,什么时候索引会更新,但是你有没有注意到,即使你设置了索引,有些时候索引他是不会生效的!这不仅考察了...
  • Java 基础高频面试题(2021年最新版)

    万次阅读 多人点赞 2021-03-31 23:39:26
    很多题目早已不是当前的热门题目,没有必要在这些题目上花太多时间。 很多答案放现在已经不准确,可能会误导新人。 因此,我花了几天时间整理了一些时下高频的 Java 基础题目,并反复斟酌,给出符合当前版本的解析...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 526,324
精华内容 210,529
关键字:

创建太多新的id