(一)初识线程

多任务执行的演变

玩电脑的时候,可以一边听歌,一边聊天,电脑可以同时做很多事,多个任务宏观上同时在执行。

  • 单核CPU时代:
    • 一个任务的时候,在一个时间点只能执行一个任务。
    • 多个任务的时候,“同一个时间点”执行多个任务,并非真正意义的同个时间点,而是由操作系统不断切换时间片
      (不断切换任务占领CPU),让每个任务都有一定时间可以运行。由于这个时间片特别短,所以我们几乎感受不出来
  • 多核CUP时代:真正的不同线程可以被不同CPU核执行,也就是可能同时在操作一个内存数据,如果使用不好,
    这种行为导致结果可能是不可预测的。(一个线程A写操作改了数据,但是另一个线程B不知道A改了数据,
    读取出了这个数据,却发现不是自己想要的)

线程和进程的区别

  • 进程:操作系统资源分配的基本单位,打开电脑的任务管理器就可以看到很多进程,每一个程序(比如QQ)一般不止一个进程。

  • 线程:任务调度和执行的基本单位,一个进程可能有很多线程,一个有效的进程至少有一个线程。每一个线程都有自己的局部变量表,
    程序计数器(指向正在执行的指令指针)。

  • 进程有自己的内存地址空间,线程没有,被包含在进程的地址空间中。

  • 进程最少有一条线程,线程可以看成是轻量级的进程。

  • 本质区别:进程拥有自己的一整套变量,而线程则共享数据,撤销或者启动一个线程的成本更低。

为什么需要多线程

在平时下载东西的时候,我们可能需要同时做点其他东西,加入一个音乐软件只能有一个线程,那么下载的时候,我们就不能同时听音乐了,说更严重一些,如果下载的时候网路比较慢,阻塞了,我们想点击按钮让它停下来,也是不能的。

如果一个线程,我们模拟一下听歌又看书的情景,我们会发现永远不会输出看书。

public class TryConcurrent {
    public static void main(String[] args) {
        listenToMusic();
        readBook();
    }
    public static void listenToMusic(){
        for(;;){
            System.out.println("I am listening to music!");
        }
    }

    public static void readBook(){
        for(;;) {
            System.out.println("I am reading a book!");
        }
    }
}

输出结果:

I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
I am listening to music!
...

启动多线程

于是我们使用new Thread来创建一个新的线程,如下:

public class TryConcurrent {
    public static void main(String[] args) {
        listenToMusic();
        new Thread(new Runnable() {
            @Override
            public void run() {
                readBook();
            }
        }).start();
    }
    public static void listenToMusic(){
        for(;;){
            System.out.println("I am listening to music!");
        }
    }

    public static void readBook(){
        for(;;) {
            System.out.println("I am reading a book!");
        }
    }
}

但是这样也得不到我们想要的结果,也是会一直输出听音乐,这是为什么呢?明明后面启动了新的线程了。
上面代码通过匿名内部类的方式创建线程并且重写了run方法。
启动新线程必须在其中一个任务之前,因为启动线程本身是依赖于主线程的,也就是主线程的听音乐一直不结束的话,就永远执行不到新建线程那一步,自然也就不会有readbook输出。
其中,调用start方法,才是真正的派生了一个新的线程,否则只是一个简单的Thread对象,start方法是一个立即返回的方法,也就是不管线程里面逻辑多复杂,都是会执行后面的方法的,因为里面的逻辑归新创建的线程,而不是当前线程。
如果使用java 8的Lambda属性的话,可以直接写成new Thread(TryConcurrent::readBook).start();

在运行的时候,我们可以使用Jconsole来观察线程,只要在cmd输入Jconsole即可,或者Jstack也是可以的,都是由JDK提供的。

选择自己要监控的进程,点进去就可以看到线程页面的一些信息了。

其实我们可以看到并不只有main线程和自己新建的线程Thread-0,还有一些守护线程,比如垃圾回收线程等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章