目录
1. 认识线程
进程VS线程
- 进程实习是系统分配资源的最小单位;线程是系统调度的最小单位
- 一个进程中可以包含多个线程。
- 进程的实际执行单位是线程。
- 一个进程里面最少包括一个线程,线程的存在必须依托于进程。
- 进程不可以资源共享,线程可以。
多线程的优势-增加运行速度
主线程:主要执行业务的线程
子线程:在主线程中创建的线程是子线程
2.创建线程
方法1-继承 Thread 类
public class ThreadDemo {
static class MyThread extends Thread{
@Override
public void run() {
//打印当前线程的名称
System.out.println("子线程名"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
//1.继承Thread类
MyThread myThread =new MyThread();
//启动线程
myThread.start();
System.out.println(Thread.currentThread().getName());
}
}
缺点:因为Java语言的设计是单继承的,当我继承了Thread类之后,就不能继承其他类
方法2-实现Runnable接口
public class ThreadDemo {
public static void main(String[] args) {
Thread thread=new Thread(){
public void run(){
System.out.println("当前线程名称"+Thread.currentThread().getName());
}
};
thread.start();
}
}
方法3-实现Callable 有返回值,重写call
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建一个有返回值的线程
*/
public class ThreadDemo {
// 创建线程
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 产生随机数
int num = new Random().nextInt(10);
System.out.println(String.format("线程:%s,生产了随机数:%d",
Thread.currentThread().getName(), num));
return num;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.创建 Callable 子对象
MyCallable callable = new MyCallable();
// 2.使用 FutrueTask 接收 Callable
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 3.创建线程并设置任务
Thread thread = new Thread(futureTask);
// 执行线程
thread.start();
// 得到线程的执行结果
int num = futureTask.get();
System.out.println("线程返回结果:" + num);
}
}
方法4-使用匿名类创建Thread子类对象
// 使用匿名类创建 Thread 子类对象
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("使用匿名类创建 Thread 子类对象");
}
};
方法5-使用匿名类创建Runable子类对象
// 使用匿名类创建 Runnable 子类对象
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("使用匿名类创建 Runnable 子类对象");
}
});
方法6-使用lambda表达式创建Runable子类对象(JDK1.8之后才支持)
// 使用 lambda 表达式创建 Runnable 子类对象
Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
Thread t4 = new Thread(() -> {
System.out.println("使用匿名类创建 Thread 子类对象");
});
如果报错或者线程无法启动:
- 点击项目属性,选择Modules->Jdk项目设置为1.8;
- 点击file->settings->build->Compiler->Java Compiluer 中将target bytecode version 改为1.8
高频面试题:
1.线程创建的数量是不是越多越好?
答:不是。
2.创建多少个线程合适?
答:要看实际情况。
任务类型:
1.计算密集型的任务(CPU使用密集型任务)线程数量=CPU核数最好;
2.读写文件任务 对于读写文件操作,理论上来讲线程数量越多越好。
3.Thread 类及常见方法
3.1
线程优先级默认为:5
线程最小优先级为1 表示(CPU执行权重)权重最低
守护线程是为用户线程服务的
守护线程需要注意的事项:
- 守护线程的设置必须放在开启线程(start())之前
- 在守护线程中创建的线程默认是守护线程
守护线程的经典使用场景:
- Java垃圾回收器
- 监控检测任务
3.2启动一个线程
1.run()方法是一个对象的普通方法,它使用的是主线程来执行任务的
2.start()方法是线程的开启方法,他使用新的线程来执行任务
run方法可以多次执行,start方法只能一次
3.3中断一个线程
目前常见的有以下两种方式:
- 通过共享的标记来进行沟通
- 调用 interrupt() 方法来通知
示例-1
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false;
@Override
public void run() {
while (!isQuit) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
target.isQuit = true;
}
}
示例-2
public class Thread2 {
private static class MyRunnable implements Runnable {
@Override
public void run() {
while (!Thread.interrupted()) {
//while (!Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ ": 别管我,我忙着转账呢!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName()
+ ": 有内鬼,终止交易!");
break;
}
}
System.out.println(Thread.currentThread().getName()
+ ": 啊!险些误了大事");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "李四");
System.out.println(Thread.currentThread().getName()
+ ": 让李四开始转账。");
thread.start();
Thread.sleep(10 * 1000);
System.out.println(Thread.currentThread().getName()
+ ": 老板来电话了,得赶紧通知李四对方是个骗子!");
thread.interrupt();
}
}
重点说明下第二种方法:
1.通过 thread 对象调用 interrupt() 方法通知该线程停止运行
2. thread 收到通知的方式有两种:
1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式 通知,清除中断标志
2. 否则,只是内部的一个中断标志被设置,thread 可以通过
1. Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
2. Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
第二种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到
两者的区别:
使用系统的Intrrput()可以及时的终止进程,而使用自定义局部变量的方式,比较温柔,不能立即终止进程
示例-3
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.interrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "t1");
thread.start();
thread.interrupt();
}
}
运行结果:
thread.interrupt() 第一次接收到终止状态是true,之后会将状态复位
示例-4
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
}
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target, "t1");
thread.start();
thread.interrupt();
}
}
运行结果:
全部是 true,没有复位
3.4等待一个线程-join()
public class Thread2 {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName()
+ ": 我还在工作!");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": 我结束了!");
};
Thread thread1 = new Thread(target, "李四");
Thread thread2 = new Thread(target, "王五");
System.out.println("先让李四开始工作");
thread1.start();
thread1.join();
System.out.println("李四工作结束了,让王五开始工作");
thread2.start();
thread2.join();
System.out.println("王五工作结束了");
}
}
3.5线程休眠:
方式1:
public static void main(String[] args) throws InterruptedException {
String content="你喜欢海风咸咸的气息\n" +
"踩着湿湿的沙砾\n" +
"你说人们的骨灰应该撒进海里\n" +
"你问我死后会去哪里\n" +
"有没有人爱你";
for(char item:content.toCharArray()){
System.out.print(item);
Thread.sleep(300);
}
}
方法2:
public static void main(String[] args) throws InterruptedException {
String content="你喜欢海风咸咸的气息\n" +
"踩着湿湿的沙砾\n" +
"你说人们的骨灰应该撒进海里\n" +
"你问我死后会去哪里\n" +
"有没有人爱你";
for(char item:content.toCharArray()){
System.out.print(item);
TimeUnit.SECONDS.sleep(1);//休眠1秒
TimeUnit.HOURS.sleep(1);//休眠1小时
}
}
方法3:
public static void main(String[] args) throws InterruptedException {
String content="你喜欢海风咸咸的气息\n" +
"踩着湿湿的沙砾\n" +
"你说人们的骨灰应该撒进海里\n" +
"你问我死后会去哪里\n" +
"有没有人爱你";
for(char item:content.toCharArray()){
System.out.print(item);
Thread.sleep(TimeUnit.SECONDS.toMillis(1));
}
}
4. 线程的状态
线程的状态是一个枚举类型 Thread.State
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
运行结果:
NEW:创建了线程但是还没有工作
RUNNABLE:运行
BLOCKED:阻塞
WAITING:等待
TIMED_WAITING:超时等待
TERMINATEO:终止
线程常用方法:yield【用来让出CPU】
yield 分配执行权不一定成功,要看CPU的选择,但总体来说基本符合预期。
5.线程通信
wait【休眠线程】/ notify【唤醒一个线程】/ notifyAll【唤醒所有的线程】
wait() 和 notify():
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1");
t1.start();
}
运行结果:
发现会发现报监视器异常,所以在使用时有以下注意事项
- 需要在使用 wait() 和 notify() 之前加锁,配合 synchronized 一起使用
- wait() 和 notify() 在配合 synchronized 使用的时候需要使用同一个锁
- wait() 和 notify() 需要操作同一把锁
- wait() 和 notify() 配合时不能唤醒指定线程
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("wait 之前");
lock.wait();
System.out.println("wait 之后");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"t1");
t1.start();
Thread.sleep(500);
System.out.println("主线程唤醒t1");
//在主线程中唤醒t1
//唤醒操作之前加锁
synchronized (lock) {
lock.notify();
}
}
运行结果:
6.小结:
wait () 的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒(进入“就绪状态”)
- wait (long timeout) 让当前线程处于超时等待状态,直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量,当前线程被唤醒(进入“就绪状态”)。当调用wait 不传参数时,其底层也是调用了wait (0)
- notify () 和 notifyAll () 的作用,则是唤醒当前对象上的等待线程;notify() 是唤醒单个线程,而 notifyAll() 是唤醒所有的线程。
7.相关面试题:
1.wait() 和 sleep()(面试重点)
相似点:
- wait 和 sleep 都会使线程进入休眠状态
- wait 和 sleep 在执行的过程中都可以接收到终止线程执行的通知
区别:
- wait 必须配合 synchronized 一起使用,wait 执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对象上的 monitor lock
- sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求
- wait 是 Object 的方法, sleep 是 Thread(线程) 的静态方法
- 默认情况下 wait 不传递任何参数,wait 会进入 waiting 状态,sleep 会进入 timed_waiting
- 使用wait 时可以主动唤醒线程,使用 sleep 不能主动唤醒线程,只能等待结束时间之后进行唤醒
2.sleep(0)和 wait(0)的区别:
- sleep(0)表示过0毫秒之后继续执行,而wait 一直休眠
- sleep(0)表示重新触发一次 CPU 竞争
3.为什么 wait 会释放锁,而 sleep 不会释放锁?
答:sleep 必须传递一个最大等待时间,也就是说sleep在时间层面来讲是可控的,而 wait 是可以不传递参数的,从设计层面来讲,让 wait 这个没有超时等待时间机制不释放锁的话,那么线程可能会一直阻塞,而sleep不存在这个问题。
4.为什么wait 是 Object 的方法,而 sleep 是 Thread 的方法?
答:因为 wait 需要操作锁,而锁是属于对象级别的(所有的锁是放在对象头中),它不是线程级别的,一个线程中可以拥有多把锁的,为了灵活起见,所以就将wait 放在 Object 当中。