概述:
1. 进程和多线程介绍:
简单来说:
- 进程 : 启动一个xxx.exe, 这叫一个进程
- 线程 : 线程是进程内部同时做的事
- 多线程 : 多线程就是多个线程同时运行或交替运行。单核 CPU 的话是顺序执行,也就是交替运行。多核 CPU 的话,因为每个 CPU 有自己的运算器,所以在多个 CPU 中可以同时运行。
为什么需要多线程:
- 使用线程可以把占据长时间的程序中的任务放到后台去处理
- 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
2. 如何使用多线程
1. 继承Thread: 重写run()方法
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("MY THREAD");
}
}
2. 实现Runnable接口, 重写run()方法
- 推荐使用这个办法实现多线程, 因为Java只能单继承, 但是可以实现多个接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("My Runnable");
}
}
测试代码:
public class ThreadTest {
public static void main(String[] args) {
//继承Thread的多线程
MyThread thread1 = new MyThread();
thread1.start();
System.out.println("运行结束");
//"运行结束"先出来, 说明cpu随机调用run方法
System.out.println("==========================================================");
//实现Runnable接口的多线程
Runnable runnable = new MyRunnable();
Thread thread2 = new Thread(runnable);
thread2.start();
System.out.println("运行结束");
}
}
运行结果: 说明cpu执行run方法时随机的
运行结束
MY THREAD
==========================================================
运行结束
My Runnable
3. 实例变量和线程安全
· 定义线程类中的实例变量针对其他线程有共享和不共享之分
不共享的情况:
1. 不共享数据
public class ThreadNotShareData extends Thread {
private int count = 5;
//定义线程名字
public ThreadNotShareData(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while (count > 0){
count --;
System.out.println("由 " + ThreadNotShareData.currentThread().getName() + " 计算, count = "+ count);
}
}
}
2. 共享数据的情况
public class ThreadShareData extends Thread {
private int count = 6;
@Override
synchronized public void run() {
super.run();
count --;
System.out.println("由 " + ThreadNotShareData.currentThread().getName() + " 计算, count = "+ count);
}
}
测试类:
public class DataShareTest {
public static void main(String[] args) {
//数据不共享的情况
// ThreadNotShareData a = new ThreadNotShareData("a");
// ThreadNotShareData b = new ThreadNotShareData("b");
// ThreadNotShareData c = new ThreadNotShareData("c");
//
// a.start();
// b.start();
// c.start();
//结果看出每个线程都有一个自己的count, 互不影响
//数据共享情况
ThreadShareData thread = new ThreadShareData();
//以下线程都是 ThreadShareData 对象创建的
Thread d = new Thread(thread, "d");
Thread e = new Thread(thread, "e");
Thread f = new Thread(thread, "f");
Thread g = new Thread(thread, "g");
Thread h = new Thread(thread, "h");
d.start();
e.start();
f.start();
g.start();
h.start();
//count是Thread对象的属性, 其他线程都是操作thread的count属性, 会造成混乱
//在run方法前面加synchronized关键字就可以得到正确答案, 加锁
}
}
4. 线程类常用方法
- currentThread()
返回对当前正在执行的线程对象的引用。 - getId()
返回此线程的标识符 - getName()
返回此线程的名称 - getPriority()
返回此线程的优先级 - isAlive()
测试这个线程是否还处于活动状态。
- 什么是活动状态呢?
- 活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。
- sleep(long millis)
使当前正在执行的线程以指定的毫秒数 “休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 - interrupt()
中断这个线程。 interrupted() 和 isInterrupted()
- interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为 false 的功能
- isInterrupted(): 测试线程 Thread 对相关是否已经是中断状态,但部清楚状态标志
setName(String name)
将此线程的名称更改为等于参数 name 。isDaemon()
测试这个线程是否是守护线程。setDaemon(boolean on)
将此线程标记为 daemon 线程或用户线程。join()
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到 join() 方法了。
join() 的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了 join() 方法后面的代码,只有等到子线程结束了才能执行- yield()
yield() 方法的作用是放弃当前的 CPU 资源,将它让给其他的任务去占用 CPU 时间。注意:放弃的时间不确定,可能一会就会重新获得 CPU 时间片。 - setPriority(int newPriority)
更改此线程的优先级
5. 如何停止一个线程
1. 三个弃用的方法:
2. 停止线程: 在后面介绍
6. 线程优先级
- 每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。
- 线程优先级具有继承特性比如 A 线程启动 B 线程,则 B 线程的优先级和 A 是一样的。
- 线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。
- Thread 类中包含的成员变量代表了线程的某些优先级。如
- Thread.MIN_PRIORITY(常数 1),Thread.NORM_PRIORITY(常数 5),
Thread.MAX_PRIORITY(常数10)。 - 其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)到 Thread.MAX_PRIORITY(常数10)之间,
- 在默认情况下优先级都是 Thread.NORM_PRIORITY(常数 5)。
- Thread.MIN_PRIORITY(常数 1),Thread.NORM_PRIORITY(常数 5),
7. Java多线程分类
用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程
守护线程:运行在后台,为其他前台线程服务. 也可以说守护线程是 JVM 中非守护线程的 “佣人”。
特点:一旦所有用户线程都结束运行,守护线程会随 JVM 一起结束工作
应用:数据库连接池中的检测线程,JVM 虚拟机启动后的检测线程
最常见的守护线程:垃圾回收线程
如何设置守护线程: 调用Thead 类的 setDaemon(true) 方法
- setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
- 在守护线程中产生的新线程也是守护线程
- 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