之多线程"/>
JavaSE总复习之多线程
文章目录
- 多线程概念
- 实现线程的三种方式
- 线程的生命周期
- 线程控制问题
- 线程的优先级
- 线程休眠与中断
- 线程的让步与插队
- 如何正确的停止一个线程
- 线程同步与死锁
- 线程死锁
- 守护线程
- 定时器
- wait()和notify()
多线程概念
线程指进程中的一个执行场景
- 每个进程是一个应用程序,都有独立的内存空间
- 同一个进程中的线程共享其进程中的内存和资源
共享的内存是堆内存和方法区内存,栈内存不共享
实现线程的三种方式
Java 虚拟机的主线程入口是 main 方法,用户可以自己创建线程,创建方式有三种:
- 继承 Thread 类
- 实现 Runnable 接口(推荐使用 Runnable 接口)
- 实现 Callable 接口
继承 Thread 类
编写一个类,直接继承java.lang.Thread,重写run方法
- 优点: 编写简单, 可以在子类中增加新的成员变量, 使线程具有某种属性, 也可以在子类中增加方法, 使线程具有某种功能。
- 缺点: 因为线程类已经继承了 Thread 类, 所以不能再继承其他的父类。
class MyThread extends Thread {@Overridepublic void run() {// 编写程序,这段程序运行在分支线程中for(int i = 0; i < 1000; i++){System.out.println("分支线程--->" + i);}}
}
public class ThreadTest {public static void main(String[] args) {// 新建一个分支线程对象MyThread t = new MyThread();// 再新建一个分支线程对象MyThread t1 = new MyThread();// 启动线程t.start(); t1.start(); for(int i = 0; i < 1000; i++){System.out.println("主线程--->" + i);}}
}
- start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间
- start()方法就结束了。线程就启动成功了
- 启动成功的线程会自动调用run方法
实现 Runnable 接口
- 优点: 线程类只是实现了 Runable 接口, 还可以继承其他的类。 在这种方式下,可以多个线程共享同一个目标对象, 所以非常适合多个相同线程来处理同一份资源的情况, 从而可以将 CPU 代码和数据分开, 形成清晰的模型, 较好地体现了面向对象的思想。
- 缺点: 编程稍微复杂。
class MyRunnable implements Runnable {@Overridepublic void run() {for(int i = 0; i < 100; i++){System.out.println("分支线程--->" + i);}}
}
public class ThreadTest {public static void main(String[] args) {// 创建一个可运行的对象MyRunnable r = new MyRunnable();// 将可运行的对象封装成一个线程对象Thread t = new Thread(r);// 启动线程t.start();for(int i = 0; i < 100; i++){System.out.println("主线程--->" + i);}}
}
【运行结果(部分)】
主线程--->97
分支线程--->86
主线程--->98
分支线程--->87
主线程--->99
分支线程--->88
分支线程--->89
分支线程--->90
分支线程--->91
或者使用匿名内部类
//匿名内部类
Thread t = new Thread(new Runnable(){@Overridepublic void run() {for(int i = 0; i < 100; i++){System.out.println("t线程---> " + i);}}
});
//lambda表达式
Thread t1 = new Thread(()->{for(int i = 0; i < 100; i++){System.out.println("t线程---> " + i); }
});
实现 Callable 接口
- 这种方式的优点:可以获取到线程的执行结果
- 这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低
public class ThreadTest {public static void main(String[] args) throws Exception {// 第一步:创建一个“未来任务类”对象。// 参数非常重要,需要给一个Callable接口实现类对象。FutureTask task = new FutureTask(new Callable() {// call()方法就相当于run方法。只不过这个有返回值@Overridepublic Object call() throws Exception { // 模拟执行System.out.println("call method begin");Thread.sleep(1000 * 3);//睡觉3秒System.out.println("call method end!");int a = 100,b = 200;return a + b; //自动装箱(300结果变成Integer)}});// 创建线程对象Thread t = new Thread(task);// 启动线程t.start();// 在主线程中,怎么获取t线程的返回结果?// get()方法的执行会导致“当前线程阻塞”Object obj = task.get();// main方法这里的程序要想执行必须等待get()方法的结束System.out.println("线程执行结果:" + obj);}
}
线程的生命周期
线程的生命周期存在五个状态:新建、就绪、运行、阻塞、死亡
新建:采用 new语句创建完成
就绪:执行 start() 后
运行:占用 CPU 时间
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁、等待输入的场合
终止:退出 run()方法
线程控制问题
多线程中常用方法
返回值类型 | 方法名 | 描述 |
---|---|---|
static Thread | currentThread() | 怎么获取当前线程对象 |
String | getName() | 获取线程对象的名字 |
void | setName(String name) | 修改线程对象的名字 |
static void | sleep(long millis) | 让当前线程进入休眠 |
void | interrupt() | 终止线程的睡眠 |
void | setPriority(int newPriority) | 设置线程的优先级 |
static | yield() | 让位给其它线程 |
void | join() | 合并线程 |
void | setDaemon(boolean on) | 设置守护线程 |
线程的优先级
处于就绪状态的线程首先进入就绪队列排队等候 CPU 资源, 同一时刻在就绪队列中的线程可能有多个。 Java 虚拟机中的线程调度器负责管理线程, 调度器把线程的优先级分为10 个级别, 分别用 Thread 类中的常量表示。 每个 Java 线程的优先级都在常数 1 和 10 之间线程的优先级分
- 最高优先级:Thread.MAX_PRIORITY (对应优先级参数为10)
- 最低优先级:Thread.MIN_PRIORITY (对应优先级参数为1)
- 默认优先级:Thread.NORM_PRIORITY (对应优先级参数为5)
线程的优先级可以通过 setPriority(int grade)方法调整。getPriority ()方法返回线程的优先级
线程休眠与中断
Thread 的 sleep( )方法能使当前线程暂停运行一段时间 (单位: 毫秒) 需要注意的是, sleep( )方法的参数不能为负, 否则会抛出 IllegalArgumentException 异常。 而 Thread 的 interrupt( )方法经常用来 “吵醒” 休眠的线程。
休眠 sheep()
static void sleep(long millis)
1、静态方法:Thread.sleep(1000);
2、参数是毫秒
3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用
这行代码出现在A线程中,A线程就会进入休眠
这行代码出现在B线程中,B线程就会进入休眠
4、Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
public class ThreadTest {public static void main(String[] args) {Thread t = new MyThread();t.setName("t");t.start();try {// 问题:这行代码会让线程t进入休眠状态吗?// 这样代码出现在main方法中,main线程睡眠t.sleep(1000 * 5); // 在执行的时候还是会转换成:Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}// 5秒之后这里才会执行。System.out.println("hello World!");}
}class MyThread extends Thread {public void run(){for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}
中断 interrupt()
这个不是终断线程的执行,是终止线程的睡眠
【案例】
模拟一个火车站的售票窗口, 有两个线程售票员 ticketSeller 和乘客 passenger, 因为没人买票, 售票员决定休息 30 分钟, 这时有个乘客过来买票,吵醒休眠的售票员
public class Main {public static void main(String[] args) throws InterruptedException {WaitingRoom waitRoom = new WaitingRoom();waitRoom.ticketSeller.start();//售票员决定休息Thread.sleep(5*1000);//过了5秒钟来了个乘客waitRoom.passenger.start();//乘客买票}
}class WaitingRoom implements Runnable {Thread ticketSeller;//售票员线程Thread passenger;//乘客线程public WaitingRoom() {ticketSeller = new Thread(this);passenger = new Thread(this);ticketSeller.setName("售票员");passenger.setName("乘客");}public void run() {if (Thread.currentThread() == ticketSeller) {try {System.out.println(ticketSeller.getName() + "决定休息 30 分钟");Thread.sleep(1000 * 60 * 30);} catch (InterruptedException e) {System.out.println(ticketSeller.getName() + "被叫醒了!");}System.out.println(ticketSeller.getName() + "开始卖票");} else if (Thread.currentThread() ==passenger){System.out.println("乘客说:买票");ticketSeller.interrupt();//吵醒 ticketSeller}}
}
线程的让步与插队
让步 yield()
线程的让步就是让正在执行的任务暂停, 使其他任务继续执行。当前线程暂停,回到就绪状态。yield( )方法不会阻塞该线程, 之后该线程与其他线程是相对公平的。 这就好比篮球赛两队同学互相抢篮球, 当某个同学抢到篮球后就可以拍一会, 之后他会把篮球让出来, 大家重新开始抢篮球。
public class Main {public static void main(String[ ] args){Basketball basketball = new Basketball( );basketball.playerOne.start( );basketball.playerTwo.start( );}
}class Basketball implements Runnable {Thread playerOne, playerTwo;public Basketball( ){playerOne = new Thread(this);playerTwo = new Thread(this);playerOne.setName("红方");playerTwo.setName("蓝方");}public void run( ){for(int i = 1;i<= 5;i++){System.out.println(Thread.currentThread( ).getName( )+"拍了第"+i+"下");Thread.yield( ); //线程让步}}
}
【运行结果】
蓝方拍了第1下
蓝方拍了第2下
红方拍了第1下
蓝方拍了第3下
红方拍了第2下
蓝方拍了第4下
红方拍了第3下
蓝方拍了第5下
红方拍了第4下
红方拍了第5下Process finished with exit code 0
插队 join()
线程插队是通过 join( )方法阻塞当前线程, 先完成被 join( )方法加入的线程, 之后再完成其他线程。 使用线程插队 join( )方法时, 需要抛出 InterruptedException 异常。
【案例】
在火车站买票的时候, 有的乘客着急赶火车, 会插到队伍前面先买车票, 其他乘客再买票。 那么在多线程程序中, 也可以通过线程插队, 让插队的线程先执行完, 然后本线程才开始执行。
public class Main {public static void main(String[] args) {ThreadJoin join = new ThreadJoin();join.passenger.start();}
}class ThreadJoin implements Runnable {Thread passenger;//正常排队的线程Thread joinPassenger; //插队的线程public ThreadJoin() {passenger = new Thread(this);joinPassenger = new Thread(this);passenger.setName("排队线程");joinPassenger.setName("插队线程");}public void run() {if (Thread.currentThread() == passenger) {System.out.println(passenger.getName() + "想买票");joinPassenger.start();try {joinPassenger.join(); //当前排队线程等待插队线程完成买票} catch (InterruptedException e) {e.printStackTrace();}System.out.println(passenger.getName() + "开始买票");} else if (Thread.currentThread() == joinPassenger) {System.out.println(joinPassenger.getName() + "说:我着急,请让我先买票。");System.out.println(joinPassenger.getName() + "买票中…");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(joinPassenger.getName() + "买票完毕");}}
}
【运行结果】
排队线程想买票
插队线程说:我着急,请让我先买票。
插队线程买票中…
插队线程买票完毕
排队线程开始买票Process finished with exit code 0
如何正确的停止一个线程
public class ThreadTest10 {public static void main(String[] args) {MyRunable4 r = new MyRunable4();Thread t = new Thread(r);t.start();// 模拟5秒try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}//t.stop();已过时(不建议使用)// 终止线程(正确方式)r.run = false;}
}class MyRunable4 implements Runnable {boolean run = true;public void run() {for (int i = 0; i < 10; i++){if(run){System.out.println(Thread.currentThread().getName() + "--->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}else{//save....//终止当前线程return;}}}
}
线程同步与死锁
线程同步
线程同步,指某一个时刻,指允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量
【案例】
假设去买火车票, 一趟列车的车票数量是固定的, 不管有多少个地方可以买火车票, 买的一定是这些固定数量的火车票。 如果把各个售票点理解为线程的话, 则所有线程应该共同拥有同一份票数。
不加锁前
public class Main {public static void main(String[] args) {MyThread mt = new MyThread();//定义线程对象Thread t1 = new Thread(mt);//定义 Thread 对象Thread t2 = new Thread(mt);//定义 Thread 对象Thread t3 = new Thread(mt);//定义 Thread 对象t1.start();t2.start();t3.start();}
}class MyThread implements Runnable {private int ticket = 5; //假设一共有 5 张票public void run() {while (ticket > 0) { //还有票try {Thread.sleep(100); //加入延迟} catch (InterruptedException e) {e.printStackTrace();}System.out.println("卖票:ticket = " + ticket--);}}
}
【运行结果】
卖票:ticket = 5
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 3
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 0
卖票:ticket = 1Process finished with exit code 0
很明显卖出去的票大于5张,如何解决以上的问题 ?
- 1、使用同步代码块实现同步
public void run() {synchronized (this){//要对当前对象进行同步while (ticket > 0) {try {Thread.sleep(300);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("卖票:ticket = " + ticket--);}}
}
- 2、使用同步方法实现同步
public synchronized void run() {while (ticket > 0) { //还有票try {Thread.sleep(300); //加入延迟} catch (InterruptedException e) {e.printStackTrace();}System.out.println("卖票:ticket = " + ticket--);}
}
【修改后的运行结果】
卖票:ticket = 5
卖票:ticket = 4
卖票:ticket = 3
卖票:ticket = 2
卖票:ticket = 1Process finished with exit code 0
对象锁与类锁
在实例方法上使用synchronized表示共享对象一定是this
在静态方法上使用synchronized表示找类锁。类锁永远只有1把。
- 对象锁:1个对象1把锁,100个对象100把锁。
- 类锁:100个对象,也可能只是1把类锁。
怎么解决线程安全问题
- 第一种方案:尽量使用局部变量代替实例变量和静态变量
- 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
- 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制
线程死锁
资源共享时需要进行同步操作, 程序中过多的同步会产生死锁。 比如, 张三想要李四的画,李四想要张三的书, 张三对李四说: “先把你的画给我, 我就给你书。” 李四也对张三说: “先把你的书给我, 我就给你画。” 此时, 张三等李四答复, 而李四也等张三答复, 那么这样下去最终结果就是张三得不到李四的画, 李四也得不到张三的书, 这实际上就是死锁。
public class DeadLock {public static void main(String[] args) {Object picture = new Object();Object book = new Object();// t1和t2两个线程共享picture,bookThread zs = new MyThread1(picture,book);Thread ls = new MyThread2(picture,book);zs.start();ls.start();}
}class MyThread1 extends Thread{Object picture;Object book;public MyThread1(Object picture,Object book){this.picture = picture;this.book = book;}public void run(){synchronized (picture){try {System.out.println("张三对李四说:“你给我画,我就把书给你”");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (book){System.out.println("张三得到画");}}}
}class MyThread2 extends Thread {Object picture;Object book;public MyThread2(Object picture,Object book){this.picture = picture;this.book = book;}public void run(){synchronized (book){try {System.out.println("李四对张三说:“你给我书,我就把画给你”");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (picture){System.out.println("李四得到书了");}}}
}
【运行结果】
张三对李四说:“你给我画,我就把书给你”
李四对张三说:“你给我书,我就把画给你”Process finished with exit code -1
张三永远得不到画,李四永远得不到画
守护线程
所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束。
public class ThreadTest {public static void main(String[] args) {Thread t = new BakDataThread();t.setName("备份数据的线程");// 启动线程之前,将线程设置为守护线程t.setDaemon(true);t.start();// 主线程:主线程是用户线程for(int i = 0; i < 10; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}class BakDataThread extends Thread {public void run(){int i = 0;// 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。while(true){System.out.println(Thread.currentThread().getName() + "--->" + (++i));try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
定时器
定时器的作用:间隔特定的时间,执行特定的程序。
public class TimerTest {public static void main(String[] args) throws Exception {// 创建定时器对象Timer timer = new Timer();//Timer timer = new Timer(true); 守护线程的方式SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");Date firstTime = sdf.parse("2020-03-14 09:34:30");//timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);// 指定定时任务}
}// 编写一个定时任务类
// 假设这是一个记录日志的定时任务
class LogTimerTask extends TimerTask {public void run() {// 编写你需要执行的任务就行了。SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");String strTime = sdf.format(new Date());System.out.println(strTime + ":成功完成了一次数据备份!");}
}
wait()和notify()
- wait和notify方法不是线程对象的方法,是普通java对象都有的方法
- wait方法和notify方法建立在线程同步的基础之上。因为多线程要同时操作一个仓库。有线程安全问题
- wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
- notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁
【案例】
模拟仓库我们采用List集合。List集合中假设只能存储1个元素。1个元素就表示仓库满了。如果List集合中元素个数是0,就表示仓库空了。保证List集合中永远都是最多存储1个元素。
public class ThreadTest {public static void main(String[] args) {// 创建1个仓库对象,共享的。List list = new ArrayList();// 生产者线程Thread t1 = new Thread(new Producer(list));// 消费者线程Thread t2 = new Thread(new Consumer(list));t1.setName("生产者线程");t2.setName("消费者线程");t1.start();t2.start();}
}// 生产线程
class Producer implements Runnable {private List list;public Producer(List list) {this.list = list;}public void run() {while(true){synchronized (list){if(list.size() > 0){ // 说明仓库中已经有1个元素了try {// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到这里说明仓库是空的,可以生产Object obj = new Object();list.add(obj);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒消费者进行消费list.notifyAll();}}}
}// 消费线程
class Consumer implements Runnable {private List list;public Consumer(List list) {this.list = list;}public void run() {while(true){synchronized (list) {if(list.size() == 0){// 仓库已经空了try {// 消费者线程等待,释放掉list集合的锁list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序能够执行到此处说明仓库中有数据,进行消费。Object obj = list.remove(0);System.out.println(Thread.currentThread().getName() + "--->" + obj);// 唤醒生产者生产。list.notifyAll();}}}
}
【运行结果(部分)】
生产者线程--->java.lang.Object@2bf1d4e6
消费者线程--->java.lang.Object@2bf1d4e6
生产者线程--->java.lang.Object@7f05c69
消费者线程--->java.lang.Object@7f05c69
生产者线程--->java.lang.Object@4a569b99
消费者线程--->java.lang.Object@4a569b99
生产者线程--->java.lang.Object@1174068f
消费者线程--->java.lang.Object@1174068f
生产者线程--->java.lang.Object@3407b6a3
消费者线程--->java.lang.Object@3407b6a3
生产者线程--->java.lang.Object@3111b75e
消费者线程--->java.lang.Object@3111b75e
生产者线程--->java.lang.Object@4f6568b6
消费者线程--->java.lang.Object@4f6568b6
生产者线程--->java.lang.Object@7e944fab
消费者线程--->java.lang.Object@7e944fab
生产者线程--->java.lang.Object@772f5c74
消费者线程--->java.lang.Object@772f5c74
生产者线程--->java.lang.Object@4b38be88
消费者线程--->java.lang.Object@4b38be88
生产者线程--->java.lang.Object@2ffc5f81Process finished with exit code -1
notifyAll()方法:o.notifyAll()这个方法是唤醒o对象上处于等待的所有线程
更多推荐
JavaSE总复习之多线程
发布评论