Java_多线程(二十六)——多线程(运行原理、主线程、Thread类、创建线程的两种方式)、线程池(Runnable接口run方法、Callable接口call方法)

目录

第一章 多线程

1.1 多线程介绍

1.2 程序运行原理

1.2.1 抢占式调度详解

1.3 主线程

1.4 Thread类

1.5 创建线程方式一继承Thread类

1.5.1 继承Thread类原理

1.5.2 多线程的内存图解

1.5.3 获取线程名称

1.6 创建线程方式—实现Runnable接口

1.6.1 实现Runnable的原理

1.6.2  实现Runnable的好处

1.7 线程的匿名内部类使用

第二章 线程池

2.1 线程池概念

2.2 使用线程池方式--Runnable接口

2.3 使用线程池方式—Callable接口

2.4 线程池练习:返回两个数相加的结果

第三章 总结


第一章 多线程

1.1 多线程介绍

进程:进程指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。

 

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。

什么是多线程呢?即就是一个程序中有多个线程在同时执行。

区别单线程与多线程的不同:

  • 单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。
  • 多线程程序:即,若有多个任务可以同时执行。

 

1.2 程序运行原理

 1. 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

 2.抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

 

1.2.1 抢占式调度详解

大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核(单核)而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。

其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

 

1.3 主线程

jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程。

 

1.4 Thread类

Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。

 

1. 构造方法:

 

2. 常用方法

 

创建新线程有两种方法:

  • 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象,开启线程。run方法相当于其他线程的main方法。
  • 另一种方法是声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

 

1.5 创建线程方式一继承Thread类

创建线程的步骤:

1. 定义一个类继承Thread。

2. 重写run方法。

3. 创建子类对象,就是创建线程对象。

4. 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。

思考:线程对象调用 run方法和调用start方法区别?

线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程,并让jvm调用run方法在开启的线程中执行。

 

1.5.1 继承Thread类原理

1. 我们为什么要继承Thread类,并调用start方法才能开启线程呢?

继承Thread类:因为Thread类用来描述线程,具备线程应该有功能。那为什么不直接创建Thread类的对象呢?如下代码:

Thread t1 = new Thread();

t1.start();//这样做没有错,但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

 

2. 创建线程的目的是什么?

是为了建立程序单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定线程要执行的任务。

对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。

Thread类run方法中的任务并不是我们所需要的,只有重写这个run方法。既然Thread类已经定义了线程任务的编写位置(run方法),那么只要在编写位置(run方法)中定义任务代码即可。所以进行了重写run方法动作。

 

1.5.2 多线程的内存图解

多线程执行时,到底在内存中是如何运行的呢?

因为先入栈的后出栈,若都在一个栈内,会出现错误。

因此多线程执行时,在栈内存中,其实每个执行线程都有一片自己的栈内存空间。进行方法的压栈和弹栈。 

当执行线程的任务结束了,线程自动在栈内存中释放了。但是当所有的执行线程都结束了,那么进程就结束了。

 

1.5.3 获取线程名称

 开启的线程都会有自己的独立运行栈内存,那么这些运行的线程的名字是什么呢?该如何获取呢?既然是线程的名字,按照面向对象的特点,是哪个对象的属性和谁的功能,那么我们就去找那个对象就可以了。查阅Thread类的API文档发现有个方法是获取当前正在运行的线程对象。还有个方法是获取当前线程对象的名称。

  • Thread.currentThread()获取当前线程对象
  • Thread.currentThread().getName();获取当前线程对象的名称

为线程设置名字,建议子类对象调setName,在执行前设置名字比较合乎逻辑。

NameThread nt = new NameThread();
		nt.setName("旺财");//为线程设置名字,建议子类对象调setName,在开始前设置名字比较合乎逻辑
package day26.demo1;

/*
 *  每个线程,都有自己的名字
 *  运行方法main线程,名字就是"main"
 *  其他新键的线程也有名字,默认 "Thread-0","Thread-1"
 *  
 *  JVM开启主线程,运行方法main,主线程也是线程,是线程必然就是
 *  Thread类对象
 *  Thread类中,静态方法
 *   static Thread currentThread()返回正在执行的线程对象
 */
public class ThreadDemo1 {
	public static void main(String[] args) {
		NameThread nt = new NameThread();
		nt.setName("旺财");//为线程设置名字,建议子类对象调setName,在开始前设置名字比较合乎逻辑
		nt.start();
		
		/*Thread t =Thread.currentThread();
		System.out.println(t.getName());*/
		System.out.println(Thread.currentThread().getName());//main


	}
}
package day26.demo1;

/*
 *  获取线程名字,父类Thread方法
 *    String getName()
 */
public class NameThread extends Thread{
	
	public NameThread(){
		super("小强");// new NameThread()时名字改为小强,nt.setName("旺财"),又修改为旺财
	}
	
	public void run(){
		System.out.println(getName());//旺财
	}
}

运行结果:

进行多线程编程时,不要忘记了Java程序运行是从主线程开始,main方法就是主线程的线程执行内容。

 

1.6 创建线程方式—实现Runnable接口

创建线程的另一种方法是声明实现 Runnable 接口的类。该类先实现 run 方法,然后创建Runnable的子类对象,传入到某个线程的构造方法中,开启线程。

为何要实现Runnable接口,Runable是什么?

查看Runnable接口说明文档:Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法,需要由接口实现类重写该方法。

1. Thread类构造方法

 

2. 接口中的方法

 

创建线程的步骤。

1、定义类实现Runnable接口。

2、覆盖接口中的run方法。。

3、创建Thread类的对象

4、将Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5、调用Thread类的start方法开启线程。

 

