多线程
作用:
防止线程阻塞,执行某个耗时任务
让多个程序能够看起来像是**“同时执行”**
单独执行某个任务
文章目录
1.进程和线程
进程: 正在执行的程序 【前台进程和后台进程或者一些服务进程】
线程: 一条执行路径
- 一个进程可以包含一个线程,也可以包含多个线程
- 一条线程包含了一条原子性语句,也可以包含n条原子性语句
- 原子性语句: 不可再分割的语句 i ++ ; i = 10 ;
- CPU在某一个时间刻度上只能够执行一条线程的一条原子性语句
2.多线程的特点
-
开启多线程可以提高CPU的使用率,从而间接提高进程的使用率
-
CPU在分配给线程资源的时候是随机的
Java 虚拟机允许应用程序并发地运行多个执行线程。
- 并发: 同一个时间段同时执行多个线程
- 并行: 同一个时间刻度【不能分割的时间单位】下同时执行多个线程 多核处理器
- 高并发: 线程数太大
3.多线程的实现方法(6种)
线程的封装:线程中需要用到的任务参数通过外界使用构造方法传入
3.1方式一:继承Thread类
-
自定义类MyThread继承Thread类
-
MyThread类里面重写run()方法
-
创建线程对象
-
启动线程
public class ThreadDemo {
public static void main(String[] args) {
CalculateThread t2 = new CalculateThread(1, 1000);//创建线程对象
t2.start();//启动线程
}
}
class CalculateThread extends Thread {//继承Thread类
private int m;
private int n;
public CalculateThread() {
super();
}
public CalculateThread(int m, int n) {//任务参数通过外界使用构造方法传入
super();
this.m = m;
this.n = n;
}
@Override
public void run() {//重写run()方法
int sum = 0;
for (int i = m; i <= n; i++) {
System.out.println("subThread: " + i);
sum += i;
}
System.out.println(m + "~" + n + "的和为: " + sum);
}
public int getM() {
return m;
}
public void setM(int m) {
this.m = m;
}
public int getN() {
return n;
}
public void setN(int n) {
this.n = n;
}
}
3.2方式二:实现Runnable接口
1.自定义类MyRunnable实现Runnable接口
2.重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
5.启动线程
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建IteratorFileThread类的对象
IteratorFileThread ift = new IteratorFileThread(new File("D:\\JavaSE"));
// 4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
Thread t = new Thread(ift);
CalculateThread t2 = new CalculateThread(1, 1000);
// 5.启动线程
t.start();
t2.start();
for (int i = 0; i <= 1000; i++) {
System.out.println("mainThread: " + i);
}
}
}
// 开启线程一 遍历文件夹中所有的文件
class IteratorFileThread implements Runnable {
private File srcFile;
public IteratorFileThread() {}
public IteratorFileThread(File srcFile) {
this.srcFile = srcFile;
}
@Override
public void run() {
getAllFile(srcFile);
}
public static void getAllFile(File srcFile) {
if (srcFile == null) {
throw new RuntimeException("文件对象不能够为null!");
}
// 2.遍历文件对象下面所有的文件或者文件夹
File[] files = srcFile.listFiles();
if (files == null) {
throw new RuntimeException("文件遍历失败!");
}
// 说明文件夹中没有文件
if (files.length == 0) {
return;
}
// 3.获取到每一个File对象
for (File f : files) {
// 4.判断该对象是文件还是文件夹
if (f.isDirectory()) {
// a.是文件夹 递归,回到第二步
getAllFile(f);
} else {
String fileName = f.getName();
// b.是文件
System.out.println(fileName);
}
}
}
public File getSrcFile() {
return srcFile;
}
public void setSrcFile(File srcFile) {
this.srcFile = srcFile;
}
}
3.3方式三:匿名内部类开启线程
3.3.1继承Thread方式
new Thread() {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("匿名内部类 继承Thread方式开启线程: " + i);
}
}
}.start();
3.3.2实现Runnable方式
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("匿名内部类 实现Runnable方式方式开启线程: " + i);
}
}
}).start();
3.3.3Thread和Runnable镶嵌
如果继承Thread和实现Runnble同时实现,继承Thread优先
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("==>匿名内部类 实现Runnable方式方式开启线程: " + i);
}
}
}) {
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println("==>匿名内部类 继承Thread方式开启线程: " + i);
}
};
}.start();
3.4方式四:Lambda表达式方式
new Thread(()-> {
for (int i = 1; i <= 1000; i++) {
System.out.println("==>Lambda表达式方式: " + i);
}
}) .start();
3.5方式五:实现Callable方式开启线程
重写run方法可以书写线程的任务,但是run方法是无参数无返回值的,同时没有异常
- 所以开启线程的线程无法知道被开启线程的异常和返回值
- Java提供了Callable接口开启线程,借助一个中间类 FutrueTask
public class CallableTest {
public static void main(String[] args) {
//public interface RunnableFuture<V> extends Runnable, Future<V>
//RunnableFuture继承了 Runnable, Future,所以可以Thread多态调用Runnable的调用
//public class FutureTask<V> implements RunnableFuture<V>
//且public FutureTask(Callable<V> callable)
//FutureTask是RunnableFuture子类,FutureTask包含Callable
//Thread -> Runnable -> RunnableFuture -> FutureTask -> Callable
FutureTask<Integer> task = new FutureTask<>(new MyCallable(1, 100));
Thread t = new Thread(task);
t.start();
try {
Integer result = task.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
System.out.println("over");
}
}
class MyCallable implements Callable<Integer> {
private int m;
private int n;
public MyCallable() {
super();
}
public MyCallable(int m, int n) {
super();
this.m = m;
this.n = n;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = m; i <= n; i++) {
System.out.println(Thread.currentThread().getName() + "|" + i);
sum += i;
}
return sum;
}
}
3.6方式六:线程池开启线程
Executors工厂类来产生线程池
3.6.1构造方法
说明 | |
---|---|
public static ExecutorService newCachedThreadPool() | |
public static ExecutorService newFixedThreadPool(int nThreads) | |
public static ExecutorService newSingleThreadExecutor() |
public class ThreadPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(3);
// 将线程提交到线程中
pool.submit(new MyRunnable());
Future<Integer> future = pool.submit(new MyCallable(1, 50));
pool.submit(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "|" + i);
}
}
});
Integer result = future.get();
System.out.println(result);
pool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "|" + i);
}
}
}
4.设置和获取线程名称
- 通过构造方法
说明 | |
---|---|
Thread(String name) | 分配新的 Thread 对象。 |
Thread(Runnable target, String name) | 分配新的 Thread 对象。 |
- 通过线程的成员方法
说明 | |
---|---|
public final String getName() | |
public final void setName(String name) |
- 通过静态方法
public static Thread currentThread() | 可以获取任意方法所在的线程名称 |
Thread.currentThread().getName(); | 可以获取任意线程的线程名称 |
Thread.currentThread().setName(); | 可以设置任意线程的线程名称 |
class NameRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
4.1继承Thread开启线程和实现Runnable开启线程的区别
- 继承Thread的方式可以更为方便地访问线程的API
- 继承Thread方式如果一旦某个类已经继承了一个类,那么就不能够再继承Thread类 【单一继承性】
5.调度模型
Java使用的是抢占式调度模型
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 (并不是优先级高的先执行,只是过去执行权的可能性大)
- 设置和获取线程的优先级
说明 | |
---|---|
public final int getPriority() | |
public final void setPriority(int newPriority) |
t1.setPriority(Thread.MAX_PRIORITY); //10
t2.setPriority(Thread.NORM_PRIORITY); //5
t3.setPriority(Thread.MIN_PRIORITY); //1
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
6.线程休眠
该方法是一个阻塞方法,会阻塞正在执行的线程
sleep方法是一个静态方法,可以让任意线程睡眠
- public static void sleep(long millis)
// 使用线程休眠模拟时钟
class SleepRunnable implements Runnable {
@Override
public void run() {
while (true) {
String dateStr = new SimpleDateFormat("yyyy年MM月dd号 HH点mm分ss秒").format(new Date());
System.out.println("现在是北京时间: " + dateStr);
// 线程休眠
try {
Thread.sleep(100000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
6.1实现Runnable的类不能用thorws抛异常
- 因为Runnable没有抛出任何异常,子类抛出的异常不能大于父类,父类没有抛异常,子类也不能抛异常
6.2Thread & Runnable 不足
- Thread和Runnable没有异常,子类也不能有异常,所以run只能使用try…catch处理异常,不用抛异常
- run 方法的返回值类型是void,所以没有返回值
7.中断线程
说明 | |
---|---|
public final void stop() | |
public void interrupt() |
7.1面试题: stop和interrupt的区别
- stop:会中断子线程
- interrupt:主线程向子线程抛出一个异常 【InterruptedException】,但线程能继续执行下去
- 可以通过System.exit(0)来中断线程
8.后台线程
后台线程 / 守护线程 / 服务线程 / 非用户线程
一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程和后台线程。
说明 | |
---|---|
public final void setDaemon(boolean on) | 用户线程的设置 |
- 后台线程守护的是用户线程(后台线程不是不可或缺的)
- 用户线程存在则守护线程存在,用户线程死亡了,那么所有的守护该用户线程的线程都将死亡
9.线程加入
说明 | |
---|---|
public final void join() | 非静态方法,需要执行某个线程对象去执行,是让join之外的线程处于阻塞状态 |
- join需要加在某线程之后,而不是所有线程启动之后
10.线程礼让
唯一一个方法能够让线程从运行态到就绪态的方法
public static void yield() | 主动让出CPU的执行权,让出之后自己重新参与抢夺CPU资源的队列中重新抢 |
11.线程同步
线程同步的问题,简单来说根本原因就是 CPU在某一个时间刻度上只能够执行一条原子性语句
11.1程序模拟该火车站售票
11.1.1线程同步实现的三个问题
-
问题一: 继承Thread方式不能够共享同一份数据
需要对数据进行static修饰 -
问题二: 卖出了负票
窗口2正在出售第1张票!!!
窗口1正在出售第0张票!!!
窗口3正在出售第-1张票!!!
原因: 原子性语句导致的同时还对共享数据做了写操作 -
问题三: 卖出了同票
窗口1正在出售第53张票!!!
窗口3正在出售第53张票!!!
原因: 当多个线程争夺资源的时候,原子性语句导致的
11.1.1.1线程同步问题的产生原因:
- 必须存在多线程环境
- 必须多线程环境下存在多条原子性语句操作共享数据
- 并且对共享数据做写的操作
11.1.2解决方案
通过加锁【互斥锁 同步锁】可以解决以上不同步问题
- 同步代码块
//格式:
synchronized(对象){需要同步的代码;}
- 特点:
- 需要显示创建对象
- 不知道在哪里加锁,在哪里释放锁(在出了同步代码块之后)
// synchronized(对象){需要同步的代码;}
while (true) {
synchronized (MyLock.LOCKA) {//通过枚举,创建同一个锁对象
if (tickets > 0) {
// 模拟网络延迟
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
}
}
}
//枚举
enum MyLock {
LOCKA,LOCKB,LOCKC
}
- 同步方法
如果锁对象是this,就可以考虑使用同步方法
//格式:
public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
}
- 特点
- 不知道锁对象是谁
- 能够不用单独编写锁对象
- 不知道在哪里加锁,在哪里释放锁 (出了同步方法就等于解锁)
//public synchronized 返回值 方法名(参数列表) {
//需要同步的代码块
// }
public synchronized void sellTickets() {
if (tickets > 0) {
// 模拟网络延迟
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
}
}
- Lock锁 【JDK提供的一种锁对象】
- 特点:
- 明确锁对象
- 在何时加锁以及何时释放锁非常清晰,更加满足了面向对象思想
class SellTicketRunnable implements Runnable {
private int tickets = 100;
private Lock lock = new ReentrantLock();//
@Override
public void run() {
while (true) {
lock.lock();//
if (tickets > 0) {
// 模拟网络延迟
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
}
lock.unlock();//
}
}
}
11.2继承Thread和实现Runnable的区别?
好处
-
继承Thread的方式可以方便地访问线程的API
-
实现Runnable方式可以避免单继承带来的缺陷
-
实现Runnable方式可以在多个线程之间共享同一个Runnble接口,也就是说可以共享同一份数据【变量】和同一份代码【方法】
SellTicketRunnable sr = new SellTicketRunnable(); Thread t1 = new Thread(sr); Thread t2 = new Thread(sr); Thread t3 = new Thread(sr);
缺陷
- 继承Thread类每个线程都有自己的一份数据
- 继承Thread方式单一继承性
- 实现Runnable方式不能够直接访问线程的API,我们可以间接使用 Thread.currentThread() 这个方法来获取当前正在执行run方法的线程对象
- 然后通过这个对象来访问线程API。
11.2.1静态 / 非静态
静态方法: Class对象
- 当使用的是Thread继承,run()重写的方法需要加上static,使所有成员共享同一个数据
//因为Thread继承需要创建三个对象,所以需要static是方法静态,
//Class<SellTicketThread> c1 = SellTicketThread.class(锁对象)
//同一个类下,不管创建多少个对象,都共用同一个class对象(.class字节码文件)
SellTicketThread t1 = new SellTicketThread();
SellTicketThread t2 = new SellTicketThread();
SellTicketThread t3 = new SellTicketThread();
class SellTicketThread extends Thread {
private static int tickets = 100;
@Override
public void run() {
sellTickets();
}
public static synchronized void sellTickets() {
if (tickets > 0) {
// 模拟网络延迟
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
}
}
}
非静态方法: this
- 指调用类的对象
12.死锁
死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
public class DeadLockThread {
public static void main(String[] args) {
DieLock t1 = new DieLock(true);
DieLock t2 = new DieLock(false);
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t1.start();
t2.start();
}
}
class DieLock extends Thread {
private boolean flag;
public DieLock() {
super();
}
public DieLock(boolean flag) {
super();
this.flag = flag;
}
@Override
public void run() {
if (flag) {//1. DieLock t1 = new DieLock(true);调用MyLock.LOCKA,进行上锁,
//当CPU执行权被抢
synchronized(MyLock.LOCKA) {
System.out.println("if语句中的LOCKA锁");
synchronized (MyLock.LOCKB) {
System.out.println("if语句中的LOCKB锁");
}
}
} else {//2. DieLock t2 = new DieLock(false);调用MyLock.LOCKB,进行上锁,
synchronized(MyLock.LOCKB) {
System.out.println("else语句中的LOCKB锁");
synchronized (MyLock.LOCKA) {
System.out.println("else语句中的LOCKA锁");
}
}
}
}
}
//到时AB同时上锁,都在等待,B因为A上锁等待解锁,A因为B上锁等待解锁
//枚举,创建锁对象
enum MyLock {
LOCKA, LOCKB
}
13.未捕获异常
替代JVM,自行捕获未捕获异常
说明 | |
---|---|
static interface Thread.UncaughtExceptionHandler | 这个就是未捕获异常处理器 |
public class ThreadDemo01 {
public static void main(String[] args) {
//1.捕获所有异常
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
// 2.需求: 希望主线程未捕获异常自己处理 ,子线程还是虚拟机处理
Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName() + "线程出现了问题!");
System.out.println(e);
}
});
ExceptionThread t = new ExceptionThread();
t.setName("子线程");
// 3.需求: 希望子线程未捕获异常自己处理 ,主线程还是虚拟机处理
t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName() + "线程出现了问题!");
System.out.println(e);
}
});
t.start();
System.out.println("start");
int a = 10;
int b = 0;
System.out.println(a/b);
System.out.println("end");
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
/*
* t: 表示出现异常的线程对象
* e: 表示出现的异常信息
*/
System.out.println(t.getName() + "线程出现了问题!");
e.printStackTrace();
}
}
class ExceptionThread extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + ":" + i);
if (i == 10) {
System.out.println(i / 0);
}
}
}
}
14.线程组
ThreadGroup :为了更加方便批量管理线程
14.1线程组和线程池有关系吗?
- 没有关系,
- 单个线程也可以使用线程组
- 线程池也可以使用线程组进行管理
public class ThreadDemo {
public static void main(String[] args) {
ThreadGroup tg1 = new ThreadGroup("三国演义");
ThreadGroup tg2 = new ThreadGroup("西游记");
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(tg1, mr, "诸葛亮");
Thread t2 = new Thread(tg1, mr, "赵云");
Thread t3 = new Thread(tg1, mr, "吕布");
Thread t4 = new Thread(tg2, mr, "二郎神");
Thread t5 = new Thread(tg2, mr, "嫦娥");
Thread t6 = new Thread(tg2, mr, "铁扇公主");
System.out.println(t6.toString());
tg1.setDaemon(true);
System.out.println(tg1.getName());
// t1.start();
// t2.start();
// t3.start();
// t4.start();
// t5.start();
// t6.start();
//集合也可以对线程进行管理
List<Thread> threads = new ArrayList<Thread>();
threads.add(t1);
threads.add(t2);
threads.add(t3);
threads.add(t4);
threads.add(t5);
threads.add(t6);
for (Thread thread : threads) {
thread.start();
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "|" + i);
}
}
}
15.等待唤醒机制
线程通信的一种方式 wait() notify()
15.1为什么 wait和nofity这些方法不设计在线程Thread类里面,而设计在Object类中?
- 因为锁对象是任意对象,所以锁的类型是Object类型