多线程
1. 多线程的基本概念
- 每一个正在执行的程序都是一个进程,资源只有一块,所以在同一时间段会有多个程序同时执行,但是在一个时间点上,只能由一个程序执行,多线程是在一个进程的基础之上的进一步划分,因为进程的启动所消耗的时间是非常长的,所以在进程之上的进一步划分就变得非常的重要,而且性能也会有所提高,所有的线程一定要依附进程才能够存在,那么进程一旦消失,线程也一定会消失,但反过来不一定,而Java是支持多线程的开发语言之一。
2. 多线程的优缺点
- 优点:
1.提高资源利用率
2.提升用户体验 - 缺点:
1.降低了其它线程的执行概率
2.用户会感到一点的卡顿问题
3.给系统增加了资源压力
4.多线程下的共享资源问题,线程冲突,安全问题
3. 多线程的实现
在Java之中,如果想实现多线程,就必须依靠一个线程的主体类,但是这个线程的主体类在定义的时候需要有一些特殊的要求,这个类可以继承Thread类或者实现Runnable接口来完成定义。
3.1 继承Thread类:
- Thread类是Java当中的一个线程类,Thread是Runnable接口的一个实现类,同时提供了很多线程的操作方法。
- 通过自定义一个继承了Thread类的自定义类,重写run方法,然后创建自定义线程类对象,调用start方法,启动线程。
- 存在单继承的问题。
/*
* 自定义线程类MyThread1继承Thread类
*/
class MyThread1 extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("自定义线程类,通过继承Thread类");
}
}
}
public class Demo1 {
public static void main(String[] args) {
/*
* 通过继承Thread类得到线程对象
*/
MyThread1 myThread1 = new MyThread1();
myThread1.start();
}
}
3.2 实现Runnable接口:
- 自定义线程类遵从Runnable接口,重写run方法。
- Runnable接口当中只有一个run方法,所有通过遵从Runnable接口实现的自定义线程类在启动的时候需要通过Thread类中的start方法来启动。
- 使用自定义的线程类对象作为Thread类构造方法的参数,得到一个Thread类对象,再调用start方法开启线程。Thread thread = new Thread(Runnable target)。
/*
* 自定义线程类MyThread2遵从Runnable接口
*/
class MyThread2 implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("自定义线程类,通过遵从Runnable接口");
}
}
}
public class Demo1 {
public static void main(String[] args) {
/*
* 通过遵从Runnable接口得到线程对象
*/
Thread thread = new Thread(new MyThread2());
thread.start();
}
}
3.3 继承Thread类和实现Runnable接口的区别:
- 多线程的两种实现方式都需要一个线程的主类,而这个主类可以是通过继承Thread类或者实现Runnable接口,但是不管哪一种方式,都需要在子类当中重写run方法。
- Thread类是Runnable接口的一个子类,使用Runnable接口可以避免单继承局限,以及更方便的表示数据共享的概念。
3.4 提问:每次重写run方法之后,需要调用start方法启动而不是直接调用run方法,但是调用start方法实际上调用的还是run方法,那为什么不直接调用run方法呢?
- 观察Theard类的源码中start方法的定义。
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
- 抛出一个IllegalThreadStateException,该异常属于RuntimeException的子类,当一个线程对象被重复调用之后,会抛出此异常,即一个线程对象只允许启动唯一的一次。
- 在start方法中间接调用了一个 start(0) 方法,该方法没有方法体,而且这个方法定义时使用了关键字native修饰。
- native关键字指的是Java本地接口调用(Java Native Interface),即使用Java调用本机操作系统的函数功能完成一些特殊的操作。
- 多线程的实现一定需要操作系统的支持,那么没有方法体的start(0)方法实际上就和抽象方法很类似,没有方法体但是交给JVM去实现凡是在调用的时候并不会去关心各种操作系统的如何实现start(0)方法,只会关心最终的操作结果,交给JVM去匹配不同的操作系统。
- 所以不直接调用run方法而是通过调用start方法间接调用run方法,除了保证每一个线程有且只能启动一次之外,还需要通过start方法去匹配当前操作系统,从而调用操作系统中的函数功能。
3.5 Thread类当中需要知道的方法:
- Constructor:
Thread( ):
创建一个新的线程类对象,但是不指定目标代码,以及不指定线程名字。
Thread(Runnable target):创建一个线程类对象,且使用Runnable接口实现类对象作为该线程的执行目标代码,不指定线程名字。
Thread(String name):
创建一个新的线程类对象,不指定目标代码,但是指定了该线程的名字。
Thread(Runnable target,String name):
创建一个新的线程类对象,使用Runnable接口实现类对象作为目标执行代码,且指定当前线程的名字。
/*
* 线程构造方法演示
*/
public class Demo2 {
public static void main(String[] args) {
//不做任何要求的构造
Thread thread = new Thread();
//约束线程的名字的构造
Thread thread2 = new Thread("朱朱");
//使用Runnable接口类对象作为构造的参数
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类对象作为线程的执行目标");
}
});
//使用Runnable接口类对象作为构造的参数,且约束线程的名字
Thread thread4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(
"带有名字的通过RUnnable接口类对象作为执行目标的线程");
}
},"龙龙");
System.out.println(thread);
System.out.println(thread2);
System.out.println(thread3);
System.out.println(thread4);
}
}
- Method:
void setName( ):
通过线程类对象调用,设置当前线程的名字。
String getName( ):
通过线程类对象调用,得到当前线程的名字。
void setPriority(int Priority):
通过线程类对象调用,设置当前线程的优先级。
int getPriority( ):
通过线程类对象调用,得到当前线程的优先级。
void start( ):
通过线程类对象调用,启动当前线程。
public static void sleep(int ms):
静态方法,通过类名直接调用,使当前所在线程代码块对应的线程进行休眠操作,休眠指定的毫秒数。
public static Thread currentThread( ):
静态方法,通过类名直接调用,获取当前所在线程代码块对应的线程类对象。
/*
* Thread类成员方法演示
*/
class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
Thread currentThread = Thread.currentThread();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("自定义线程");
}
}
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程目标代码");
}
},"扫雷线程");
//main线程
Thread currentThread = Thread.currentThread();
System.out.println(thread.getName());
thread.setName("纸牌线程");
System.out.println(thread.getName());
Thread.sleep(1000);
System.out.println(thread.getPriority());
thread.setPriority(Thread.MAX_PRIORITY);
System.out.println(thread.getPriority());
}
}
3.6 守护线程:
- 守护线程也称之为后台线程,伴随着主线程的存在而存在,主线程消失,守护线程也会跟着消失。
- 通过线程类对象调用setDeamon(boolean flag)方法设置是否为守护线程,true表示为守护线程。
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new BackUpThread());
// 当前线程作为一个守护线程使用
thread.setDaemon(true);
thread.start();
for (int i = 0; i <= 50; i++) {
Thread.sleep(100);
System.out.println("主线程运行中。。。。");
}
}
}