1. 线程常用方法接上
2. 用户线程和守护线程 591
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3. 常见的守护线程: 垃圾回收机制
2.1 应用案例 591
下面我们测试如何将一个线程设置成守护线程
代码在com.stulzl.thread_method03.包中
Thread_Method03
package com.stulzl.thread_method03;
//下面我们测试如何将一个线程设置成守护线程 591
public class Thread_Method03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束
//,只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true);
myDaemonThread.start();//启动子线程
for (int i = 1; i <=10; i++) {//main主线程
System.out.println("宝强再辛苦的工作……");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {//Daemon汉译守护线程
public void run() {
for (; ; ) {//无限循环
try {
Thread.sleep(1000);//休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}
3. 线程的生命周期 592
3.1 JDK 中用 Thread.State 枚举表示了线程的几种状态
3.2 线程状态转换图
代码在com.stulzl.thread_state_.包中
Thread_State_
package com.stulzl.thread_state_;
//线程状态说明 592
public class Thread_State_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName()+"状态 "+t.getState());//刚创建子线程立马输出状态
t.start();//启动子线程
while(t.getState() != Thread.State.TERMINATED){//Thread.State.TERMINATED终止状态
//只要当前子线程不等于终止状态,我就输出子线程现在的状态
System.out.println(t.getName()+"状态 "+t.getState());
Thread.sleep(1000);//这个休眠时让主线程休眠不是让子线程休眠
}
System.out.println(t.getName()+"状态 "+t.getState());//最后子线程执行完 在获取子线程状态
}
}
class T extends Thread{
@Override
public void run() {
while(true){
for (int i = 0; i <10; i++) {
System.out.println("hi "+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
4. 线程的同步 593
4.1 先看一个问题
存在超卖问题,这就来解决它
5. Synchronized 593
5.1 线程同步机制
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
2.也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才 能对该内存地址进行操作
5.2 同步具体方法-Synchronized
3.如何理解:就好像某小伙伴上厕所前先把门关上(.上锁),完事后再出来(解锁),那么其它小伙伴就可在使用厕所了,如图:
5.2.1 使用synchronized解决售票的超卖问题问题 593
代码在com.stulzl.synchronized_.包中
SellTicKet
package com.stulzl.synchronized_;
//同步synchronized 线程练习 593
// [售票系统] ,编程模拟三个售票窗口售票100张,分别使用继承Thread和实现Runnable方式,并分析有什么问题?
public class SellTicKet {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //这里会出现超卖现象,原因是比如 当我们线程1进去后还没来的及--ticketNum,线程2线程3也进来了
// //这样就会出现超卖现象
// sellTicket01.start();//启动售票,开启线程
// sellTicket02.start();
// sellTicket03.start();
//这样也会出现超卖现象,原因如上
System.out.println("===使用实现接口的方式===");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第一个线程窗口
new Thread(sellTicket02).start();//第二个线程窗口
new Thread(sellTicket02).start();//第三个线程窗口
}
}
//使用继承Thread类
//class SellTicket01 extends Thread{
// //让多个线程共享ticketNum,因为静态的会随着类的加载而创建的嘛,而且只会被创建一次,因为我们后三个线程
// //都要多ticketNum进行--,但是呢又不能让ticketNum重置,所以用static
// private static int ticketNum = 100;
// @Override
// public void run() {
// while(true){
// if(ticketNum<=0){
// break;
// }
// //休眠50毫秒
// try {
// Thread.sleep(50);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
// "剩余票数="+(--ticketNum));
// }
// }
//}
///实现接口Runnable接口
class SellTicket02 implements Runnable{
private int ticketNum = 100;//让多个线程共享ticketNum
private boolean loop = true;
//synchronized同步方法, 在同一时刻只能有一个线程来执行sell方法
//解释为什么把synchronized加到sell()方法上而不是直接加到我们下面的run()方法上
//因为直接加到run()方法就不能保证同时多线程了(synchronized作用就是保证同时只能由一个线程进入方法),
//而题目要求我们需要三个线程
public synchronized void sell(){//同步方法
if(ticketNum<=0){
System.out.println("售票结束");
loop = false;//结束while循环
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
"剩余票数="+(--ticketNum));
}
@Override
//synchronized 直接加到run()方法就不能保证同时多线程,所以写一个sell方法,在那里加synchronized
//这样既保证了同时可以有三个线程进入run,又可以保证同时只能有一个线程去访问sell方法
public void run() {
while(loop){
sell();
}
}
}
6. 分析同步原理 594
7. 互斥锁 594
7.1 基本介绍
1. Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
2.每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身。
7.2 使用互斥锁来解决售票问题 594
代码演示(两种方式都演示下, 代码块加锁,和方法上加锁)
代码在com.stulzl.synchronized_.包中
SellTicket
package com.stulzl.synchronized_;
//互斥锁synchronized线程练习 594
// [售票系统] ,编程模拟三个售票窗口售票100张,分别使用继承Thread和实现Runnable方式,并分析有什么问题?
public class SellTicket {
public static void main(String[] args) {
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
// //这里会出现超卖现象,原因是比如 当我们线程1进去后还没来的及--ticketNum,线程2线程3也进来了
// //这样就会出现超卖现象
// sellTicket01.start();//启动售票,开启线程
// sellTicket02.start();
// sellTicket03.start();
//这样也会出现超卖现象,原因如上
System.out.println("===使用实现接口的方式===");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第一个线程窗口
new Thread(sellTicket02).start();//第二个线程窗口
new Thread(sellTicket02).start();//第三个线程窗口
}
}
//使用继承Thread类
//class SellTicket01 extends Thread{
// //让多个线程共享ticketNum,因为静态的会随着类的加载而创建的嘛,而且只会被创建一次,因为我们后三个线程
// //都要多ticketNum进行--,但是呢又不能让ticketNum重置,所以用static
// private static int ticketNum = 100;
// @Override
// public void run() {
// while(true){
// if(ticketNum<=0){
// break;
// }
// //休眠50毫秒
// try {
// Thread.sleep(50);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
// "剩余票数="+(--ticketNum));
// }
// }
//}
///实现接口Runnable接口
class SellTicket02 implements Runnable{
private int ticketNum = 100;//让多个线程共享ticketNum
private boolean loop = true;
Object object = new Object();
//同步方法(静态的)的锁为当前类本身
//解读
//1. public synchronized static void m1() {} 锁是加在 SellTicket02.class
//2. 如果在静态方法中,实现一个同步代码块.
/*
synchronized (SellTicket02.class) {
System.out.println("m2");
}
*/
public synchronized static void m1() {
}
public static void m2() {
synchronized (SellTicket02.class) {
System.out.println("m2");
}
}
//synchronized同步方法, 在同一时刻只能有一个线程来执行sell方法
//解释为什么把synchronized加到sell()方法上而不是直接加到我们下面的run()方法上
//因为直接加到run()方法就不能保证同时多线程了(synchronized作用就是保证同时只能由一个线程进入方法),
//而题目要求我们需要三个线程
//说明
//1. public synchronized void sell() {} 就是一个同步方法这时锁在 this对象
//2. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public /*synchronized*/ void sell(){//同步方法
//5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
//这里写this可以也可以写object,因为我们在主方法中new的是同一个对象
synchronized (/*this*/object){//同步代码块
if(ticketNum<=0){
System.out.println("售票结束");
loop = false;//结束while循环
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 "+Thread.currentThread().getName()+"售出一张票"+
"剩余票数="+(--ticketNum));
}
}
@Override
//synchronized 直接加到run()方法就不能保证同时多线程,所以写一个sell方法,在那里加synchronized
//这样既保证了同时可以有三个线程进入run,又可以保证同时只能有一个线程去访问sell方法
public void run() {
while(loop){
sell();
}
}
}
7.3 互斥锁注意事项和细节 594
1.同步方法如果没有使用static修饰:默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可!
8. 线程的死锁 595
8.1 基本介绍
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一 定要避
免死锁的发生.
举个例子
妈妈:你先完成作业,才让你玩手机
小明:你先让我玩手机,我才完成作业
8.2 模拟死锁的产生 595
代码在com.stulzl.deadlock.包中
Dead_Lock
package com.stulzl.deadlock;
//演示死锁 595
public class Dead_Lock {
public static void main(String[] args) {
//模拟死锁现象
//开两个线程A和B
DeadLockDemo A = new DeadLockDemo(true);//子线程A拿到o1锁
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);//子线B程拿到o2锁
B.setName("B线程");
//启动
//结果肯定卡住,因为
//子线程A拿到o1锁后还需要拿到o2锁才能出去。但是o2锁被子线程B拿走了
//子线程B拿到o2锁后还需要拿到o1锁才能出去。但是o1锁被子线程A拿走了
//这样他俩就产生了死锁
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
9. 释放锁 596
9.1 下面操作会释放锁
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所, 完事出来
2.当前线程在同步代码块、同步方法中遇到break、 return。
案例:没有正常的完事,经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束
案例:没有正常的完事,发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释
放锁。
案例:没有正常完事,觉得需要酝酿下,所以出来等会再进去
9.2 下面操作不会释放锁
1. 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、 Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
2. 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。提示应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用