-
3)匿名内部类
-
4)使用lambda表达式来创建
-
二、了解Thread 类
-
- 2.1Thread的常见的构造方法
-
2.2Thread的几个常见的属性
-
三、启动一个线程
-
四、中断一个线程
-
- 4.1 让线程的入口方法执行完毕
-
4.2 使用Thread类提供的interrupt方法
-
五、等待线程
-
六、线程休眠
-
七、线程的状态
-
八、线程安全(重要)!!!
-
- 8.1导致线程不安全的原因:
-
8.2解决线程安全问题方法
-
- synchronized
-
volatile
-
九、对象等待集
=============================================================================
利用多态机制,继承于Thread机制
**1)创建一个子类,继承于Thread
2)重写run 方法
3)创建子类实例
4)调用start 方法**
public class Demo {
//创建多线程
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
myThread2.start();
}
}
class MyThread2 extends Thread{
@Override
public void run() {
//这里写线程要执行的代码
}
}
通过实现Runnable 接口,把Runnable 接口的实例赋值给Thread
**1)定义Runnable接口的实现类
2)创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象
3)调用start () 方法**
public class Demo {
//创建多线程
public static void main(String[] args) {
//通过Runnable 接口来创建
Runnable myTask=new MyTask();
Thread t=new Thread();
t.start();
}
}
class MyTask implements Runnable{
@Override
public void run() {
//重写run()方法
}
}
Runnable 本质上还是要搭配Thread来使用,只不过和直接继承Thread相比,换了一种指定任务的方式而已
这两种方式中Runnable 方式更好一点,能够让线程本身,和线程要执行的任务,更加“解耦合”
通过匿名内部类相当于继承了Thread,作为子类重写run()实现
public class Demo {
//创建多线程
public static void main(String[] args) {
//通过匿名内部类来实现
Thread t=new Thread(){
@Override
public void run() {
//重写run方法
}
};
t.start();
}
}
通过Runnable 匿名内部类来实现
public class Demo {
//创建多线程
public static void main(String[] args) {
//通过匿名内部类来实现
Thread t=new Thread(new Runnable() {
@Override
public void run() {
//重写run方法
}
});
t.start();
}
}
public class Demo {
//创建多线程
public static void main(String[] args) {
Thread t=new Thread(()->{
//编写线程代码
}
);
t.start();
}
}
()->{ }这个就是lambda表达式
===============================================================================
| Thread() | 创建线程对象 |
| — | — |
| Thread(Runnable target) | 使用Runnable对象创建线程对象 |
| Thread(String name) | 创建线程对象并命名 |
| Thread(Runnable target,String name) | 使用Runnable对象来创建线程,并命名 |
| ID | .getId() |
| — | — |
| 名称 | .getName() |
| 优先级 | .getPriority() |
| 状态 | .getState() |
| 是否后台线程 | .isDaemon() |
| 是否存活 | .isAlive() |
| 是否被中断 | .isInterrupted |
| 获取当前线程的实例 | currentThread() |
优先级和线程调度有关,由操作系统来完成。
后台线程,不影响整个进程的结束
前台线程,会影响到整个进程的结束
是否存活就是run()方法是否运行结束了
===========================================================================
start
start 是Thread类的一个关键方法
功能:让操作系统内核真正创建一个线程来执行
start 和run 的区别
start 是创建线程(有新的执行流)
调用run只是一个普通的方法调用,不涉及创建新线程(仍然在原来的线程中,没有涉及到新的执行流)
调用strat 方法
public class Demo {
//start 和 run 的区别
public static void main(String[] args) {
MyThread2 myThread2=new MyThread2();
myThread2.start();
while(true){
System.out.println(“hehe”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
while(true){
System.out.println(“haha”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
可以看到两个线程并发执行,
而如果是调用run()方法,就是普通的调用,没有创建新线程,一直在循环里出不来
===========================================================================
线程执行完毕,运行5S 线程执行完毕
public class Demo {
//中断一个线程
static boolean isRunning=true;
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
while(isRunning){
System.out.println(“hello”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isRunning=false;
System.out.println(“线程运行5S结束”);
}
}
针对上面的方式进行修改
1,把上面的while()中判断条件进行修改
2,把catch里面的代码,加一个break
调用 interrupt()是通知线程结束,具体还是看内部代码的实现
public class Demo {
//使用Thread 方法来中断线程
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
System.out.println(“hello”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
break;
}
}
}
};
t1.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“线程结束”);
t1.interrupt();
}
}
=========================================================================
我们在创建多个线程之后,每个线程都是一个独立的执行流~
这些线程每个线程的执行顺序都是不确定的,完全取决于操作系统的调度,这里的等待线程机制就是一种确定线程先后顺序方式,确定线程的结束顺序,无法确定谁先开始,可以确定谁先结束 使用join()
join 起到的效果就是等待某个线程结束,谁调用join就等待谁结束
通过代码来解释:
public class Demo {
//使用Thread 方法来中断线程
public static void main(String[] args) {
Thread t1=new Thread(){
@Override
public void run() {
for(int i=0;i<5;i++){
System.out.println(“hello”);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里在main 方法中调用了join,相当于在主线程中,等待t1线程结束
main方法在执行的时候,遇到join就会堵塞等待,一直等t1线程执行完毕,这个时候join才会继续往下执行
也就是说谁调用join 谁先结束
=========================================================================
当执行sleep时就是让线程休眠,所谓的休眠就是把线程的task struct放入等待队列
CPU在执行的时候是挑等待队列中的线程来执行的,而sleep的线程在等待队列不在就绪队列,所以不会被执行
也可以在sleep中加上时间,等时间过去后,等待队列的线程才有机会到就绪队列,至于什么时候到就绪队列还是要看调度器执行
等待队列可能有好多了,具体谁先出队列,先回到就绪队列和设定的时间相关,如果时间一样就看系统的调度
==========================================================================
在我们调试多线程程序有帮助
| NEW | Thread 对象刚创建,还没有在系统中创建线程,相当于任务交给了线程,但是线程还没有开始执行 |
| — | — |
| Runnable | 线程是一个准备就绪的状态,随时可能调度到CPU上执行,或者正在CPU上执行(线程的task struct在就绪队列中) |
| Blocked | 线程堵塞(线程在等待队列里)没有竞争到锁 |
| Waiting | 线程堵塞(线程在等待队列里)调用waiting 方法 |
| Timed_Waiting | 线程堵塞(线程在等待队列里)调用sleep方法 |
| Terminated | 线程结束了(Thread对象还没销毁) |
================================================================================
多线程虽然是更轻量的并发编程(相比于进程),但是线程是访问同一份内存资源,由于线程是一个抢占式执行的过程中谁先执行,谁后执行,不确定,完全却决于系统的调度。由于这里不确定性太多就可能导致多个线程访问同一个资源的时候,出现BUG所以引出了线程安全问题。 访问分为读和写操作,读操作不会涉及到线程安全问题,只有写操作涉及线程安全工作
多线程修改同一变量
public class Demo {
//线程安全问题
static class Counter{
//创建一个自增类,通过线程调度来展示线程安全问题
public int count=0;
public void increase(){
count++;
}
}
public static void main(String[] args) {
//通过两个线程同时对count进行自增
Counter counter=new Counter();
Thread t1=new Thread(){
@Override
public void run() {
for(int i=0;i<50000;i++){
counter.increase();
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for(int i=0;i<50000;i++){
counter.increase();
}
}
};
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(counter.count);
}
}
此处代码就是t1,t2 两个线程修改同一变量,存在线程安全问题,正常情况下count值应该是100000,但是运行下来,count值是在50000~100000之间,每次都在变化,这是为什么呢? 这里就是触发了线程安全的问题
这里我们先看count ++ 具体做了什么事情,这里我们就要引入JMM了
JMM(JVM 实现方式的抽象,Java 程序和内存之间是如何交互的)
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://img-blog.csdnimg.cn/img_convert/f83aca2c33baa9a25d148be584c48f0b.jpeg)
总结
面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。
以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
*因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**[外链图片转存中…(img-OW1ByR6B-1713137278101)]
[外链图片转存中…(img-WCBn2sps-1713137278102)]
[外链图片转存中…(img-xW59puvY-1713137278102)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://img-blog.csdnimg.cn/img_convert/f83aca2c33baa9a25d148be584c48f0b.jpeg)
总结
面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。
以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考
[外链图片转存中…(img-HHv2Y9gc-1713137278103)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!