线程与进程
进程:
是指一个内存中运行的应用程序,每个进程有一个独立的内存空间
线程:
进程中的执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
线程实际上是进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
每个线程都有自己的栈空间,共用一份堆内存。
线程调度
分时调度
所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间
抢占式调度
优先让优先级高的线程使用cpu,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
抢占式调度
cpu使用抢占式调度模式在多个线程间进行高速切换,看起来像是同时做多件事情。多线程并不能提高程序的运行速度,但能提高运行的效率。
并发与并行
并发:可以是一个处理器或者是多个处理器,通过来回切换运行,使得看起来像是同时发生,实际上是两个或多个事件在同一个时间段发生。
并行:一个处理器以上,两个或多个事情在同一个时刻发生(同时发生)
多线程的实现
继承Thread,继承Thread的类则须重写其run方法,run方法的调用则是执行了一条新的路径,这个执行路径的触发方式,不是调用run,而是通过thread对象的start()方法来启动任务。
public class MyThread extends Thread{ /** * run方法是线程要执行任务的方法 */ public void run() { //这里的代码就是一条新的执行路径 //这个执行路径的触发方式,不是调用run,而是通过thread对象的start()方法来启动任务 for (int i=0;i<10;i++){ System.out.println("一条大河波浪宽"+i); } } }
public class Demo1 { public static void main(String[] args) { //一开始main函数就开启了一个线程 MyThread m = new MyThread(); m.start();//这个方法开启了第二个线程 for (int i=0;i<10;i++){ System.out.println("风吹稻花上两岸"+i); } } }
//还可以使用匿名内部类的方式实现多线程 public class Demo1 { new Thread(){ @Override public void run() { for (int i=0;i<10;i++){ System.out.println("一条大河波浪宽"+i); } } }.start(); for (int i=0;i<10;i++){ System.out.println("风吹稻花上两岸"+i); } }
实现接口Runnable
创建一个实现Runnable接口的类,创建它的一个对象(任务),new一个Thread对象把任务传进去。然后执行这个Thread的star方法。
public class MyRunnable implements Runnable{ @Override public void run() { for (int i=0;i<10;i++){ System.out.println("床前明月光"+i); } } }
public class Demo1 { public static void main(String[] args) { MyRunnable r = new MyRunnable(); //视作创建了一个任务 Thread t = new Thread(r); //交给多个线程去执行 t.start(); for (int i=0;i<10;i++){ System.out.println("疑是地上霜"+i); } } }
实现runnable与继承Thread相比有如下优势:
1、通过创建任务(也就是实现Runnable的类创建出来的对象),然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
2、可以避免单继承带来的局限性。(可以同时实现多个接口,但是只能继承一个父类)
3、任务与线程本身是分离的,提高了程序的健壮性。
4、后续学习的线程池技术,接受runnable类型的任务,不接受thread。
给线程设置名称
如果是Runnable则有两个方法
public static void main(String[] args) { new Thread(new MyRunnable(),"大河线程"); //一种 Thread t = new Thread(new MyRunnable()); t.setName("这是我父亲日记里的文字。"); //另一种 t.start(); } static class MyRunnable implements Runnable{ public void run(){ System.out.println(Thread.currentThread().getName()); } }
而若没有使用Runnable直接是继承Thread类的对象则是只有后一种方法,Thread对象.setName()方法。
public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("一条大河"); myThread.start(); } private static class MyThread extends Thread{ public void run(){ System.out.println(Thread.currentThread().getName()); } }
线程休眠
可以通过Thread.sleep(1000);括号里是毫秒,来让线程休眠1000毫秒后再执行。
线程阻塞
所有消耗时间的操作,比如读文件,或接受用户输入,都能算是线程阻塞,意味着现在线程处于暂停状态也没能去做其他事。
线程中断
线程中断的操作是给Thread线程对象打上标记(异常),然后在线程的run方法中设置catch来捕获这个异常,在catch代码块中用return可使得直接跳出这个run方法,结束这个线程,而也可以设置选择场景在catch中决定是否要中断(不中断则不做任何操作),实际的应用中,如果要中断也要在return前释放各种资源。
package com.test4_5; //线程的中断 //一个线程是个独立的执行路径,是否结束应该由自身决定。外部掐死总是一种能导致各种无可预料的错误, //而如果有需要中断,应该给线程打标记,线程特殊情况下会查看标记,如果有则会触发一个异常 //程序员可以设置try catch 来决定如何结束这个线程(或不结束)。 public class Demo4 { public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(new MyRunnable()); t1.start(); for(int i = 0;i<5;i++){ System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } //给线程t1添加中断标记,设置的catch中就能依照程序员的设计选择是否让它死亡。 t1.interrupt(); } private static class MyRunnable implements Runnable{ //下面的sleep可能会有异常,而在这个类中无法抛出异常,因为实现了Runnable接口,Runnable父接口 //都没有声明异常的抛出,子就不能抛出比父更大的异常。所以我们选择try catch @Override public void run() { for(int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { //e.printStackTrace(); //System.out.println("发现了终端标志,如果不想让它死亡就可以什么也不做。"); System.out.println("发现了终端标志,选择线程死亡则执行return。"); return; } } } } }
用户线程和守护线程
当一个进程不包含任何存活的用户线程时,进程结束。
1、什么是守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) ,直接创建的线程都是用户线程。
守护线程设置方式
Thread t1=new Thread(new MyRunnable()); //设置为守护线程需要在执行start之前 t1.setDaemon(true); t1.start();
The Java Virtual Machine exits when the only threads running are all daemon threads.
这句话来自 JDK 官方文档,意思是:
当 JVM 中不存在任何一个正在运行的非守护线程时,则 JVM 进程即会退出。
可以做个对比实验,设置一个用户线程,while(true)死循环执行,当主线程执行完毕退出时,jvm不会退出,因为要等待所有用户线程结束。
而这个用户线程设置为守护线程时,当主线程退出时,JVM 检测到没有其他用户线程在运行,会随之退出运行,守护线程同时也会被回收,即使你里面是个死循环也不碍事。
2、守护线程的作用及应用场景
上面,我们已经知道了,如果 JVM 中没有一个正在运行的非守护线程,这个时候,JVM 会退出。换句话说,守护线程拥有自动结束自己生命周期的特性,而非守护线程不具备这个特点。
JVM 中的垃圾回收线程就是典型的守护线程,如果说不具备该特性,会发生什么呢?
当 JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬了!!!由此可见,守护线程的重要性了。
通常来说,守护线程经常被用来执行一些后台任务,但是呢,你又希望在程序退出时,或者说 JVM 退出时,线程能够自动关闭,此时,守护线程是你的首选。
有几点需要注意:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把