1.6.1 实现Runnable的原理

为什么需要定一个类去实现Runnable接口呢?继承Thread类和实现Runnable接口有啥区别呢?

实现Runnable接口,避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法,将线程任务代码定义到run方法中。

创建Thread类的对象,只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。

 

1.6.2  实现Runnable的好处

第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。继承Thread类,线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务。实现runnable接口,将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。

 

1.7 线程的匿名内部类使用

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

方式1:创建线程对象时,直接重写Thread类中的run方法

//继承方式  XXX extends Thread{ public void run(){}}
		new Thread(){
			public void run(){
				System.out.println("!!!");
			}
		}.start();

方式2:使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法

//实现接口方式  XXX implements Runnable{ public void run(){}}
		
		Runnable r = new Runnable(){
			public void run(){
				System.out.println("###");
			}
		};
		new Thread(r).start();

方式3:将两者进行组合

new Thread(new Runnable(){
			public void run(){
				System.out.println("@@@");
			}
		}).start()

 

 

第二章 线程池

2.1 线程池概念

线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

我们详细的解释一下为什么要使用线程池?

在java中,如果每个请求到达就创建一个新线程(请求windows),开销是相当大的。

在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。

如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目。

线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。

 

2.2 使用线程池方式--Runnable接口

通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。        

Executors:线程池创建工厂类

public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。

ExecutorService:线程池类

Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行。

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

使用线程池中线程对象的步骤:

  • 创建线程池对象
  • 创建Runnable接口子类对象
  • 提交Runnable接口子类对象
  • 关闭线程池
public class ThreadPoolRunnable implements Runnable {
	public void run(){
		System.out.println(Thread.currentThread().getName()+" 线程提交任务");
	}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 *  JDK1.5新特性,实现线程池程序
 *  使用工厂类 Executors中的静态方法创建线程对象,指定线程的个数
 *   static ExecutorService newFixedThreadPool(int 个数) 返回线程池对象
 *   返回的是ExecutorService接口的实现类 (线程池对象)
 *   
 *   接口实现类对象,调用方法submit (Ruunable r) 提交线程执行任务
 *          
 */
public class ThreadPoolDemo {
	public static void main(String[] args) {
		//调用工厂类的静态方法,创建线程池对象
		//返回线程池对象,是返回的接口
		ExecutorService es = Executors.newFixedThreadPool(2);
	    //调用接口实现类对象es中的方法submit提交线程任务
		//将Runnable接口实现类对象,传递
		es.submit(new ThreadPoolRunnable());
		es.submit(new ThreadPoolRunnable());
		es.submit(new ThreadPoolRunnable());
	
	}
}

运行结果:

 

2.3 使用线程池方式—Callable接口

Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

ExecutorService:线程池类

<T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

使用线程池中线程对象的步骤:

  • 创建线程池对象
  • 创建Callable接口子类对象
  • 提交Callable接口子类对象
  • 关闭线程池

 

/*
 * Callable 接口的实现类,作为线程提交任务出现
 * 使用方法返回值
 */

import java.util.concurrent.Callable;

public class ThreadPoolCallable implements Callable<String>{
	public String call(){
		return "abc";
	}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/*
 *  实现线程程序的第三个方式,实现Callable接口方式
 *  实现步骤
 *    工厂类 Executors静态方法newFixedThreadPool方法,创建线程池对象
 *    线程池对象ExecutorService接口实现类,调用方法submit提交线程任务
 *    submit(Callable c)
 */
public class ThreadPoolDemo1 {
	public static void main(String[] args)throws Exception {
		ExecutorService es = Executors.newFixedThreadPool(2);
		//提交线程任务的方法submit方法返回 Future接口的实现类
		Future<String> f = es.submit(new ThreadPoolCallable());
		String s = f.get();
		System.out.println(s);
	}
}

 

2.4 线程池练习:返回两个数相加的结果

要求:通过线程池中的线程对象,使用Callable接口完成两个数求和操作

Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用

          V get() 获取Future对象中封装的数据结果

package day26.demo1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/*
 * 使用多线程技术,求和
 * 两个线程,1个线程计算1+100,另一个线程计算1+200的和
 * 多线程的异步计算
 */
public class ThreadPoolCallable {
	public static void main(String[] args)throws Exception {
		ExecutorService es = Executors.newFixedThreadPool(2);
		Future<Integer> f1 =es.submit(new GetSumCallable(100));
		Future<Integer> f2 =es.submit(new GetSumCallable(200));
		System.out.println(f1.get());
		System.out.println(f2.get());
		es.shutdown();
	}
}
package day26.demo1;

import java.util.concurrent.Callable;

public class GetSumCallable implements Callable<Integer>{
	private int a;
	public GetSumCallable(int a){
		this.a=a;
	}
	
	public Integer call(){
		int sum = 0 ;
		for(int i = 1 ; i <=a ; i++){
			sum = sum + i ;
		}
		return sum;
	}
}

 

因为往call()中传参,会报错,因此使用构造函数进行传参。

运行结果:

 

第三章 总结

创建线程的方式

 方式1,继承Thread线程类

  步骤

  1. 自定义类继承Thread类
  2. 在自定义类中重写Thread类的run方法
  3. 创建自定义类对象(线程对象)
  4. 调用start方法,启动线程,通过JVM,调用线程中的run方法

方式2,实现Runnable接口

  步骤

  1. 创建线程任务类 实现Runnable接口
  2. 在线程任务类中 重写接口中的run方法
  3. 创建线程任务类对象
  4. 创建线程对象,把线程任务类对象作为Thread类构造方法的参数使用
  5. 调用start方法,启动线程,通过JVM,调用线程任务类中的run方法

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值