多任务执行的演变
玩电脑的时候,可以一边听歌,一边聊天,电脑可以同时做很多事,多个任务宏观上同时在执行。
- 单核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,还有一些守护线程,比如垃圾回收线程等等。