Day29
多线程(剩余部分)
十二、线程的礼让
Thread.yield();
理解:此方法为静态方法,此方法写在哪个线程中,哪个线程就礼让
注意:所谓的礼让是指当前线程退出CPU资源,并转到就绪状态,接着再抢
需求:创建两个线程A,B,分别各打印1-100的数字,其中B一个线程,每打印一次,就礼让一次,观察实验结果
随机输出:
A:1
B:1
A:2
B:2
A:3
A:4
…
public class A extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("A:" + i);
}
}
}
public class B extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("B:" + i);
//礼让:让当前线程退出CPU资源,当前线程退出后立刻转入抢资源的状态,可能又会抢到CPU资源
Thread.yield();
}
}
}
public class Test01 {
public static void main(String[] args) {
A a = new A();
B b = new B();
a.start();
b.start();
}
}
十三、线程的合并
t.join(); 合并方法
需求:主线程和子线程各打印200次,从1开始每次增加1,当主线程打印到10之后,让子线程先打印完再打印主线程
随机输出:(可能情况去理解)
主线程抢到资源直接打印到十,子线程才开始
主线程抢资源到打印到十,子线程也抢到资源打印了一些
主线程抢到资源打印未到十,子线程抢到资源已经打印完
理解图
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <=200; i++) {
System.out.println("子线程:" + i);
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
for (int i = 1; i <=200; i++) {
System.out.println("主线程:" + i);
if(i == 10){
//让t线程加入到当前线程
t.join();
}
}
}
}
十四、线程的中断
线程的中断1
public class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("111");
System.out.println("222");
System.out.println("333");
System.out.println("444");
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
//面试题:下列代码的子线程开启后,是否会在3000毫秒就被销毁?
//答:不一定,因为3000毫秒后主线程才休眠结束,这时会抢CPU资源
// 如果立刻抢到,那么子线程就是3000毫秒后销毁
// 如果没有抢到CPU资源,那么子线程会继续运行,直到主线程抢到CPU资源
MyThread t = new MyThread();
t.start();
Thread.sleep(3000);
t.stop();//立刻停止(缺点:可能会导致功能确实)
//stop()方法已经过时(有个横杠线)
}
}
线程的中断2
线程run()结束就会死,runn方法死循环就不会死
用变量flag,防止直接死亡,弥补stop的缺点,线程也有相关的方法
public class MyThread extends Thread{
private boolean flag = true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while(flag){
System.out.println("111");
System.out.println("222");
System.out.println("333");
System.out.println("444");
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.sleep(3000);
t.setFlag(false);
}
}
线程的中断3
线程也有相关的方法
public class MyThread extends Thread{
@Override
public void run() {
//获取线程状态(是否消亡)
// System.out.println(Thread.currentThread().isInterrupted());
//输出完才会因为线程状态改变而终止
while(!Thread.currentThread().isInterrupted()){
System.out.println("111");
System.out.println("222");
System.out.println("333");
System.out.println("444");
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.sleep(3000);
//改变线程状态
t.interrupt();
}
}
十五、守护线程
守护线程 (后台线程)默默守护着前台线程,当所有的前台线程都消亡后,守护线程会自动消亡
注意:垃圾回收器就是守护线程
t.setDaemon(true);
注意:
new出来的都是前台线程
Daemon要在线程启动前设置把,不能在线程启动后设置
父类没有抛异常,子类不能抛异常,只能try catch
public class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("后台线程默默守护着前台线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test01 {
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.setDaemon(true);//将当前线程设置为守护线程
t.start();
for (int i = 1; i <= 5; i++) {
System.out.println("主线程:" + i);
Thread.sleep(1000);
}
}
}
十六、线程局部变量(实现线程范围内的共享变量)
线程局部变量共享
理解图
共享单个数据
注意:
A类虽然不是线程类
但是A类的对象在线程中调用了println方法
线程1中的A类对象调用了println方法,那么println方法里的Thread.currentThread()就是获取的是线程1对象
线程2中的A类对象调用了println方法,那么println方法里的Thread.currentThread()就是获取的是线程2对象
public class A {
public void println(){
Thread t = Thread.currentThread();
Integer value = Test01.map.get(t);
System.out.println(t.getName() + "里的A类对象获取了数据:" + value);
}
}
//B类同理
public class Test01 {
public static final ConcurrentHashMap<Thread, Integer> map = new ConcurrentHashMap<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
int i = 10;
//存数据
map.put(Thread.currentThread(), i);
A a = new A();
B b = new B();
a.println();//10
b.println();//10
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
int i = 20;
//存数据
map.put(Thread.currentThread(), i);
A a = new A();
B b = new B();
a.println();//20
b.println();//20
}
}, "线程2").start();
}
}
共享多个数据
1.自定义方法解决
public class A {
public void println(){
Thread t = Thread.currentThread();
Data value = Test01.map.get(t);
System.out.println(t.getName() + "里的A类对象获取了数据:" + value);
}
}
//B类同理
//数据包类
public class Data {
private int i;
private String str;
//有参、无参、get、set、toString方法(略)
}
public class Test01 {
public static final ConcurrentHashMap<Thread, Data> map = new ConcurrentHashMap<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Data data = new Data(10,"xxx");
//存数据
map.put(Thread.currentThread(), data);
A a = new A();
B b = new B();
a.println();//10
b.println();//10
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
Data data = new Data(20,"yyy");
//存数据
map.put(Thread.currentThread(), data);
A a = new A();
B b = new B();
a.println();//20
b.println();//20
}
}, "线程2").start();
}
}
2.ThreadLocal
存数据
local.set(data)底层原理:
1.获取当前线程对象
2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
3.map.put(this,t)
获取数据
local.get()底层原理:
1.获取当前线程对象
2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
3.map.getEntry(this) -> Entry对象
4.entry.getValue()
public class A {
public void println(){
Thread t = Thread.currentThread();
/**
* 获取数据
* local.get()底层原理:
* 1.获取当前线程对象
* 2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
* 3.map.getEntry(this) -> Entry对象
* 4.entry.getValue()
*/
Data value = Test01.local.get();
System.out.println(t.getName() + "里的A类对象获取了数据:" + value);
}
}
//B类同理
//数据包类
public class Data {
private int i;
private String str;
//有参、无参、get、set、toString方法(略)
//保证每个线程里只有一个Data包对象
public static Data getInstance(int i,String str){
Data data = Test01.local.get();//获取当前线程的Data对象
if(data == null){
data = new Data(i, str);
Test01.local.set(data);
}else{//有数据就set
data.setI(i);
data.setStr(str);
}
return data;
}
}
public class Test01 {
public static final ThreadLocal<Data> local = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Data data = Data.getInstance(10,"xxx");
/**
* 存数据
* local.set(data)底层原理:
* 1.获取当前线程对象
* 2.通过当前线程对象获取ThreadLocalMap<ThreadLocal,T>
* 3.map.put(this,t)
*/
local.set(data);
A a = new A();
B b = new B();
a.println();//10
b.println();//10
}
},"线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
Data data = Data.getInstance(20,"yyy");
data = Data.getInstance(30,"zzz");
//存数据
local.set(data);
A a = new A();
B b = new B();
a.println();//20
b.println();//20
}
}, "线程2").start();
}
}
十七、线程的生命周期
概念
1、新建状态
i. 在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了相应的内存空间和其它资源,但还处于不可运行状态。新建一个线程对象可采用线程构造方法来实现。
ii. 例如:Thread thread=new Thread();
2、 就绪状态
i. 新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU调用,这表明它已经具备了运行条件。
3、运行状态
i. 当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。
4、 阻塞状态
i. 一个正在执行的线程在某些特殊情况下,如被人为挂起,将让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(2000)、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
5、死亡状态
i. 线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
线程生命周期图
练习
1.计算任务,一个包含了2万个整数的数组,分拆了多个线程来进行并行计算,最后汇总出计算的结果。
2.铁道部发布了一个售票任务,要求销售1000张票,要求有3个窗口来进行销售,请编写多线程程序来模拟这个效果(该题涉及到线程安全,https://www.jb51.net/article/221008.htm)
i. 窗口001正在销售第1张票
ii. 窗口001正在销售第2张票
iii. 窗口002正在销售第3张票
iv. 。。。
v. 窗口002正在销售第1000张票
涉及到线程安全,要加锁
总结:
1.线程的礼让 – yield
2.线程的合并 – join
3.线程的中断
4.守护线程
5.线程局部变量共享 – 重要理解ThreadLocal底层原理
6.线程生命周期 — 重要
线程生命周期图