概述
JavaEE | 多线程基础
- 1 多线程与多进程
- 1.1 进程
- 1.2 线程
- 1.3 线程与进程
- 1.4 并行和并发
- 1.5 JVM的启动是多线程的
- 2 多线程实现的三种方式
- 2.1 方式一:继承Thread类
- 2.1.1 实现步骤
- 2.1.2 实例
- 2.2 方式二:Runnable接口 (避免单继承的局限性,run方法没有返回值)
- 2.2.1 实现步骤
- 2.2.2 实例
- 2.3 方式三:Callable 接口(call方法有返回值)
- 2.3.1 实现步骤
- 2.2.2 实例
- 3 线程的设置
- 3.1 获取和设置线程对象名称
- 3.2 线程调度与线程的优先级
- 4 线程控制
- 4.1 休眠线程(sleep)
- 4.2 加入线程(join)
- 4.3 礼让线程(yield)
- 4.4 守护线程(setDaemon)
- 4.6 中断线程(interrupt)
- 5 用内部类开启线程
1 多线程与多进程
1.1 进程
进程的定义:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
1.2 线程
线程的定义:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。
多线程的作用:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。
我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到 CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性。
1.3 线程与进程
进程是拥有资源的基本单位, 线程是CPU调度的基本单位。
我们一般是进行多线程代码编写,而不是多进程程序,这是因为不同进程之间共享数据是异常困难的,而在同一个进程中不同的线程之间,方便进行资源的共享。
1.4 并行和并发
并行:指应用能够同时执行不同的任务
并发:指应用能够交替执行不同的任务,
并发类似于Java多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务, 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果"
1.5 JVM的启动是多线程的
Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。 所以 main方法运行在主线程中。
JVM的启动是多线程的:JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的
2 多线程实现的三种方式
2.1 方式一:继承Thread类
2.1.1 实现步骤
- 定义一个类,继承Thread类,创建线程
- 重写Thread类中的run方法
run方法中的代码的书写原则: 一般是比较耗时的代码 - 创建定义的类的对象
- 开启线程
注意事项:
- 调用run方法并没有开启线程。调用run()方法,仅仅是使用一个对象,调用一个方法,让这个方法执行而已。
- 正确开启线程的方式是调用start() 开启线程,start()方法会先做一些准备工作,如线程的栈、线程的状态等信息后,再由线程去调用run()去执行run方法里面的代码。
- 同一个线程不要多次开启,会抛异常:IllegalThreadStateException
- 定义的类(本例中指的是MyThread类)中,可以写其他的方法,但是只有run方法中的代码被线程执行
2.1.2 实例
public class MyThread extends Thread {
@Override
public void run() {
//这个run方法就是需要线程来执行的代码,一般耗时的操作,我们就会写在run方法里面,让线程去执行
Thread thread = Thread.currentThread();
String name = thread.getName();
for (int i = 0; i < 1000; i++) {
System.out.println(name+":"+i);
}
}
}
public class MyTest1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();//此行代码,开启线程
}
}
2.2 方式二:Runnable接口 (避免单继承的局限性,run方法没有返回值)
2.2.1 实现步骤
- 创建一个类,实现Runnable接口,重写改接口中的run方法
- 创建Thread类,对象,将Runnable接口的子类对象传递进来
- 调用start()方法开启线程
实现Runnable接口方式的好处:可以避免由于Java单继承带来的局限性,即MyRunnable1 还可以继承别的类
2.2.2 实例
public class MyTest1 {
public static void main(String[] args) {
MyRunnable1 myRunnable1 = new MyRunnable1();
Thread thread = new Thread(myRunnable1);
thread.setName("A");
thread.start();
}
}
public class MyRunnable1 implements Runnable{
@Override
public void run() {
//只能用Thread静态方法调用线程的名字
String name = Thread.currentThread().getName();
//不能this.getname方法,因为MyRunnable1并不是Thread的子类
System.out.println(name+"线程执行了");
}
}
2.3 方式三:Callable 接口(call方法有返回值)
2.3.1 实现步骤
- 创建一个类实现Callable 接口
- 创建一个FutureTask类将Callable接口的子类对象作为参数传进去(执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果)
- 创建Thread类,将FutureTask对象作为参数传进去
- 开启线程
特点:相较于实现 Runnable 接口的方式2,Callable 接口可以有返回值,并且可以抛出异常
2.2.2 实例
public class MyTest1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.setName("A");
thread.start();
//获取线程的返回值
Integer integer = futureTask.get();
System.out.println(integer);
}
}
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//call方法就是线程要执行的方法
String name = Thread.currentThread().getName();
System.out.println(name+"线程执行了");
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
}
3 线程的设置
3.1 获取和设置线程对象名称
-
Thread类的基本获取和设置方法:
public final String getName():获取线程名称
public final void setName(String name):设置线程名称 -
获取当前所在的线程名称
public static Thread currentThread():获取当前执行的线程
3.2 线程调度与线程的优先级
-
线程调度
假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?
-
两种调度模型
a) 分时调度模型 :所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
b) 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。
-
线程优先级的设置和获取
public final int getPriority():获取线程的优先级
public final void setPriority(int newPriority):设置线程的优先级 (范围:1-10 ;默认优先级:5)
4 线程控制
4.1 休眠线程(sleep)
线程休眠(sleep)指让线程处于阻塞状态。
- public static void sleep(long millis)
public class MyTest4 {
public static void main(String[] args) throws InterruptedException {
//线程控制之休眠线程
//public static void sleep(long millis)
MyThread2 th = new MyThread2("线程");
th.start();
//让主线程休眠
Thread.sleep(1000);
System.out.println("主线程");
}
}
public class MyThread2 extends Thread{
//通过构造,给线程设置名字
public MyThread2(String name){
super(name);
}
@Override
public void run() {
//获取当前线程名字的两种方法
String name = this.getName();
String name1 = Thread.currentThread().getName();
//sleep方法的异常只能抓,不能抛,因为其父类Thread的run方法没有抛出异常
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
System.out.println(name+":"+i);
}
}
}
4.2 加入线程(join)
public final void join(): 等待该线程执行完毕了以后,其他线程才能再次执行,实际实现的功能是将并发执行变为串行。
注意:在线程启动(start方法)之后,再调用join方法。
public class MyTest5 {
public static void main(String[] args) throws InterruptedException {
//线程控制之加入线程
//public final void join()
//等待该线程执行完毕了以后,其他线程才能再次执行
//注意:在线程启动之后,再调用join方法
//A、B、C三个线程串行执行
MyThread2 th1 = new MyThread2("A");
MyThread2 th2 = new MyThread2("B");
MyThread2 th3 = new MyThread2("C");
th1.start();
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
}
}
4.3 礼让线程(yield)
public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
public class MyTest {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread("张飞");
MyThread th2 = new MyThread("刘备");
th1.start();
th2.start();
}
}
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run(){
//String name = this.getName();
String name1 = Thread.currentThread().getName();
for (int i = 0; i < 100; i++) {
Thread.yield();
System.out.println(name1+i);
}
}
}
按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.
原因:
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.
4.4 守护线程(setDaemon)
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
注意事项:
- 调用线程对象的方法setDaemon(true),设置线程为守护线程。
- thread.setDaemon(true)必须在thread.start()之前设置。
- 在Daemon线程中产生的新线程也是Daemon的。
- 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
public class MyTest7 {
public static void main(String[] args) {
MyThread3 th1 = new MyThread3("A");
MyThread3 th2 = new MyThread3("B");
MyThread3 th3 = new MyThread3("C");
th1.setDaemon(true);
th2.setDaemon(true);
th3.setDaemon(true);
th1.start();
th2.start();
th3.start();
for (int i = 0; i < 100; i++) {
String name = Thread.currentThread().getName();
System.out.println(name+":"+i);
}
}
}
public class MyThread3 extends Thread{
public MyThread3(String name) {
super(name);
}
@Override
public void run() {
String name = this.getName();
for (int i = 0; i < 1000; i++) {
Thread.yield();
System.out.println(name+":"+i);
}
}
}
1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束,而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。
2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的
4.6 中断线程(interrupt)
public final void stop(): 停止线程的运行(该方法已废弃,不推荐使用!)
public void interrupt(): 中断线程,作用是打破线程的阻塞状态。
(当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过interrupt方法清除阻塞)
MyThread2 th = new MyThread2("A");
th.start(); //th中阻塞了3s
th.interrupt();//此行代码可以打断th的阻塞状态
5 用内部类开启线程
public class MyTest {
public static void main(String[] args) {
//用内部类开启线程
//方法一:重新Runnable内部类的run方法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("aaa");
}
}).start();
//方法二:重新Thread的run方法
new Thread(){
@Override
public void run() {
System.out.println("bbb");
}
}.start();
Thread th = new Thread() {
@Override
public void run() {
System.out.println("ccc");
}
};
th.start();
}
}
最后
以上就是俭朴耳机为你收集整理的JavaEE | 多线程基础1 多线程与多进程2 多线程实现的三种方式3 线程的设置4 线程控制5 用内部类开启线程的全部内容,希望文章能够帮你解决JavaEE | 多线程基础1 多线程与多进程2 多线程实现的三种方式3 线程的设置4 线程控制5 用内部类开启线程所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复