Java进阶八—多线程
Java进阶八—多线程
一.?问题的提出
1.一台计算机为何能够执行多个程序?它们是怎么执行多个程序的?
电脑可以同时做很多事情,一边聊天,一边听歌,一边上网查资料等,原因是电脑有多个核心(脑子),一个核心可以做一件事情,多个核心就可以做多件事情。而在很早的时候计算机CPU只有一个核心只能处理一件事情,所以科学家想出了一个办法,假如有两个应用,一个应用不使用时可将这个应用暂停(挂起),使用时打开将另一个应用暂停(挂起)。造成一种假象表面上CPU可以同时运行两个程序,实际上是进程切换的快,第一个进程打开,第二个进程挂起,给你一种错觉。得出:多核CPU可以同时执行多个进程,单核CPU就可以“同时”执行多个进程。
微观上说,多核CPU可以同时执行多个进程,进程数与CPU核数相当。但宏观上说,由于CPU会分时间片执行多个进程,所以实际执行进程个数会远多于CPU核数。
?2.那么核心与进程和线程有什么关系?
粗略理解:核心 > 一个进程或多个进程(同时)?> 一个线程或多个线程
具体理解:腾讯面试题04.进程和线程的区别?_面试题线程和进程-CSDN博客
3.什么是并发和并行与区别??
并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间 段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
区别:并发和并行是即相似又有区别的两个概念,并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机系统中,每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。倘若在计算机系统中有多个处理机,则这些可以并发执行的程序便可被分配到多个处理机上,实现并行执行,即利用每个处理机来处理一个可并发执行的程序,这样,多个程序便可以同时执行。
二.两种方法实现多线程?
1.通过继承Thread类?
package com.thread;
// 继承多线程Thread类
public class DemoTest extends Thread{
// 重新run方法
@Override
public void run() {
while (true) {
System.out.println("one");
}
}
}
package com.thread;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
// start()方法多开启一个线程,然后自动调用run()
demoTest.start();
while (true) {
System.out.println("two");
}
}
}
2.通过实现接口Runnable?
package com.thread;
public class DemoTest implements Runnable{
// 重新run方法
@Override
public void run() {
while (true) {
System.out.println("one");
}
}
}
package com.thread;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
// 使用接口Runnable,需要创建一个Thread类
Thread thread = new Thread(demoTest);
// start()方法多开启一个线程,然后自动调用run()
thread.start();
while (true) {
System.out.println("two");
}
}
}
写法二:简化操作以及线程名?
package com.thread;
public class DemoTest implements Runnable{
// 重新run方法
@Override
public void run() {
while (true) {
// getName()设置线程名
System.out.println("one" +Thread.currentThread().getName());
}
}
}
package com.thread;
import com.microsoft.bean.Test;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
new Thread(demoTest," Fuck_one").start();
while (true) {
System.out.println("two");
}
}
}
补充:匿名内部类创建多线程——你们老师喜欢的???
package com.thread;
public class DemoTwo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("one");
}
}
}).start();
while (true) {
System.out.println("two");
}
}
}
三.多线程的执行过程?
- 一般的程序是从
main
出发,直线向下进行,只有一条主线 - 多线程在
main
主线程序遇到线程程序时会转到线程程序,并返回到主线程序中,这样main程序和线程程序同时执行
?
四.案例,抢鞋
抢鞋的逻辑代码涵盖在线程当中,假设有10双鞋,有三个人来抢,一个线程就是一个用户,所以这就有三个名称不一样的线程名。(三个人抢占共同资源10双鞋)
package com.thread;
public class DemoTest implements Runnable{
public int nike = 10;
@Override
public void run() {
while (true) {
if (nike >0 ) {
System.out.println(Thread.currentThread().getName() +"还剩"+(--nike)+"双鞋");
}
}
}
}
package com.thread;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
new Thread(demoTest,"one").start();
new Thread(demoTest,"two").start();
new Thread(demoTest,"three").start();
}
}
五.后台、守护进程的提出?
?
图中可知线程分为前台线程(应用)与后台进程(后台进程)。
- 是前台进程,Background processes是后台进程也叫守护进程,这些进程在电脑开机时就被启动,这样电脑才能正常且安全的运作起来,在程序中也是同理
- 与进程同理,前台线程为用户提供服务,也有后台线程为前台线程提供的服务进行保护或者守护
?后台线程的创建过程
package com.thread;
public class DaemoTest implements Runnable{
@Override
public void run() {
System.out.println("守护线程开启");
}
}
package com.thread;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
DaemoTest daemoTest = new DaemoTest();
Thread dThread = new Thread(daemoTest);
// 后台线程(守护线程)--维护前台线程
// 开启
dThread.setDaemon(true);
dThread.start();
// 判断是否开启
System.out.println(dThread.isDaemon());
// 前台线程--应用
new Thread(demoTest,"one").start();
new Thread(demoTest,"two").start();
new Thread(demoTest,"three").start();
}
}
六.发现问题,提出synchronized的概念和用途
但在现实情况,人抢鞋是有延迟的,你不可能像程序一样一秒钟不到就抢完了,所以有使用sleep()方法。
package com.thread;
public class DemoTest implements Runnable{
public int nike = 10;
@Override
public void run() {
while (true) {
try {
// 设置延迟为500毫秒,等于半秒种
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (nike >0 ) {
System.out.println(Thread.currentThread().getName() +"还剩"+(--nike)+"双鞋");
}
}
}
}
package com.thread;
public class Demo {
public static void main(String[] args) {
DemoTest demoTest = new DemoTest();
new Thread(demoTest,"one").start();
new Thread(demoTest,"two").start();
new Thread(demoTest,"three").start();
}
}
?
可以从运行结果看出,one和two都抢到第六双鞋,three抢到第零双鞋。
出现这样的原因,是因为线程不同步,线程不安全。可以使用synchronized
锁对象,同步数据。
package com.thread;
public class DemoTest implements Runnable{
public int nike = 10;
// 创建synchronized锁对象
Object lock = new Object();
@Override
public void run() {
while (true) {
// 同步数据,同步代码块
synchronized (lock) {
try {
// 设置延迟为500毫秒,等于半秒种
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (nike > 0) {
System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
}
}
}
}
}
synchronized
锁的原理:
如何理解锁呢?当用户一抢到第一双鞋时,锁住第一双鞋,其它用户就无法抢了上面的synchronized
锁的使用方法为同步代码块,还有一种为同步方法(推荐使用)。
package com.thread;
public class DemoTest implements Runnable{
public int nike = 10;
// 创建synchronized锁对象
Object lock = new Object();
@Override
public void run() {
while (true) {
nikeCatch();
}
}
// 同步方法,推荐使用,清晰可观
public synchronized void nikeCatch() {
try {
// 设置延迟为500毫秒,等于半秒种
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (nike > 0) {
System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
}
}
}
七.Lock、ReentrantLock同步锁?
相比synchronized锁,其实还有更好的锁,名为同步锁。
package com.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DemoTest implements Runnable{
public int nike = 10;
// 创建同步锁,Lock是一个接口,所以要使用它的实现类
Lock reentrantLock = new ReentrantLock();
@Override
public void run() {
while (true) {
// 上锁
reentrantLock.lock();
try {
if (nike > 0) {
// 设置延迟为500毫秒,等于半秒种
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + "还剩" + (--nike) + "双鞋");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
// 解锁
reentrantLock.unlock();
}
}
}
}
?
比较synchronized与reentrantLock区别
- JDK1.5中,synchroized是重量级操作,性能低效,Lock性能高,更稳定
- JDK1.6中,synchroized加入很多优化,更加稳定了
锁的释放
- synchronized以获取锁的线程执行完同步代码,如果线程执行发生异常,jvm会让线程释放锁
- Lock在finally中必须释放锁,不然容易造成线程死锁
死锁产生
- synchronized在发生异常时候会自动释放占有的锁,不会出现死锁
- Lock发生异常时候,不会主动释放,必须手动unlock来释放锁,可能引起死锁的发生
用法
- synchronized在需要同步的对象中加入,可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象
- Lock一般使用ReentrantLock类做为锁,通过lock()加锁和unlock()解锁指出,在finally中写unlock()防止死锁
?
八.Thread API说明
?Thread (Java Platform SE 7 ) (oracle.com)
九.CPU线程调度、Priority线程优先级、优先级常量、剩余小问题?
CPU线程调度
- 每一个线程的优先使用权都是系统随机分配的,人人平等,谁先分配到谁先用
- 可以设置优先级赋予某一个线程拥有至高适用权,最高为10,最低为1,默认为5,Java可以抢占CPU
线程1-10中,main()
主线程的value = 5,创建?MaxPriorityThread
?类和MinPriorityThread
来查看线程执行顺序?
package com.thread;
public class MaxPriorityThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
package com.thread;
public class MinPriorityThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
package com.thread;
public class Mani {
public static void main(String[] args) {
Thread maxThread = new Thread(new MaxPriorityThread(),"max");
Thread minThread = new Thread(new MinPriorityThread(),"min");
// 使用setPriority()方法即可越权
maxThread.setPriority(Thread.MAX_PRIORITY);
minThread.setPriority(Thread.MIN_PRIORITY);
maxThread.start();
minThread.start();
}
}
?
我上面的结果是对的,但是有些系统的结果跟我不一样。原因是你的CPU调度程序非常快,程序还没有反应过来程序就结束了。
十.?join线程插队?
上面的CPU调度,如果你把main加进去,你会发现无论main在哪里都会优先调度。?
package com.thread;
public class Mani {
public static void main(String[] args) {
Thread maxThread = new Thread(new MaxPriorityThread(),"max");
Thread minThread = new Thread(new MinPriorityThread(),"min");
// 使用setPriority()方法即可越权
maxThread.setPriority(Thread.MAX_PRIORITY);
minThread.setPriority(Thread.MIN_PRIORITY);
maxThread.start();
minThread.start();
for (int i = 1; i < 8; i++) {
System.out.println(Thread.currentThread().getName());
}
}
}
但是我们可以使用join()方法,进行插队。
package com.thread;
public class Mani {
public static void main(String[] args) throws InterruptedException{
Thread maxThread = new Thread(new MaxPriorityThread(),"max");
Thread minThread = new Thread(new MinPriorityThread(),"min");
// 使用setPriority()方法即可越权
maxThread.setPriority(Thread.MAX_PRIORITY);
minThread.setPriority(Thread.MIN_PRIORITY);
maxThread.start();
minThread.start();
for (int i = 1; i < 8; i++) {
System.out.println(Thread.currentThread().getName());
if (i == 3) {
// 使用时,需要抛出异常InterruptedException
maxThread.join();
}
}
}
}
?
十一.sleep线程休眠
?在上面抢鞋的案例中,说到过。
package com.thread;
public class Mani {
public static void main(String[] args) throws InterruptedException {
Thread maxThread = new Thread(new MaxPriorityThread(),"max");
Thread minThread = new Thread(new MinPriorityThread(),"min");
// 使用setPriority()方法即可越权
maxThread.setPriority(Thread.MAX_PRIORITY);
minThread.setPriority(Thread.MIN_PRIORITY);
// 需要抛出异常或捕获异常
maxThread.sleep(1000);
maxThread.start();
minThread.start();
}
}
十二.yield线程让步?
package com.thread;
public class YieldTest implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName());
// 每被5整除就让步
if (i % 5 == 0) {
System.out.println(Thread.currentThread().getName()+"------让步");
// 让步使用方法:yield()
Thread.yield();
}
}
}
}
package com.thread;
public class Main {
public static void main(String[] args) {
Thread yieldOne = new Thread(new YieldTest(),"one");
Thread yieldTwo = new Thread(new YieldTest(),"two");
yieldOne.start();
yieldTwo.start();
}
}
.yield()
方法可以实现线程让步,让其它线程执行,one输出一次的时候给two让步了,有时程序运行的太快了,以至于还没打印出让步输出,two已经输出完毕了?
十三. 线程状态还是斗地主吧
?
Java中线程的状态分为6种–以斗地主为例
1.新建(NEW)-新建一局游戏
2.可运行(RUNNABLE)-初始状态是可运行的
3.阻塞(BLOCKED)-谁出牌谁获得一个锁,导致阻塞,出好牌则疏通阻塞
4.等待(WAITING)-不出牌的等待通知
5.计时等待(TIMED_WAITING)-出牌时,其他人计时等待超时或通知
6.终止(TERMINATED)-游戏结束
?
十四.?发现实际问题,抛出线程通信的含义?
线程优先级
- Win11务管理器中,线程有6个优先级设置
- 线程的调度目的就是通知另一个线程去执行
?线程通信
线程通信就如斗地主一名玩家出完牌,要通知其他两位玩家出了什么牌。?
十五.线程的通信:wait和notify
线程通信有两种状态等待(wait)和唤醒(notify),我们接下来以nike生产者与消费者为例子;
产品:nike
生产者:Producer
消费者:Customer?
package com.thread;
public class Nike {
// 判断nike是否有库存
public boolean isStatus = false;
}
package com.thread;
public class Producer extends Thread{
// 设置synchronized锁类型为Nice
private Nike nike;
public Producer(Nike nike) {
this.nike = nike;
}
@Override
public void run() {
while (true) {
// synchronized锁为nike,以产品nike同步
synchronized (nike) {
// nike没有库存了,打电话给生产者,消费者等待。
if (nike.isStatus == false) {
try {
// 消费者等待使用:wait()方法
nike.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// nike买完了
nike.isStatus = false;
// 唤醒其他线程,唤醒生产者
System.out.println(Thread.currentThread().getName()+"赶紧生产");
// 唤醒生产者使用:notify()方法
nike.notify();
}
}
}
}
package com.thread;
public class Customer extends Thread{
private Nike nike;
// 设置synchronized锁类型为Nice
public Customer(Nike nike) {
this.nike = nike;
}
@Override
public void run() {
while (true) {
// synchronized锁为nike,以产品nike同步
synchronized (nike) {
// 库存充足,生产者等待
if (nike.isStatus == true) {
try {
// 生产者等待使用:wait()方法
nike.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
// nike生产好了
nike.isStatus = true;
// 唤醒其他线程,唤醒消费者
System.out.println(Thread.currentThread().getName()+"赶紧购买");
// 唤醒消费者使用:notify()方法
nike.notify();
}
}
}
}
package com.thread;
public class Main {
public static void main(String[] args) {
Nike nike = new Nike();
new Producer(nike).start();
new Customer(nike).start();
}
}
十六.notifyAll?
如果你有多个生产者或消费者要唤醒,那就使用notifyAll()方法?
// nike买完了
nike.isStatus = false;
// 唤醒其他线程,唤醒生产者
System.out.println(Thread.currentThread().getName()+"赶紧生产");
// 唤醒全部的生产者使用:notifyAll()方法
nike.notifyAll();
// nike生产好了
nike.isStatus = true;
// 唤醒其他线程,唤醒消费者
System.out.println(Thread.currentThread().getName()+"赶紧购买");
// 唤醒全部的消费者使用:notifyAll()方法
nike.notifyAll();
十七.提及Process进程
我们上面讲的都是线程,而进程可以看以下的文档简单了解
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!