目录
多线程
进程是系统资源分配的基本单位
线程是系统调度执行的基本单位
一个进程可以包含一个线程,也可以包含多个线程(不能包含 0 个线程)之前学习过的都是单线程。
1. Thread创建的写法
1.继承Thread,重写run--->描述线程执行的任务
public class Test5 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Mythead();
thread.start();
thread.run();
Thread.sleep(1000);
}
}
class Mythead extends Thread{
@Override
public void run(){
System.out.println("hello thread");
}
}
2.实现Runnable,重写run
使用Runnable表示任务的具体情况--》解耦合
public class Test6 {
public static void main(String[] args) throws InterruptedException {
Mythread mythread=new Mythread();
mythread.run();
Thread.sleep(1000);
}
}
class Mythread implements Runnable{
@Override
public void run() {
}
}
3.继承Thread,重写run,使用内部匿名类
1.创建一个 Thread 的子类,(叫啥名字不知道)
2.同时也创建了这个子类的实例
3.重写 run 方法,
public class Test7 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(){
@Override
public void run(){
System.out.println("1211231");
}
};
thread.start();
Thread.sleep(100000);
}
}
4实现Runnable,重写run,使用内部匿名类
1.创建新的类,实现 Runnable.但是类的名字是匿名的
2.创建了这个新类的实例.(一次性)
3.重写 run 方法,
public class Test8 {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable(){
@Override
public void run(){
System.out.println("线程正在1111");
}
};
thread.start();
}
}
5使用lambda方法
public class Test9 {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(()->{
for (int i=0;i<100;i++){
System.out.println(1111);
}
});
thread.start();
Thread.sleep(10000);
}
}
Thread中的一些核心属性和方法
- id
- name
- demon(后台线程)
- 状态---getState
- 优先级---priority
- isAlive—判断系统内核中的线程是否存在
- 后台线程--》isDaemon
- start 启动线程
- 线程终止写法isinterruptted(即使出现sleep等阻塞状态也会被提前唤醒)
前台线程和后台线程
前台线程: 这样的线程如果不运行结束的话,此时java 进程是一定不会结束的
后台线程: 这样的线程,即使继续在执行,也不能阻止 java 进程结束。
如果main方法执行完成,并且thread线程为后台线程的时候,没有thread的堵塞方法,那么thread很有可能完成不了。
前台最后一个进程结束的时候,总进程就会结束。进程就会被gc回收。
public class Test9 {
public static void main(String[] args) throws InterruptedException {
int count = 0;
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
count++;
}
});
// 将线程设置为后台线程
thread.setDaemon(true);
thread.start();
Thread.sleep(10000);
System.out.println(count);
}
}
是否存活
指的是系统中的线程(PCB)是否还存在,
t.start方法才是真正在系统中创建出的先成功
一个线程需要先通过 run /lambda 把线程要完成的任务,定义出来.star 才是真正创建线程, 并开始执行。
核心就是是否真的创建线程出来,每个线程都是独立调度执行的.(相当于整个程序中多了一个执行流)
一个Thread对象,只能star一次
是否会被中断
即终止。t线程在执行的时候,其他线程
终止线程,在 Java 中, 都只是"提醒,建议",真正要不要终止,还得线程本体来进行决定的!!
这里所指的不是强制终止。如果强制终止
pcb是内核中线程的体现
1.自己来实现控制线程结束代码的例子,
思路:让线程的入口方法尽快执行结束。
2.)使用 Thread 提供的 interrupt 方法 和 isInterruptted 方法,来实现上述的效果
t.interrupt(); 通过这个方法,就相当于是设置 boolean 值为 true
使用这个方法会使线程直接进入跳出状态,这时候就会抛出一个InterruptedException 异常,不会再等待,立即就唤醒了,然后可以在异常中写出像写出的代码
线程的等待
多个线程,调度的顺序在系统是无序的,通过线程等待就可以将线程结束的顺序确定出来
通过t.join的方法就可以将结束顺序固定
假设在main线程调用t.join,main线程就会等待t,t结束后,main也结束了。
t线程没有结束,join堵塞等待,等到t结束后,join就会解除堵塞状态,继续执行,
。(堵塞:该线程不参与cpu的调度执行,解除堵塞继续执行,线程参与到cpu的调度。)
可以理解为两个线程换为一前一后的一个线程。
同时调用t1.join和t2.join的时候,先执行t1。join等待t1线程执行完毕,结束后继续执行t2。join等待t2结束main就会结束。但是t1和t2的线程是在main执行的时候也在执行,他只是等待结束,并不是有了main执行到了join才开始。
线程是并发执行的
t1 和t2是各自调度各自执行
执行先后顺序/谁先结束,都是不确定只不过上述代码中,t1 sleep 3s, t2 sleep 4s
此时 t1 先结束了t2后结束了
所以在执行main方法的时候会在t1join后再等待大约1s(堵塞在t2),才会结束。如果反过来就会变得t2join后,t1。join直接通过。
可以通过对main的方法,用joint1,同时在t1中用joint2,这样就完成了t2对t1的顺序排序。
join() 死等
join(long mills)等到nms
join(long mills,int naps)等到n毫秒m纳秒。
时间戳:System.currentTimeMills();
通常用sleep方法让他堵塞,防止线程打乱顺序。
在seep的时间到了时候,线程会从堵塞状态恢复到就绪状态。
线程状态
也就是PCB的状态。
1.NEW Thread 对象有了,还没调用 start
系统内部的线程还未创建.
2.TERMINATED 线程已经终止了.
terminated
内核中的线程已经销毁了,
3RUNNABLE 就绪状态.
指的是,这个线程“随叫随到'
a) 这个线程正在 CPU 上执行
b) 这个线程虽然没在CPU 上执行,随时可以调度到 CPU 上执行.
4WAITING
死等 进入的阻塞
5TIMED_WAITING 带有超时时间的等
6BLOCKE
进行锁竞争的时候产生的阻塞
线程安全问题!!![核心& 难点 &考点]
线程 是随机调度,抢占式执行.=>这样的随机性,就会使程序的执行顺序,产生变数 =>不同的结果.
但是有的时候,週到不同的緒果、认为不可接收。这为是bug)
如果不加join,直接打印,由于三个线程完全并发执行
此时就会使 主线程 打印 count 并非是 t1 t2 算完的效果.(类似于 读脏数据了
1)把内存 count 中的数值,读取到 cpu 寄存器中,load
2) 把寄存器中的值,+1,还是继续保存在寄存器中 add
3) 把寄存器上述计算后的值,写回到内存 count 里 save
通过上述执行过程,发现,两次++
最后内存的結果、仍然是1!!!
后一次计算 把前一次计算的结果,覆盖掉
ア!!
由于当前线程执行的顺序不确定,有些执行顺序,加两次,结果正确的有些执行顶序,加两次,最后只增加 1
具体有多少次正确,多少次不正确,随机的!!
因此看到的结果,不是精确的 Sw!!!