我的后端学习大纲
我的Java学习大纲
1、方式1:继承Thread类:
1.1.实现步骤
- 1.创建一个
继承于Thred()类
的子类 - 2.
重写
Thread类的run() - 3.创
建
Thread类的子类
的对象 - 4.通过这个对象去调用
start()方法
在调用start方法时就做了两件事,分别是:
- 启动当前线程
- 调用当前线程的run()
1.2.代码实现:
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run(),把这个线程需要干的事情写在run方法中就可以,现在以循环100以内的数为例来写
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
package com.atguigu.java;
public class ThreadTest001 {
public static void main(String[] args) {
//3. 主线程创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start(),会执行如下两部操作:
// 启动当前线程
// 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
//t1.run();这种方式相当于是对象调方法,和多线程无关系
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
//t1.start();
//我们需要重新创建一个线程的对象来实现
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
1.3.分析代码中线程执行的情况:
1.4.案例:找出1到100以内的所有奇数和偶数
- 1.原始方式实现:
package com.jianqun.day09;
public class ThreadDemo {
public static void main(String[] args) {
//原始方式验证多线程:
MyThread1 myThread1 = new MyThread1();
MyThread2 myThread2 = new MyThread2();
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 != 0){
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}
- 2.
利用Thread类的匿名子类
的方式来实现:
package com.jianqun.day09;
public class ThreadDemo {
public static void main(String[] args) {
//利用Thread类的匿名子类的方式来测试多线程
//遍历奇数
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 != 0){
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}.start();
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2 == 0){
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}.start();
}
}
1.5.Thread中的常用方法:
a.常用方法介绍:
- 1.
start()
:启动当前线程;调用当前线程的run()
- 2.
run()
: 通常需要重写Thread类中的此方法
,将创建的线程要执行的操作声明在此方法中
- 3.
currentThread()
:是个静态方法,返回执行当前代码的线程
- 4.
getName()
:获取当前线程的名字
- 5.
setName()
:设置当前线程的名字
- 6.
yield():释放当前cpu的执行权
- 7.
join()
:在线程a中调用线程b的join(),
此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。 - 8.
stop()
:已过时。当执行此方法时,强制结束当前线程
。 - 9.
sleep(long millitime)
:让当前线程“睡眠”指定的millitime毫秒
。在指定的millitime毫秒时间内,当前线程是阻塞状态。 - 10.
isAlive()
:判断当前线程是否存活
b.编码测试方法:
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
// 子类重写的方法不能比父类抛的异常还大,父类如果没有抛异常,那么子类必定不能抛异常了
//try {
// sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
//}
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// 如果i除以20取模为0的话,释放当前cpu的执行权
//if(i % 20 == 0){
// yield();
//}
}
}
// 有参构造器方式给线程命名
// 定义了有参构造器后,无参构造器就没有了,所以类的实例化的时候,要传一个参数,这个参数的含义就是线程的名称
public HelloThread(String name){
super(name);
}
}
package com.atguigu.java;
/**
* @author: jianqun
* @email: 1033586391@qq.com
* @creat: 2022-04-08-22:35
* @Description:
*/
public class ThreadTest002 {
public static void main(String[] args) {
// 未声明HelloThread 的有参构造的时候,如下方式更改线程的名称
// HelloThread h1 = new HelloThread();
// h1.setName("线程一");
//利用有参构造器给线程名称命名
HelloThread h1 = new HelloThread("Thread:1");
//设置分线程的优先级
h1.setPriority(Thread.MAX_PRIORITY);
h1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i == 20){
// try {
// 在主线程中调用了子线程的join()方法,
// 此时主线程就进入阻塞状态,直到子线程完全执行完以后,主线程才结束阻塞状态
// h1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//}
}
// System.out.println(h1.isAlive());
}
}
1.6.线程的调度
a.调度策略:
- 策略1:时间片:
- 策略2:抢占式,
高优先级的线程抢占CPU
b.调度方法:
- 同优先级线程组成的
先进先出队列(先到先服务)
,使用时间片策略 - 对
高优先级,使用优先调度的抢占式策略
c.线程优先级:
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
d.涉及的方法
getPriority() :
返回线程优先级的值setPriority(int newPriority)
:改变线程的优先级
e.说明
- 线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
2、方式2:实现Runnable接口
2.1.线程创建的步骤:
- 1
.定义子类,实现Runnable接口
。 - 2
子类中重写Runnable接口中的run方法
- 3.创建实现类的对象secondTread :
SecondTread secondTread = new SecondTread();
- 4.将创建的实现类的这个对象
secondTread
作为参数传递到Thread类的含参构造器中,创建线程对象
。 - 5.
调用Thread类的start方法
:开启线程,去调用Runnable子类接口的run方法
class SecondTread implements Runnable{
// b `子类中重写Runnable接口中的run方法`。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "::" + i);
}
}
}
}
package com.atguigu.java;
/**
* @author: jianqun
* @email: 1033586391@qq.com
* @creat: 2022-04-09-9:29
* @Description: 第二种创建方式
*/
public class ThreadTest004 {
public static void main(String[] args) {
//c.创建实现类的对象
SecondTread secondTread = new SecondTread();
//d.将创建的实现类的这个对象secondTread作为参数传递到Thread类的含参构造器中,创建线程对象。
Thread t1 = new Thread(secondTread);
//e.调用Thread类的start方法
t1.setName("线程1");
t1.start();
//再启动一个线程的方式:
Thread t2 = new Thread(secondTread);
t2.setName("线程2");
t2.start();
}
}
2.2.案例分析:卖票问题
a.编码实现卖票:
- 上述代码中,卖票的三个窗口,每个窗口都卖100张票,共计300张票,但是其实就100张,卖超了;
private int ticket = 100
;这个会导致每个对象都有100张票,属性资源不共享
;
b.迭代1:改善代码:
- 1.解决办法是添加static,定义:
private static int ticket = 100;
,对象们共享一个静态变量,一共就100张票
package com.atguigu.java;
class Bwindow extends Thread{
//private int ticket = 100;//每个对象都有100张票,属性不共享
private static int ticket = 100;//对象们共享一个静态变量,一共就100张票,但是存在线程安全问题,会导致卖票重复
@Override
public void run() {
while(true){
if (ticket >0){
System.out.println(getName() + ":卖票,票号是::" + ticket);
ticket--;
}else{
break;
}
}
}
}
public class ThreadTest003 {
public static void main(String[] args) {
Bwindow bwindow1 = new Bwindow();
Bwindow bwindow2 = new Bwindow();
Bwindow bwindow3 = new Bwindow();
bwindow1.setName("窗口1");
bwindow2.setName("窗口2");
bwindow3.setName("窗口3");
bwindow1.start();
bwindow2.start();
bwindow3.start();
}
}
上述改善方式存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说
存在线程安全问题,会导致卖票重复
c.迭代2:改善代码:
class Bwindow implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
if (ticket >0){
System.out.println(getName() + ":卖票,票号是::" + ticket);
ticket--;
}else{
break;
}
}
}
}
package com.atguigu.java;
/**
* @author: jianqun
* @email: 1033586391@qq.com
* @creat: 2022-04-09-9:09
* @Description:
*/
public class ThreadTest003 {
public static void main(String[] args) {
Bwindow bwindow1 = new Bwindow();
// 创建线程:3个线程共用一个bwindow1对象,这样就共用了ticket 属性,就不用再加static了,但是还是会存在线程安全问题
Thread t1 = new Thread(bwindow1);
Thread t2 = new Thread(bwindow1);
Thread t3 = new Thread(bwindow1);
t1.start();
t2.start();
t3.start();
}
}
上述改善方式仍然存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说
存在线程安全问题,会导致卖票重复
2.3.两种线程创建方式的对比:
说明:上面说到的线程安全问题在此笔记中进行了分析和解决
3、方式3:实现Callable接口
3.1.与使用Runnable相比, Callable功能更强大些
- 1.相比run()方法,可以
有返回值
- 2.
方法可以抛出异常
- 3.
支持泛型的返回值
- 4.需
要借助FutureTask类
,比如获取返回结果
3.2.Future接口
- 1.
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
- 2.
FutrueTask是Futrue接口的唯一的实现类
- 3.FutureTask
同时实现了Runnable, Future接口
。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
3.3.实现过程:
- 1.创建一个实现callable接口的实现类
- 2.实现call方法,将此线程需要执行的操作声明在call()方法中
- 3.
创建Callable接口实现类的对象
- 4.将实现Callable接口实现类的对象作为参数传递到FutureTask构造器中,
创建FutureTask的对象
- 5.将FutureTask的对象作为参数传递到Thread类的构造器中,
创建Thread对象,并调用start()方法
3.4.代码举例:
- 如下代码中的get()方法的返回值是:
FutureTask构造器参数Callable实现类重写的call方法的返回值
:
3.5.Callable和Runnable对比:
3.6.泛型举例:
4、方式4:使用线程池
4.1.线程池背景:
- 1.经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 2.提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
4.2.好处:
- 1.提高响应速度(减少了创建新线程的时间)
- 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 3.便于线程管理
corePoolSize
:核心池的大小maximumPoolSize
:最大线程数keepAliveTime
:线程没有任务时最多保持多长时间后会终止- …
4.3.线程池相关API:
- 1.JDK 5.0起提供了线程池相关API:
ExecutorService
和Executors
- 2.ExecutorService:真正的线程池接口。常见子类
ThreadPoolExecutor
void execute(Runnable command)
:执行任务/命令,没有返回值,一般用来执行Runnable<T> Future<T> submit(Callable<T> task)
:执行任务,有返回值,一般又来执行Callablevoid shutdown()
:关闭连接池
- 3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool()
:创建一个可根据需要创建新线程的线程池Executors.newFixedThreadPool(n)
; 创建一个可重用固定线程数的线程池Executors.newSingleThreadExecutor()
:创建一个只有一个线程的线程池Executors.newScheduledThreadPool(n)
:创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
4.4.代码实现:
5、多线程创建中的继承方式和实现方式的联系与区别
5.1.区别
继承Thread
:线程代码存放Thread子类run方法中。实现Runnable
:线程代码存在接口的子类的run方法
5.2.实现Runnable接口方式
的好处:
Runnable
接口避免了单继承
的局限性:
Runnable方式
可以实现多个线程可以共享同一个接口实现类
的对象,非常适合多个相同线程来处理同一份资源
5.3.联系:
- Thread类也是实现了
Runnable
接口 - 两种方式
都需要重写run()
,将线程要执行的逻辑在run()
方法中声明
6、线程的启动
-
1.Java语言的
JVM允许程序运行多个线程
,它通过java.lang.Thread
类来体现
-
2.Thread类的特性
每个线程都是通过某个特定Thread对象的run()方法来完成操作的
,经常把run()方法的主体称为线程体
通过该Thread对象的start()方法来启动这个线程,而非直接调用run()