文章目录
线程
程序、进程、线程
概念
1、程序(program):为了完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
2、进程(process):正在执行的程序,从Windows角度讲,进程是含有内存和资源并安置线程的地方。
3、线程(thread):进程可进一步细化位线程,是一个进程内部的最小执行单元。简单理解就是进程中的一个任务,比如QQ中的一个聊天窗口就可以理解成一个线程。
线程和进程的关系
1、一个进程包含有多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行。
2、每一个进程至少包含一个线程(即主线程);在主线程中开始执行程序,java程序的入口main()方法就是在主线程中被执行的。
3、在主线程中可以创建并启动其它线程。
4、一个进程内的所有线程共享该进程的内存资源。
多线程的概念
1、多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
2、何时需要多线程
(1)程序需要同时执行两个或多个任务;
(2)程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等;
(3)需要一些后台运行的程序时。
3、多线程的好处
(1)提高程序的响应;
(2)提高CPU的利用率;
(3)改善程序结构,将复杂任务分为多个线程,独立运行。
4、多线程的不利方面
(1)线程也是程序,所以线程需要占用内存,线程越多占用的内存就越多。
(2)多线程需要协调和管理,所以需要CPU跟踪线程;
(3)线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。
创建线程
创建线程有两种方式,一种是继承Thread类的方式,另一种是实现Runnable接口的方式。
继承Thread类
1、在Java中要实现线程,最简单的方法就是扩展Thread类,重写其中的run方法,方法原型如下:
//创建
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//以下代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(143);
p.start();
2、Thread类中的run方法本身并不执行任何操作,如果我们重写了一个run方法,当线程启动时,它将执行run方法。
代码示例—创建一个线程,随机性的打印结果。
public class Preview1 {
/*这是一个程序入口*/
public static void main(String[] args) {
//创建thread类的子类对象
PrimeThread p = new PrimeThread();
//调用thread类中的方法start方法,开启新的线程,执行run方法
/*
* start()方法:开启新的线程,JVM执行run方法
* 结果是两个线程并发地运行
* 多次启动一个线程是非法的,特别是当线程已经完成任务后,不能重新启动
*/
p.start();
//mian()方法或者说主线程会继续执行主线程中的代码
for (int i = 0; i < 10; i++) {
System.out.println("Main:"+i);
}
}
}
//创建一个Thread类的子类
class PrimeThread extends Thread {
//重写Thread类中的run方法,你让它做什么都可以写(即设置线程任务)
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("primethread:" + i);
}
}
}
代码示例的运行结果
Main:0
primethread:0
Main:1
primethread:1
Main:2
primethread:2
primethread:3
primethread:4
.....
Main:7
Main:8
Main:9
省略了中间的一部分(节省篇幅),但是很显然,上面代码示例的运行结果是无序的,证明CPU并不是按照我们所想的顺序去执行这一段代码。这里就要说到:java属于抢占式的调度,哪个线程优先级高,先执行哪个线程,同一个优先级,随机执行。
实现Runnable接口
1、java.lang.Runnable接口中仅仅只有一个抽象方法:
public void run();
2、实现Runnnable接口,实现run()方法可以创建线程。
3、Runnable接口的存在就是解决了java中不允许多继承的问题。
4、由于Runnable()接口中没有start()方法,因此不能直接运行,需要将其封装到Thread类中。
代码示例
public class Preview2 {
/*这是一个程序入口*/
public static void main(String[] args) {
//创建实现Runnable接口的子类的对象
MyThread m = new MyThread();
//将子类对象封装到一个线程中
Thread t = new Thread(m);
//调用Thread类中的start()方法
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("Main:"+i);
}
}
}
//创建一个实现Runnable接口的类
class MyThread implements Runnable {
//重写接口中的run方法,你让它做什么都可以写(即设置线程任务)
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyThread:"+i);
}
}
}
图解多线程原理
以 继承Thread类 中的代码示例为图解样例
多线程的内存原理图
创建线程两种方式的区别
1、区别
(1)继承Thread类:线程代码放在Thread子类的run()方法中;
(2)实现Runable接口:线程代码存放在接口的子类的run方法中。
2、实现Runnable接口的好处
(1)避免了单继承的局限性,因为一个类只能继承一个类,如果类继承了Thread类就不能继承其它的类。
(2)增强了程序的可扩展性,降低了程序的耦合性,实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离,创建Thread类对象,调用start()方法就只用来开启新线程。
Thread类中的方法
构造方法
方法 | 说明 |
---|---|
Thread() | 创建一个线程 |
Thread(String name) | 创建一个指定名称的线程 |
Thread(Runnable target) | 利用Runnable对象创建一个线程 |
Thread(Runnable target,String name) | 利用Runnable对象创建一个线程,并命名 |
常用方法
返回值 | 方法名 | 功能 |
---|---|---|
void | start() | 启动线程 |
final void | setName(String name) | 设置线程名称 |
final String | getName() | 返回线程名称 |
final void | setPriority(int newPriority) | 设置线程优先级 |
final int | getPriority() | 返回线程优先级 |
final void | join() | 等待线程终止 |
sattic Thread | currentThread() | 返回正在执行的线程对象引用 |
static void | sleep(long millis) | 让当前正在执行的线程休眠,指定时间,单位ms |
代码示例
public class Preview3 {
/*这是一个程序入口*/
public static void main(String[] args) {
MyThreadDemo myThreadDemo = new MyThreadDemo();
myThreadDemo.start();//Thread-0 Thread[Thread-0,5,main]
MyThreadDemo myThreadDemo1 = new MyThreadDemo();
myThreadDemo1.start();//Thread-0 Thread[Thread-1,5,main]
//在构造方法中改名
MyThreadDemo myThreadDemo2 = new MyThreadDemo("狗子");
myThreadDemo2.start();//狗子 Thread[狗子,5,main]
MyThreadDemo1 my = new MyThreadDemo1();
my.start();
}
}
//创建一个集成Thread类的子类
class MyThreadDemo extends Thread{
public MyThreadDemo(){}
public MyThreadDemo(String name){
//传给父类,让父类改名字去
super(name);
}
@Override
public void run(){
//setName(String name)设置线程名称
// setName("子线程");
//getName()获取线程名称,返回String name
System.out.println(getName());
//currentThread()返回正在执行的线程对象引用
Thread thread = Thread.currentThread();
System.out.println(thread);
}
}
class MyThreadDemo1 extends Thread{
@Override
public void run(){
//模拟一个秒表
for (int i = 1; i <= 20; i++) {
System.out.println("第"+i+"秒");
//sleep(long millis)让当前正在执行的线程休眠,指定时间,单位ms,睡眠完成后继续执行
try {
Thread.sleep(1000);//1000毫秒==1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程优先级
概述
1、由于计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务。
2、优先级较高的线程有更多获得CPU的机会,即抢夺CPU的执行权。
3、优先级用整数表示,取值范围是1~10,数字越大代表优先级越高,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或者返回优先级。
4、调度策略
(1)时间片
(2)抢占式:高优先级的线程抢占CPU
5、Java的调度方法
(1)同优先级线程组成先进先出队列(先到先服务),使用时间片策略。
(2)对于高优先级而言,使用优先调度的抢占式策略。
6、线程优先级的缺陷——线程优先级高度依赖于系统,比如,在windows系统中,有7个优先级级别,但在java中有10个优先级级别,不能做到一一对应,这就意味着至少会有两个优先级共享相同的windows操作系统优先级,而在Oracle为Linux提供的java虚拟机中,线程的优先级直接被忽略,所有线程的优先级相同,因此,不要过度以来优先级。
线程优先级的设置
1、setPriority()更改此线程优先级。
2、getPriority()返回次线程优先级。
3、Thread类有如下三个静态常量来表示优先级
(1)MAX_PRIORITY:取值为10,表示最高优先级。
(2)MIN_PRIORITY:取值为1,表示最低优先级。
(3)NORM_PRIORITY:取值为5,表示默认的优先级。
代码示例
public class PriorityDemo{
/*这是一个程序入口*/
public static void main(String[] args) {
/*创建集成Thread类的线程对象*/
MyThread4 my = new MyThread4();
/*设置线程名称*/
my.setName("线程0");
/*设置线程优先级,低到高,1-10*/
my.setPriority(Thread.MIN_PRIORITY);
/*调用Thread类的start方法启动线程*/
my.start();
MyThread4 my1 = new MyThread4();
my1.setName("线程1");
my1.setPriority(6);
/*调用Thread类的start方法启动线程*/
my1.start();
MyThread4 my2 = new MyThread4();
my2.setName("线程2");
my2.setPriority(Thread.MAX_PRIORITY);
/*调用Thread类的start方法启动线程*/
my2.start();
/*主线程的任务,与另一线程并行执行*/
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
}
//创建一个集成Thread类的子类
class MyThread4 extends Thread {
//重写Thread类中的run方法,是需要执行的线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread()+":" + i);
}
}
}