多线程实现方式
程序,进程和线程
4.1运行的程序就是进程,一个进程可以有多个线程,如视频中同时听到声音,看图像,看弹幕等。
线程是CPU调度和执行的单位。Java里的main()函数就是主线程,用于执行整个程序。
程序运行时,即使没有创建线程,后台也会有多个线程,如主线程,gc线程(垃圾回收线程)。
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制,如抢票系统。
4.2对于Java程序来说,当Dos命令窗口中输入:java Helloworld回车之后,会先启动JVM JVM就是一个进程。JVM会在启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。
4.3进程和线程是什么关系?
阿里巴巴是一个进程,马云就是一个线程。
注意:进程A和进程B的内存独立不共享,Java语言中,线程A和线程B堆内存和方法区共享,但是栈不共享,一个线程一个栈。
多个线程互不干扰,各自执行各自的,这就是多线程并发。多线程并发可以提高效率。
4.4 使用多线程机制之后,main方法结束后,是不是有可能程序也不会结束。main方法结束,只代表主线程结束,还有可能有其他的栈(线程)在工作。
4.5对于多核CPU可以做到多线程并发,对于单核的CPU来说 真的可以做到多线程并发吗? 不能做到真正的多线程并发,但是给人一种多线程并发的感觉。电影院采用胶卷播放电影,当达到一定的速度时,给人一种播放动画的感觉,说明人的反应很慢,在这期间,计算机可以进行亿万次的循环,所以计算机计算速度很快。由于CPU处理速度很快,多个线程之间频繁切换执行,给人的感觉是多个事情同时在做。
线程A 播放音乐
线程B 播放视频
多个线程之间频繁切换,给人的感觉是两个线程在同时做。
什么是真正的多线程并发?线程之间各自执行,互不影响。单核的CPU只有一个大脑。
多线程实现方式
继承Thread类,重写run方法
start方法是在JVM中开辟一个新的栈空间,启动一个分支线程,这段代码任务完成之后瞬间结束这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start方法就结束了。启动成功的线程会自动调用run方法,并且run 方法在分支栈的栈底部(压栈)run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。
程序的输出结果有先有后,有多有少,这是因为控制台只有一个,这是因为占用cpu的时间片不同。
使用Thread.run()方法和Thread.start()方法的区别
使用run()方法会执行run()方法里面的程序,然后再执行main()方法,按照程序的顺序执行,只有主线程一条执行路径,属于单线程。
使用start()方法,多条执行路径,子线程和主线程交替执行,才是多线程。
实例:继承Thread类实现多线程下载图片
实现过程:自定义线程类继承Thread类。重写run()方法,编写线程执行体。在main()方法(主线程)里创建线程对象,调用start()方法执行。
package com.demo01; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.net.URL; //实现多线程下载图片 public class TestThread2 extends Thread { private String url; private String name; public TestThread2(String url, String name) { this.url = url; this.name = name; } // 下载图片线程的执行体 @Override public void run(){ WebDownloader webDownloader=new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了"+name); } public static void main(String[] args){ TestThread2 t1=new TestThread2("https://bkimg.cdn.bcebos.com/pic/a2cc7cd98d1001e93901dbb7d0416cec54e736d13971?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxNTA=,g_7,xp_5,yp_5","1.jpg"); TestThread2 t2=new TestThread2("https://bkimg.cdn.bcebos.com/pic/a9d3fd1f4134970a304e1196fd85c6c8a786c917f771?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxMTY=,g_7,xp_5,yp_5","2.jpg"); TestThread2 t3=new TestThread2("https://bkimg.cdn.bcebos.com/pic/9345d688d43f8794a4c23297ba5419f41bd5ad6e8c71?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxODA=,g_7,xp_5,yp_5","3.jpg"); t1.start(); t2.start(); t3.start(); } } //下载器 class WebDownloader{ public void downloader(String url,String name){ try { FileUtils.copyURLToFile(new URL(url),new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常"); } } }
实现Runnable接口
实现过程:定义MyRunnable类实现Runnable接口。实现run()方法,编写线程执行体。创建线程对象,调用start()方法启动线程。
public class TestThread4 implements Runnable{ private String url; private String name; public TestThread4(String url, String name) { this.url = url; this.name = name; } @Override public void run(){ WebDownloader webDownloader=new WebDownloader(); webDownloader.downloader(url,name); System.out.println("下载了"+name); } public static void main(String[] args){ TestThread2 t1=new TestThread2("https://bkimg.cdn.bcebos.com/pic/a2cc7cd98d1001e93901dbb7d0416cec54e736d13971?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxNTA=,g_7,xp_5,yp_5","1.jpg"); TestThread2 t2=new TestThread2("https://bkimg.cdn.bcebos.com/pic/a9d3fd1f4134970a304e1196fd85c6c8a786c917f771?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxMTY=,g_7,xp_5,yp_5","2.jpg"); TestThread2 t3=new TestThread2("https://bkimg.cdn.bcebos.com/pic/9345d688d43f8794a4c23297ba5419f41bd5ad6e8c71?x-bce-process=image/watermark,image_d2F0ZXIvYmFpa2UxODA=,g_7,xp_5,yp_5","3.jpg"); new Thread(t1).start(); new Thread(t2).start(); new Thread(t3).start(); // t1.start(); // t2.start(); // t3.start(); } }
小结
继承Thread类和实现Runnable接口的区别主要在于开启线程的方式不同。
继承Thread类 t1.start();
实现Runnable接口 new Thread(t1).start();
推荐使用实现Runnable接口,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用。