计算机多线程的作用及意义
-
多线程和多进程
进程就是每一个正在进行的程序,进程是系统进行资源分配和调用的独立单位,每一个进程都拥有它自己独立的内存空间和系统资源。
一个程序最少需要一个进程,而一个进程最少需要一个线程。关系是线程–>进程–>程序的大致组成结构。所以线程是程序执行流的最小单位,而进程是系统进行资源分配和调度的一个独立单位。以下我们所有讨论的都是建立在线程基础之上。
-
多线程的设计意义
多线程的设计是为了提高CPU的利用效率。
如果学习过单片机的话,那么就会明白在单片机中CPU是通过中断来调用的,如果有需求CPU才会去处理问题,然而一般来说处理一个需求所需要的时间是极短的,如果采用单线程设计那么大部分的时间CPU都是处于等待需求的状态,只有当这个任务结束之后,才能够去处理下一个任务,这样极大的浪费了资源,并且效率低下。
比如说我要下载一个电影,我还想听歌,一般我们都是点了下载,就可以边听歌边等他下完了。如果是单线程那么我就只能点了下载然后就等着,直到电影下载完成我才可以听歌;这样估这电脑也活不了多久就被摔了;
-
Java程序运行原理
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。
该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。
所以 main方法运行在主线程中。 -
JVM的启动是多线程的吗:
JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
Java程序中多线程的实现方式
由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统所创建的,所以我们应该去掉用系统功能而创建一个进程。但是Java不能直接调用系统的功能,因此在Java中线程的开启方式是Java去调用C/C++写好的多线程程序,由C/C++去调用系统功能创建进程。是不是很麻烦,不过Java将这些都打包成了类提供给我们使用。
方式一:继承Thread类
-
代码演示
-
线程的命名
- 使用set方法
public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称- 使用构造方法
public class MyTest { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.setName("一号线程"); myThread.start(); //第二种线程命名方式 MyThread3 thread3 = new MyThread3("二号线程"); //将线程优先级设为最高 thread3.setPriority(Thread.MAX_PRIORITY); thread3.start(); } }
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+"===="+i); } } }
public class MyThread3 extends Thread{ public MyThread3(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "====" + i); } } }
-
- 这个类是一个线程类,那么在这个类中我们可不可以写一些其他的方法呢?
我们可以在写其他的方法,那么其他方法中封装的代码不一定需要被我们线程执行但是run方法里面封装的应该是必须被线程执行的代码。
-
run方法中的代码的书写原则: 一般是比较耗时的代码。
-
如何获取主线程?
可以使用方法 public static Thread currentThread()返回对当前正在执行的线程对象的引用。 拿到引用之后我们就可以使用public final void setName(String name)//设置主线程名称的名字:
Thread.currentThread().setName("刘备");
线程调度及获取和设置线程优先级
-
线程的执行
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,
线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢? -
线程有两种调度模型:
**分时调度模型 ** 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。从刚才的例子可以看出***Java使用的是抢占式调度模型。***
-
如何设置线程的优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级- 代码演示
public class MyTest { public static void main(String[] args) { //在Java中如何开启一个线程,利用 Thread 这个线程类 //线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。 MyThread th1 = new MyThread(); th1.setName("刘亦菲");/对线程进行命名 th1.setPriority(1); th1.start();//开启线程的方法 MyThread th2 = new MyThread(); th2.setPriority(Thread.MAX_PRIORITY); th2.setName("林青霞"); th2.start(); //Java中多个线程,执行是随机性的,因为我Java采用的线程调度模型是抢占式调度,线程优先级高的优先使用CPU的执行权 //优先级一样,就是机抢占 } }
public class MyThread extends Thread { @Override public void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { //System.out.println(this.getName()+"=="+i); System.out.println(Thread.currentThread().getName()+"===" + i); } } }
线程休眠
-
线程休眠: public static void sleep(long millis) 线程休眠
-
代码演示
public class MyTest { public static void main(String[] args) throws InterruptedException { System.out.println("这是一段广告"); Thread.sleep(5000); System.out.println("bbbbb"); //Thread(String name) 分配新的 Thread 对象。 MyThread th1 = new MyThread("林青霞"); th1.start(); } }
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) { try { Thread.sleep(2000);//让线程休眠 } catch (InterruptedException e) { e.printStackTrace(); } //System.out.println(this.getName()+"=="+i); System.out.println(Thread.currentThread().getName() + "===" + i); } } }
线程加入
- 加入线程: public final void join()
- 加入线程执行完毕之后,其他线程才能够再次执行
- 注意事项:在线程启动之后,再调用方法
- 案例演示
public class MyTest {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread("刘备");
MyThread th2 = new MyThread("关羽");
MyThread th3 = new MyThread("张飞");
th1.start();
th1.join();
th2.start();
th2.join();
th3.start();
}
}
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() { //需要线程来执行的方法
//一般run方法里面写的就是耗时操作
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
线程礼让
- public static void yield(): 暂停当前正在执行的线程对象,并执行其他线程。
- 案例演示线程礼让
ublic class MyTest {
public static void main(String[] args) {
MyThread th1 = new MyThread("刘备");
MyThread th2 = new MyThread("关羽");
th1.start();
th2.start();
}
}
public class MyThread extends Thread {
public MyThread(String name) {
super(name);
}
@Override
public void run() { //需要线程来执行的方法
//一般run方法里面写的就是耗时操作
for (int i = 0; i < 100; i++) {
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName() + "===" + i);
}
}
}
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显。这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
守护线程
-
守护线程: public final void setDaemon(boolean on):
将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
- 注意事项:该方法必须在启动线程前调用。
-
代码演示
public class MyTest { public static void main(String[] args) { Thread.currentThread().setName("刘备"); System.out.println("主线程开始了"); System.out.println("主线程开始了"); System.out.println("主线程开始了"); System.out.println("主线程开始了"); //A: //守护线程: //public final void setDaemon ( boolean on): //将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 //该方法必须在启动线程前调用。 //当用户线程,执行完之后,那么守护线程,必须里面死亡 MyThread th1 = new MyThread("张飞"); MyThread th2 = new MyThread("关羽"); //设置为守护线程 该方法必须在启动线程前调用。 th1.setDaemon(true); th2.setDaemon(true); th1.start(); th2.start(); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了"); System.out.println("主线程结束了"); } }
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 for (int i = 0; i < 100; i++) System.out.println(Thread.currentThread().getName() + "===" + i); } } }
中断线程
-
中断线程
public final void stop(): 停止线程的运行 过时的方法
-
清除线程的阻塞状态
public void interrupt(): 清除线程的阻塞状态,让线程继续运行
- 代码演示
public class MyTest { public static void main(String[] args) throws InterruptedException { MyThread th1 = new MyThread("张飞"); th1.start(); Thread.sleep(1000); th1.interrupt();//打断线程的阻塞状态,让线程继续运行 } }
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { //需要线程来执行的方法 //一般run方法里面写的就是耗时操作 try { Thread.sleep(5000); //让线程休眠,其实是让线程处于了一种阻塞状态 } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "===" + i); } } }
运行可以看到该程序只在主线程阻塞1000ms子线程里面直接运行没有停留
Java开启多线程的方式2
-
实现Runnable接口 这种方式扩展性强 实现一个接口 还可以再去继承其他类
- 如何获得线程的名称
- 如何给线程设置名称
代码演示
public class MyTest { public static void main(String[] args) { //1. 创建线程的另一种方法是声明实现 Runnable 接口的类。 //2. 该类然后实现 run 方法。然后可以分配该类的实例, // 3. 在创建 Thread 时作为一个参数来传递并启动 MyRunable myRunable = new MyRunable(); //Thread(Runnable target) //分配新的 Thread 对象。 //Thread(Runnable target, String name) //分配新的 Thread 对象。 Thread th = new Thread(myRunable,"王菲"); th.start(); Thread th2 = new Thread(myRunable,"谢霆锋"); th2.start(); } }
public class MyRunable implements Runnable { //Runable 任务 @Override public void run() { for (int i = 0; i < 100; i++) { // System.out.println(this.getName+"==+i"); System.out.println(Thread.currentThread().getName() + "==" + i); } } }
-
实现接口方式的好处
可以避免由于Java单继承带来的局限性。
Java开启多线程的方式3
- 实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
- 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类
- 实现步骤
- 创建一个类实现Callable 接口
- 创建一个FutureTask类将Callable接口的子类对象作为参数传进去
- 创建Thread类,将FutureTask对象作为参数传进去
- 开启线程
代码演示:
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Callable 返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call 的方法。
//
//Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
MyCallable myCallable = new MyCallable();
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
Thread th = new Thread(integerFutureTask);
th.start();
//获取线程执行完之后,返回的结果
Integer integer = integerFutureTask.get();
System.out.println(integer);
}
}
public class MyCallable implements Callable<Integer> {
//需要线程来执行的方法
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+" aaaaaa");
return 100;
}
}
- 使用Callable开启两个线程,让第一个线程求出1~ 100的和,第二个线程求出1~1000的和
public class MyTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//第一个线程
MyCallable myCallable = new MyCallable(100);
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread th = new Thread(futureTask);
th.start();
Integer integer = futureTask.get();
System.out.println(integer);
//第二个线程
MyCallable myCallable1 = new MyCallable(1000);
futureTask = new FutureTask<>(myCallable1);
Thread th2 = new Thread(futureTask);
th2.start();
Integer integer1 = futureTask.get();
System.out.println(integer1);
}
}
public class MyCallable implements Callable<Integer> {
int num;
public MyCallable(int num) {
this.num=num;
}
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <=num; i++) {
sum+=i;
}
System.out.println(Thread.currentThread().getName());
return sum;
}
}