-
day33-线程基础03
6.用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或者通知方法结束。平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程
-
守护线程(Daemon):一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
-
常见的守护线程:垃圾回收机制
例子1:如何将一个线程设置成守护线程
|
package li.thread.method; |
|
|
|
public class ThreadMethodExercise { |
|
public static void main(String[] args) throws InterruptedException { |
|
|
|
MyDaemonThread myDaemonThread = new MyDaemonThread(); |
|
|
|
//如果我们希望当主线程结束后,子线程自动结束,只需要将子线程设置为守护线程 |
|
myDaemonThread.setDaemon(true); |
|
|
|
myDaemonThread.start(); |
|
|
|
for (int i = 1; i <= 10; i++) {//main线程 |
|
System.out.println("悟空在前方打妖精..."); |
|
Thread.sleep(1000); |
|
} |
|
} |
|
} |
|
|
|
class MyDaemonThread extends Thread { |
|
|
|
public void run() { |
|
for (; ; ) {//无限循环 |
|
try { |
|
Thread.sleep(1000); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
System.out.println("八戒收拾东西回高老庄..."); |
|
} |
|
} |
|
} |
![image-20220905114351151](/articlelist/uploads/allimg/221114/1031351257-0.png)
7.线程的生命周期
- JDK中用Thread.State枚举表示了线程的几种状态:
例子
|
package li.thread.state; |
|
|
|
public class ThreadState_ { |
|
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) { |
|
System.out.println(t.getName() + "状态 " + t.getState()); |
|
Thread.sleep(1000); |
|
} |
|
|
|
System.out.println(t.getName() + "状态 " + t.getState()); |
|
|
|
} |
|
} |
|
|
|
class T extends Thread { |
|
|
|
public void run() { |
|
for (int i = 0; i < 10; i++) { |
|
System.out.println("hi" + i); |
|
try { |
|
Thread.sleep(1000); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
|
} |
8.线程同步机制
- 线程同步机制
- 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以理解为:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
- 同步具体方法--Synchronized
-
同步代码块
synchronized(对象){//得到对象的锁,才能操作同步代码
//需要被同步的代码
}
-
synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name){
//需要被同步的代码
}
就好像某个小伙伴上厕所之前先把门关上(上锁),完事之后再出来(解锁),那么其他小伙伴就可以再使用厕所了
例子:使用synchronized解决3.1售票问题
|
package li.thread.syn; |
|
|
|
//使用多线程,模拟三个窗口同时售票共100张 |
|
public class SynSellTicket { |
|
public static void main(String[] args) { |
|
|
|
SellTicket03 sellTicket03 = new SellTicket03(); |
|
new Thread(sellTicket03).start();//第1个线程-窗口 |
|
new Thread(sellTicket03).start();//第2个线程-窗口 |
|
new Thread(sellTicket03).start();//第3个线程-窗口 |
|
} |
|
} |
|
|
|
//实现接口方式,使用synchronized实现线程同步 |
|
class SellTicket03 implements Runnable { |
|
|
|
private int ticketNum = 100; |
|
private boolean loop = true;//控制run方法变量 |
|
|
|
public synchronized void sell() {//同步方法,在在同一时刻,只能有一个线程来执行run方法 |
|
if (ticketNum <= 0) { |
|
System.out.println("售票结束..."); |
|
loop = false; |
|
return; |
|
} |
|
//休眠50毫秒,模拟 |
|
try { |
|
Thread.sleep(50); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 " |
|
+ "剩余票数:" + (--ticketNum)); |
|
} |
|
|
|
|
|
public void run() { |
|
while (loop) { |
|
sell();//sell方法是一个同步方法 |
|
} |
|
} |
|
} |
![image-20220905135920187](/articlelist/uploads/allimg/221114/103135KC-3.png)
8.1互斥锁
- 基本介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每一个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能有一个线程访问
- 同步的局限性:导致程序的执行效率降低
- 非静态的同步方法,锁可以是this(当前对象),也可以是其他对象(要求锁的是同一个对象)
- 同步方法(静态的)的锁为当前类本身(类.class)
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。 具体表现为以下3种形式。 对于普通同步方法,锁是当前实例对象。 对于静态同步方法,锁是当前类的Class对象。 对于同步方法块,锁是Synchonized括号里配置的对象。
-
注意事项和细节:
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
-
实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或者同步方法
- 要求多个线程的锁对象为同一个
|
package li.thread.syn; |
|
|
|
//使用多线程,模拟三个窗口同时售票共100张 |
|
public class SynSellTicket { |
|
public static void main(String[] args) { |
|
|
|
SellTicket03 sellTicket03 = new SellTicket03(); |
|
new Thread(sellTicket03).start();//第1个线程-窗口 |
|
new Thread(sellTicket03).start();//第2个线程-窗口 |
|
new Thread(sellTicket03).start();//第3个线程-窗口 |
|
} |
|
} |
|
|
|
//实现接口方式,使用synchronized实现线程同步 |
|
class SellTicket03 implements Runnable { |
|
|
|
private int ticketNum = 100; |
|
private boolean loop = true;//控制run方法变量 |
|
Object object = new Object(); |
|
|
|
//1.public synchronized static void m1(){}的锁加在SellTicket03.class |
|
public synchronized static void m1(){} |
|
//2.如果在静态方法中,要实现一个同步代码块则应该这样写:(原因是静态方法适合类一起加载的,静态方法不能使用this) |
|
public static void m2(){ |
|
synchronized (SellTicket03.class){ |
|
System.out.println("m2"); |
|
} |
|
} |
|
|
|
// public synchronized void sell() {}就是一个同步方法。这时,锁在this对象 |
|
//也可以在代码块上写synchronized,同步代码块,互斥锁还是在this对象 |
|
public /*synchronized*/void sell() {//同步方法 |
|
synchronized (/*this*/object) {//如果是new Object就不是同一个对象 |
|
if (ticketNum <= 0) { |
|
System.out.println("售票结束..."); |
|
loop = false; |
|
return; |
|
} |
|
//休眠50毫秒,模拟 |
|
try { |
|
Thread.sleep(50); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
System.out.println("窗口:" + Thread.currentThread().getName() + "售出一张票 " |
|
+ "剩余票数:" + (--ticketNum)); |
|
} |
|
} |
|
|
|
|
|
public void run() { |
|
while (loop) { |
|
sell();//sell方法是一个同步方法 |
|
} |
|
} |
|
} |
8.2线程的死锁
- 基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。
在编程中一定要避免死锁的发生。
例子:
|
package li.thread.syn; |
|
|
|
//模拟线程死锁 |
|
public class DeadLock_ { |
|
public static void main(String[] args) { |
|
//模拟死锁现象 |
|
DeadLockDemo A = new DeadLockDemo(true); |
|
DeadLockDemo B = new DeadLockDemo(false); |
|
A.setName("A线程"); |
|
B.setName("B线程"); |
|
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; |
|
} |
|
|
|
|
|
public void run() { |
|
//下面业务逻辑的分析 |
|
//1.如果flag为true,线程就会先得到/持有 o1对象锁,然后尝试去获取o2对象锁 |
|
//2.如果线程A得不到o2对象锁,就会Blocked |
|
//3.如果flag为false,线程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"); |
|
} |
|
} |
|
} |
|
} |
|
} |
如下图:两个线程卡住了
![image-20220905175553591](/articlelist/uploads/allimg/221114/1031351494-4.png)
8.3释放锁
下面操作会释放锁:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面的操作不会释放锁:
-
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend()和resume()来控制线程,这两个方法不再推荐使用
9.本章作业
9.1线程HomeWork01
(1)在main方法中启动两个线程
(2)第一个线程循环随机打印100以内的整数
(3)直到第二个线程从键盘读取了“Q”命令
![image-20220905192152630](/articlelist/uploads/allimg/221114/1031352017-5.png)
练习:
|
package li.thread.syn.homework; |
|
|
|
import java.util.Scanner; |
|
|
|
//(1)在main方法中启动两个线程 |
|
public class ThreadHomeWork01 { |
|
|
|
public static void main(String[] args) { |
|
|
|
A a = new A(); |
|
B b = new B(a);//注意把a对象传入b构造器中 |
|
|
|
a.start(); |
|
b.start(); |
|
} |
|
} |
|
|
|
//创建A线程类 |
|
class A extends Thread { |
|
private boolean loop = true; |
|
|
|
|
|
public void run() { |
|
while (loop) { |
|
System.out.println((int) (Math.random() * 100 + 1)); |
|
try { |
|
Thread.sleep(1000); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
System.out.println("a线程退出..."); |
|
} |
|
|
|
public void setLoop(boolean loop) { |
|
this.loop = loop; |
|
} |
|
} |
|
|
|
//创建B线程类 |
|
class B extends Thread { |
|
private A a; |
|
Scanner scanner = new Scanner(System.in); |
|
|
|
public B(A a) { |
|
this.a = a; |
|
} |
|
|
|
|
|
public void run() { |
|
while (true) { |
|
//接到用户输入 |
|
System.out.println("请输入你的指令(Q)表示退出"); |
|
char key = scanner.next().toUpperCase().charAt(0); |
|
if (key == 'Q') { |
|
//以通知的方式结束a线程 |
|
a.setLoop(false); |
|
System.out.println("b线程退出..."); |
|
break; |
|
} |
|
} |
|
} |
|
} |
![image-20220905195149476](/articlelist/uploads/allimg/221114/1031351K7-6.png)
9.2线程线程HomeWork02
(1)有两个用户分别从同一张卡上取钱(总额10000)
(2)每次都取1000,当余额不足时,就不能取款了
(3)不能出现超取现象==>线程同步问题
![image-20220905201833154](/articlelist/uploads/allimg/221114/1031352L5-7.png)
易错点:关于互斥锁的理解 对于普通同步方法,锁是当前实例对象。 对于静态同步方法,锁是当前类的Class对象。 对于同步方法块,锁是Synchonized括号里配置的对象
|
package li.thread.syn.homework; |
|
|
|
public class ThreadHomeWork02 { |
|
public static void main(String[] args) { |
|
T t = new T(); |
|
Thread thread1 = new Thread(t); |
|
Thread thread2 = new Thread(t); |
|
thread1.setName("t1"); |
|
thread2.setName("t2"); |
|
thread1.start(); |
|
thread2.start(); |
|
} |
|
} |
|
|
|
class T implements Runnable { |
|
private int money = 10000; |
|
|
|
|
|
public void run() { |
|
while (true) {//while不要放到同步代码块里面 |
|
//1.使用了synchronized实现线程同步 |
|
//2.当多个线程执行到这里的时候就会去争夺 this对象锁 |
|
//3.哪个线程争夺到(获取)this对象锁,就执行synchronized代码块 |
|
//4.争夺不到this对象锁,就Blocked,准备继续争夺 |
|
//5.this对象锁是非公平锁 |
|
synchronized (this) { |
|
if (money <= 0) { |
|
System.out.println("余额不足..."); |
|
break; |
|
} |
|
money -= 1000; |
|
System.out.println(Thread.currentThread().getName() + "取出了1000元" + " 当前余额为:" + money); |
|
} |
|
try { |
|
Thread.sleep(1000); |
|
} catch (InterruptedException e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
} |
|
} |
![image-20220905205103018](/articlelist/uploads/allimg/221114/10313561O-8.png)
出处:https://www.cnblogs.com/liyuelian/p/16659594.html