目录
进程:一个正在操作系统中运行的exe程序可以理解为一个进程,完全可以将运行在内存中的exe文件理解为进程-----进程就是受操作系统管理的基本运行单元。一个最简单的Java程序的运行也可以叫做一个进程。
所有的应用程序都是由CPU执行的,对于一个CPU而言,在某一个时间点只能运行一个程序,也就是说只能执行一个进程。操作系统会给每一个进程分配一段有限的cpu使用时间,cpu在这段时间中执行某个进程。由于CPU的执行速度非常快,能在极短的时间内在不同的进程之间进行切换,所以给人同时执行多个程序的感觉。
线程:在一个进程中,还可以有多个执行单元同时运行,来同时完成一个或多个程序任务。这些执行单元可以看作是一条条线索,被称为线程。
多线程技术的使用场景:(1)阻塞。一旦系统出现了阻塞现象,则可以根据实际情况来使用多线程技术提高开发效率。(2)依赖。业务如果分为2个执行过程,分别是A和B。当A业务发生阻塞时,B业务的执行不依赖于A业务的执行结果,这时就可以通过多线程来提高运行效率。如果B业务不依赖于A业务的结果,则不必使用多线程技术,A业务出结果后再运行B业务,按顺来就行。
并发:指两个或多个事件在同一个时间段内发生,同一时刻只能发生一件事。
并行:指两个或多个事件在同一时刻发生。(同时发生)
特别注意:Java程序属于优先抢占式地调度,哪个线程的优先级高,哪个线程就有可能优先执行。优先级相同时,就随机选择执行。优先级大小只是指CPU被优先执行的概率的大小,并不一定优先级大就一定先被执行。
Java为多线程开发提供了非常优秀的支持,在java中,可以通过以下三个方式来实现多线程:
Thread类是java.lang包下的一个线程类,用来实现Java多线程。其实步骤非常简单。如下
(1)创建一个Thread线程类的子类,同时重写Thread类的run()方法。
(2)创建该子类的实例对象,并通过调用start()方法来启动线程。
start()方法使线程开始执行,Java虚拟机调用该线程的run()方法。结果是程序的main入口方法和创建的线程并发的运行。多次启动同一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。
package demo01;
public class Test {
public static void main(String[] args) {
//创建MyThread1线程实例对象
MyThread1 thread1=new MyThread1("thread1");
thread1.start();
MyThread1 thread2=new MyThread1("thread2");
thread2.start();
}
}
//定义一个线程类继承自Thread类
class MyThread1 extends Thread{
//构造方法
public MyThread1(String name){
super(name);
}
//重写run()方法
@Override
public void run() {
int i=0;
while (i++<5){
//打印输出当时运行的线程的名字
System.out.println(Thread.currentThread().getName());
}
}
}
通过继承Thread类来实现多线程的方式有一些局限性,因为java只支持类的单继承。在这种情况下就可以考虑通过实现Runnable接口的方式来实现多线程。
步骤如下:
(1)创建一个Runnable接口的实现类,同时重写接口中的run()方法。
(2)创建Runnable接口的实现类对象。
(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。
(4)调用线程实例的start()方法来启动线程
package demo02;
public class test {
public static void main(String[] args) {
//(2)创建Runnable接口的实现类对象。
myThread mythread1=new myThread();
myThread mythread2=new myThread();
//(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。
Thread thread1=new Thread(mythread1,"thread1");
Thread thread2=new Thread(mythread2,"thread2");
//(4)调用线程实例的start()方法来启动线程
thread1.start();
thread2.start();
}
}
//(1)定义一个Runnable接口的实现类
class myThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"的run()方法正在运行");
}
}
通过Thread类和Runnable接口实现多线程时都要要重写run()方法。但是,该方法却没有返回值,因此无法从多个线程中获取返回值,但实际应用中肯定会用到返回值的。为了解决这个问题,从jdk5开始,java提供了一个Callable接口,来满足这种既能创建多线程,又能有返回值的需求。
通过这个方式实现多线程和Runnable的方式实现多线程差不多,都是通过Thread类的有参构造方法传入各自接口对象为参数来实现。只不过这里传入的不是Runnable对象了,而是Runnable类的子类FutureTask对象作为参数,而FutureTask对象中则封装带有返回值的Callable接口实现类。
实现步骤如下:
(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。
(2)创建实现了Callable接口的实现类对象。
(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
(5)调用线程实例的start()方法启动线程。
package demo03;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。
class MyThread implements Callable<Object>{
@Override
public Object call() throws Exception {
int i=0;
while (i++<5){
//打印输出当时运行的线程的名字
System.out.println(Thread.currentThread().getName());
}
return i;
}
}
public class test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//(2)创建实现了Callable接口的实现类对象。
MyThread mythread1=new MyThread();
//(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
FutureTask<Object> ft1=new FutureTask<>(mythread1);
//(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
Thread thread1=new Thread(ft1,"thread1");
//(5)调用线程实例的start()方法启动线程。
thread1.start();
//(2)创建实现了Callable接口的实现类对象。
MyThread mythread2=new MyThread();
//(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
FutureTask<Object> ft2=new FutureTask<>(mythread2);
//(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
Thread thread2=new Thread(ft2,"thread2");
//(5)调用线程实例的start()方法启动线程。
thread2.start();
System.out.println("thread1返回的结果为:"+ft1.get());
System.out.println("thread2返回的结果为:"+ft2.get());
}
}
FutureTask实现了RunnableFuture接口,RunnableFuture接口继承自Runnable接口和Future接口。所以FutureTask的本质是Runnable接口和Future接口的实现类。
Future接口的方法:
boolean cancel(boolean running)用于取消任务,running参数为是否取消还在运行的任务
boolean isCancelled()用于判断任务是否被取消成功
boolean isDone()判断任务是否已经完成
V get()用于获取执行结果(返回值),这个方法会发生阻塞,一直等到任务执行完才会执行结果。
V get(long timeout,TimeUnit unit)如果指定时间内没有得到结果就返回null
实现Runnable接口比继承Thread类所更具有的优势:
1.可以避免Java中的单继承的局限性
2.线程池只能放入实现Runnable或Callable类的线程,不能直接放入继承Thread类的线程
其它方面的对比,我通过下面一个多窗口售票的案例来对比分析更多优略:
4个窗口卖100张票,这100张票可以看作是共享资源。4个售票窗口可以看成是4个线程。
我们先用第1种方法(继承Thread类)来实现这个案例:
package demo04;
//定义一个继承自thread类的子类
class TicketWindow extends Thread{
private int tickets=100;
@Override
public void run() {
while (true){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
public class test01 {
public static void main(String[] args) {
//创建4个线程对象来作为窗口卖票
new TicketWindow().start();
new TicketWindow().start();
new TicketWindow().start();
new TicketWindow().start();
}
}
运行后会发现重复卖票了,也就是说票没有共享。同一张票被卖了四次。这很明显不是我们想要的答案吧。
这时候就要通过实现Runnable接口来实现多线程:
package demo04;
//定义一个实现了Runnable接口的实现类
class TicketWindow implements Runnable{
private int tickets=100;
@Override
public void run() {
while (true){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
}
}
}
}
public class test02 {
public static void main(String[] args) {
//创建TicketWindow实例对象
TicketWindow ticketWindow=new TicketWindow();
new Thread(ticketWindow,"窗口1").start();
new Thread(ticketWindow,"窗口2").start();
new Thread(ticketWindow,"窗口3").start();
new Thread(ticketWindow,"窗口4").start();
}
}
这次就可以共享资源了,不会出现票被重复卖的情况。
新创建的线程默认是前台线程,对于java程序来说,只要有前台线程在运行,这个进程就不会关闭。如果某个线程在启动之前(start()方法)调用了setDaemon(true)语句,这个线程就会变成一个后台线程。
java中,每次程序运行至少启动2个线程,1个是main线程,另一个是垃圾收集线程。因为每当执行一个java程序时,都会启动一个jvm,每一个jvm其实就是操作系统中启动了一个进程。