“近期重新学了java多线程的知识,之前学过没怎么用,慢慢的也就忘得差不过了。这次就把多线程的基础知识在此记录一下~”
并发:一个CPU在同一时间段有多个程序在执行
并行:同一时刻有多个程序在执行,在多核(多个CPU)的计算机中可以实现
因为CPU运行的速度非常的快,所以这里的同一时间段和同一时刻都是针对于微观时间而言,我们人的感受上区别不出来这两个有什么区别。例如在单核的计算机中,我们仍然可以在同一时刻打开多个程序运行,但是其实是因为CPU快速的在多个程序中切换执行,让我们误以为是在“同时”执行,但是实际上单核CPU中每个时刻只能有一代码块在运行。
进程:指的是在计算机中已经运行的程序,也可以将之看成是内存中一块独立的程序运行空间。
例如我们打开一个客户端软件(有些软件同时开启多个进程),在IDEA中编译运行一个main方法等。这些都会开启一个进程,并在内存中划出一个独立的空间,让进程独立运行。因此一般情况下,进程中的数据是不会共享的。
进程是操作系统运行程序的一个基本单位,我们说系统在运行一个程序即是指一个进程从创建,运行,到被销毁的过程。
线程:进程是系统中程序运行的基本单位,而线程又是进程中程序执行的一个单元,用来负责进程中程序的执行,因此一个进程至少有一个线程。当一个进程中有多个线程在运行时,这个程序也就可以称之为多线程程序。
例如,在IDEA中编译运行一个main方法,在开启一个进程的同时,该进程里面也会开启一个线程,即main线程。当使用java中多线程技术时即可在该进程中开启多个线程。
可以将线程看成是CPU执行程序的一条路径(线)。
因此,可以将内存看成是进程的容器,而进程又是线程的容器。
PS:在windows10系统中,任务管理器里面即可查看当前电脑打开的进程
由于多线程程序在CPU中可以并发运行,但是CPU使用权在每个时刻只能被一个线程所占有,如果是按照一个程序执行完再执行另外一个程序的话,就会造成卡顿的现象。为了合理的安排各个线程轮流占用CPU的使用,就有了线程调度机制。
线程调度机制就是为多个线程分配CPU的使用权。
调度方式
分时调度
所有的线程轮流的获取CPU的使用权,平均分配每个线程占有CPU的使用时间。
抢占式调度
设置线程的优先级,让优先级高的线程优先占有CPU使用权,如果优先级相同的话就会随机选择一个线程执行,在java就是使用抢占式调度的方式。
java使用Thread类实例来创建一个线程,所有的线程对象都是Thread类的实例或是其子类的实例。Thread类继承Runnable接口,并实现该接口的唯一一个抽象方法run(),该方法里面的代码就是每个线程中执行的代码。就像刚开始执行程序时,会创建一个main线程,该线程执行的代码就是main方法里面的代码。而当我们在程序中另外创建一个线程时,其执行的就是run方法里面的代码了。
下面就来讲java中创建线程的几种方式
自定义线程类
public class MyThread extends Thread { public MyThread() {} /** * 带参构造,可以指定该线程的名称 */ public MyThread(String name) { super(name); //调用父类Thread的构造方法指定线程名称 } /** * 重写run方法 -- 必须 */ @Override public void run() { /* 线程执行的代码 */ } }创建实例
public class Test { public static void main(String[] args) { MyThread myThread = new MyThread("新线程名称"); //创建一个线程实例 myThread.start(); //开启新线程 -- 执行线程中run方法的内容 } }注意:要调用start方法才会自动执行run中的方法,并不是调用run方法来执行。
调用start()方法开启一个新线程的时候,并不是立刻就会执行该线程中run中的代码。因为java是采用抢占式调度的方式来分配CPU的使用,新创建的线程在不设置其优先级的情况下优先级是相同的。因此在调用start方法之后,该线程参与CPU使用权的抢夺,和main线程抢夺或者是和另外创建的线程抢夺,能不能抢夺到还是一回事,只有抢夺到了才会执行该线程对应的run方法。
构造方法
public Threa():无参构造,创建一个新的Thread对象
public Thread(Runnable target):传入Runnable接口实现类对象,在启动线程的时候调用target接口的run方法。
public Thread(String name):自定义线程名称
public Thread(Runnable target, String name):传入Runnable接口对象,并自定义线程名称
上述构造方法中传入Runnable接口实现类时需要重写该接口的方法,该接口只有run一个抽象方法,具体实现会在后面举例
常用的基础方法
public String getName():获取当前线程名称
main //main线程名称 Thread-0 //未设置名称时的线程名称public static void sleep(long millis):使当前正在执行的线程停止执行指定的毫秒数,时间一到就自动CPU就自动恢复到该线程继续执行下去。
public final void setPriority(int newPriority): 设置此此线程的优先级
public static Thread currentThread():获取当前正在执行的线程
public void start():开启一个新线程
public void run():在该方法中指定新线程要执行的代码
在Thread的构造方法中,有一个**public Thread(Runnable target)**的构造方法,即传入Runnable接口的实现类对象,重写里面的run方法创建一个新线程。值得注意的是,Thread类本身就是实现Runnable接口的,Runnable接口只有一个run方法(函数式接口)。
定义Runnable实现类接口 public class RunnableDemo implements Runnable{ @Override public void run() { //重写run方法 /* 线程执行代码 */ } } 创建实例 public static void main(String[] args) { Runnable run = new RunnableDemo(); Thread thread = new Thread(run); thread.start(); //注意最后要调用start方法才会开启该多线程 }上面创建Runnable接口实现类的时候可以发现,创建实现类仅仅是为了重写其run方法,如果要在线程中执行的代码有各种各样的话,就要创建各种各样的实现类,还只是重写其run方法,反倒有点复杂了。
而使用匿名内部类的方式可以更方便的创建一个Thread接口:
Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("这是一个匿名内部类"); } }); thread.start(); //也可以 new Thread(new Runnable() { @Override public void run() { System.out.println("这是一个匿名内部类"); } }).start();在JDK8中引入了Lambda表达式的新特性,有关于Lambda表达式会在以后中讲到,先来看看怎么用:
new Thread(()->{ /* 这里即可书写run方法中的内容 */ }).start();使用Thread和Runnable的区别
使用Runnable接口是很容易实现资源共享的,同时可以实现代码实现和线程相互独立后面的线程池中只能放入实现Runnable接口的类,不能直接放入Thread类。可以使用Lambda表达式,代码书写更加简便小结
进程是系统执行程序的一个基本单位,是一个静态的概念,相当于内存中的一块运行空间。线程是系统CPU调度的一个基本单位,是一个动态的概念,存在与进程中。创建线程的方式多种多样,使用匿名内部类或Lambda表达实现Runnable接口会更简洁。但是上述创建线程的方法在工作中应该基本不会用到,阿里的开发手册中就有提到:“线程资源必须通过线程提供,不允许在应用中显性的创建”。只是在学习过程中还是有必要知道最开始的创建方式~