线程有一种特殊的线程:
守护线程
特殊:
(1)它不能单独存在
(2)只要非守护线程结束了,守护线程自动结束
JVM有没有默认的守护线程:GC回收线程,异常检测的线程
public class Daemon {
public static void main(String[] args) {
MyDaemon my = new MyDaemon();
my.setDaemon(true);//使得my线程为守护线程
my.start();
for (int i=1; i<=5; i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:" + i);
}
}
}
class MyDaemon extends Thread{
/*
这里不能throws的原因是因为父类的run没有抛出编译时异常,
子类重写时也不能抛出
*/
@Override
public void run(){
while (true){
try {
Thread.sleep(1);
System.out.println("我默默的守护着你...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
一、线程安全问题
1、什么是线程安全问题?
同一个进程的多个线程,会共享内存(方法区,堆),
即多个线程会同时对某一个对象的实例变量或某个类的静态变量进行读(获取)和写(修改)操作,
那么就会互相影响,这个时候就可能引起线程安全问题。
说明:如果大家对同一个数据只是进行“读”操作,是不会有线程安全问题。
比喻:
大家多个人合租住一家,卫生间是共用的。
如果大家定了一个规则,每个人每次使用卫生间的时间不能超过2分钟。
那么这个时间,就可能发生这样的情况,当小贾进入卫生间了,但是2分钟之内,
没完事,小张就进去了,这个时候就很尴尬了。
Java中:
多个线程,每一个线程CPU给它分配的时间是非常短的,
所以很可能在我这个线程的任务还没有完成时,CPU就发生了切换,
给另一个线程执行了,并且此时另一个线程对“共享数据”进行了修改,
导致了我这个线程,前后对同一个变量的值的使用是不同,就会有问题。
如何判断是否有线程安全问题?
(1)多个线程
(2)有共享数据
(3)多个线程对共享数据同时有读和写的操作
2、
举例:卖票
总的票数有10张,分为三个窗口卖。
三个窗口同时卖,好比三个线程
3、每个线程独立的
(1)虚拟机栈
(2)本地方法栈
(3)程序计数器
4、如何解决线程安全问题?
JavaSE阶段是使用synchronized(同步)解决,本质上就是加锁。
(1)同步代码块
语法格式:
synchronized(锁对象){
需要加锁的代码,需要保证不被其他线程干扰的代码
}
什么对象当锁?
A:锁对象的类型没有要求,任意引用数据类型都可以
B:必须保证,有竞争关系的多个线程(即使用同一个共享数据的多个线程),使用同一个锁对象。
public class Safe {
public static void main(String[] args) {
Ticket1 t1 = new Ticket1();
Ticket1 t2 = new Ticket1();
Ticket1 t3 = new Ticket1();
t1.start();
t2.start();
t3.start();
}
}
class Ticket1 extends Thread{
private static int total= 20;
private static Object lock = new Object();
public void run(){
while (true) {
synchronized (lock) {//此处使用lock可以,因为静态的是所有对象共享的
if(total > 0) {
try {
Thread.sleep(1000);//加入这个代码是为了扩大问题的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName() + "卖了一张票,剩余" + total + "张");
}else{
break;
}
}
}
//出了synchronized代码块,自动释放锁
}
}
/*class Ticket1 extends Thread{
private static int total= 10;
public void run(){
synchronized (this) {//此处使用this不合适,因为此时不同的线程的this是不同的
while (total > 0) {//并且此时把循环锁到代码块中是不合适的
try {
Thread.sleep(1000);//加入这个代码是为了扩大问题的效果
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName() + "卖了一张票,剩余" + total + "张");
}
}
}
}*/
//total是每一个对象独立的
/*class Ticket1 extends Thread{
private int total= 10;
public void run(){
while(total>1){
total--;
System.out.println(getName()+ "卖了一张票,剩余" + total +"张");
}
}
}*/
/*
class Ticket extends Thread{
public void run(){
//这里的i是局部变量,不是线程共享的
for (int i=10; i>=1; i--){
System.out.println(getName()+ "卖了一张票,剩余" + (i-1) +"张");
}
}
}*/
(2)同步方法
语法格式:
【其他修饰符】 synchronized 返回值类型 方法名(【形参列表】)【throws 异常列表】{
}
同步方法的锁对象是谁?同步方法的锁对象是固定的,默认的,程序员无法手动选择。
A:非静态方法:this
B:静态方法:当前类的Class对象
每一个类被加载到内存的方法区之后,JVM会用一个Class对象来代表这个类。
不管是String类,Student类,Thread类,Ticket3类,都会有一个Class对象代表这个类。
每一个类的Class对象是不同的,但是只要类相同,Class对象就是相同。
public class Safe3 {
public static void main(String[] args) {
Ticket3 t1 = new Ticket3();
Ticket3 t2 = new Ticket3();
Ticket3 t3 = new Ticket3();
t1.start();
t2.start();
t3.start();
}
}
class Ticket3 extends Thread{
private static int total = 10;
public void run(){//这里run()是非静态的,锁对象是this,不合适
while (total>0){
saleOneTicket();
}
}
//静态方法的锁对象,选择当前类的Class对象。Ticket3.class
private synchronized static void saleOneTicket(){
if(total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余:" + total);
}
}
}
/*
class Ticket3 extends Thread{
private static int total = 10;
public synchronized void run(){//这里run()是非静态的,锁对象是this,不合适
while (total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName()+"卖出了一张票,剩余:" + total);
}
}
}*/
public class Safe4 {
public static void main(String[] args) {
Ticket4 t = new Ticket4();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket4 implements Runnable{
private int total = 10;
public void run(){//这里run()是非静态的,锁对象是this,不合适
while (total>0){
saleOneTicket();
}
}
//非静态方法的锁对象,选择当前对象this,这里Ticket4只有一个,是可以的
private synchronized void saleOneTicket(){
if(total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName()+"卖出了一张票,剩余:" + total);
}
}
}
二、线程的等待唤醒机制
1、为什么需要等待唤醒机制?
之前的例子,多个线程做的任务是相同,要么都是跑步,要么都是卖票。
现在我们有一种新的情况,多个线程的任务不同,并且要相互协作完成。
例如:
一个(一部分)线程负责“生产”数据,
另一个(另一部分)线程负责“消费”数据。
当“生产者线程”没有生产数据时,数据是“空”,此时“消费者线程”是无法正常工作,必须“等待”。
并且如果有的时候,存储数据的空间是有限的,此时“消费者线程”工作的有点慢,或者说没有抢到CPU资源,
就会导致“生产者线程”把数据存储空间给“填满”,此时“生产者线程”就不应该再继续生产了,必须“等待”。
当然如果“数据存储空间"没有限制,那么”生产者线程“无需等待。
当数据是“空”时,或者说,消费者线程已经消费了一部分数据,此时都可以“唤醒”生产者可以继续生产了。
如果当存储数据的空间是有限时,存储空间已经满了,此时可以“唤醒”消费者继续消费。
这个就是“等待唤醒机制”。
2、如何等待与唤醒?
需要用到Object类中的几个方法:
(1)void wait():不限时等待,直到被唤醒
(2)void wait(long timeout):限时等待,要么被唤醒了,要么时间到了
(3)void wait(long timeout, int nanos) :限时等待,要么被唤醒了,要么时间到了
(4)void notify() :唤醒一个等待的线程
(5)void notifyAll() :唤醒所有等待的线程
以上5个方法的调用,必须由锁对象(监视线程的对象,或称为对象的监视器)调用。否则报IllegalMonitorStateException
只有在synchonized同步方法或同步代码块中才有锁对象
3、如何使用以上方法?
例如:1213班开了一个小饭馆。
厨房与大堂之间有一个窗口,这个窗口时厨房与大堂之间传递“菜”用的,这个窗口的工作台上最多只能放10盘菜。
要求:当工作台上的“菜”的数量达到10时,厨师就要休息,停止做菜。
当工作台上的“菜”的数量为0时,服务员就要休息,停止上菜。
public class WaitNotify {
public static void main(String[] args) {
Workbench w = new Workbench();
Cook cook = new Cook("小贾",w);
Waiter waiter = new Waiter("小张",w);
cook.start();
waiter.start();
}
}
class Workbench{
private static final int MAX_VALUE = 10;
private int total;//代表工作台上的菜的数量
//由厨师调用,调用一次,菜的数量就增加
//非静态方法的锁对象,就是this
//this是调用这个方法的对象
public synchronized void put(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if(total >= MAX_VALUE){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
total++;
System.out.println(Thread.currentThread().getName() + "做好了一份菜,现在工作台上菜的数量是:" + total);
this.notify();
}
//由服务员调用,调用一次,菜的数量就减少
//非静态方法的锁对象,就是this
//this是调用这个方法的对象
public synchronized void take(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
if(total <=0 ){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在工作台上菜的数量是:" + total);
this.notify();
}
}
//厨师
class Cook extends Thread{
private Workbench w;
public Cook(String name, Workbench w) {
super(name);
this.w = w;
}
public void run(){
while(true){
w.put();
}
}
}
//服务员
class Waiter extends Thread{
private Workbench w;
public Waiter(String name,Workbench w) {
super(name);
this.w = w;
}
public void run(){
while(true){
w.take();
}
}
}
问题升级:
生产者和消费者都不止一个。
以下代码出现了问题,问题的思路:
(1)思路一:希望服务员唤醒厨师,厨师唤醒服务员。但是wait和notify方法无法控制。
Object中的wait和notify方法是由锁对象调用,它只负责唤醒,和我是同一个锁对象监视的
线程,不管它是生产者还是消费者。
(2)思路二:被唤醒的线程,不要着急执行业务逻辑代码,而是重新判断条件,
因为被唤醒后,当前的状态不可预知。
所以我们把if(xxx){
xx.wait();
}
的if修改为while
如果使用notify,会出现卡死状态,全部wait没有人唤醒。
修改代码,把notify修为notifyAll(),所有被唤醒的线程会判断条件,满足的可以执行,不满足,接着wait
public class TestWaitNotifyAll {
public static void main(String[] args) {
Workbench w = new Workbench();
Cook cook = new Cook("贾子强",w);
Waiter waiter = new Waiter("张永平",w);
Cook cook2 = new Cook("刘鑫",w);
Waiter waiter2 = new Waiter("李昊",w);
cook.start();
waiter.start();
cook2.start();
waiter2.start();
}
}
class Workbench{
private static final int MAX_VALUE = 1;//这里设置为1的目的,使得问题暴露的更快一点
private int total;//代表工作台上的菜的数量
//由厨师调用,调用一次,菜的数量就增加
//非静态方法的锁对象,就是this
//this是调用这个方法的对象
public synchronized void put(){
try {
if(total >= MAX_VALUE){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
total++;
System.out.println(Thread.currentThread().getName() + "做好了一份菜,现在工作台上菜的数量是:" + total);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();
}
//由服务员调用,调用一次,菜的数量就减少
//非静态方法的锁对象,就是this
//this是调用这个方法的对象
public synchronized void take(){
try {
if(total <=0 ){
this.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName() + "取走了一份菜,现在工作台上菜的数量是:" + total);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notify();
}
}
//厨师
class Cook extends Thread{
private Workbench w;
public Cook(String name, Workbench w) {
super(name);
this.w = w;
}
public void run(){
while(true){
w.put();
}
}
}
//服务员
class Waiter extends Thread{
private Workbench w;
public Waiter(String name, Workbench w) {
super(name);
this.w = w;
}
public void run(){
while(true){
w.take();
}
}
public class ThreadStatus {
public static void main(String[] args) {
MyThread my = new MyThread();
//此时只是new,没有start,就是新建状态,状态值是0
my.start();//启动,线程状态就是非0
// my.start();//IllegalThreadStateException
//一个线程对象,不能被start两次
}
}
class MyThread extends Thread{
public void run(){
}
}
三、死锁(了解)
什么情况下会发生死锁?
当两个线程持有对方想要的锁或资源不放手。
产生死锁的必要条件:
(1)互斥
(2)请求与保持
(3)不剥夺资源
(4)循环争夺资源,嵌套加锁
public class DeadLock {
public static void main(String[] args) {
//girl和money是两个线程要争夺的资源
Object girl = new Object();
Object money = new Object();
Boy boy = new Boy(girl,money);
Bang bang = new Bang(girl,money);
boy.start();
bang.start();
//有可能造成死锁
}
}
class Boy extends Thread{
private Object girl;
private Object money;
public Boy(Object girl, Object money) {
this.girl = girl;
this.money = money;
}
public void run(){
synchronized (money){//持有money锁
System.out.println("你放了我女朋友,我给你500万");
synchronized (girl){
System.out.println("给了对方500万,接回女朋友");
}
}//这里释放money锁
}
}
class Bang extends Thread{
private Object girl;
private Object money;
public Bang(Object girl, Object money) {
this.girl = girl;
this.money = money;
}
public void run(){
synchronized (girl){
System.out.println("你给我500万,我还给你女朋友");
synchronized (money){
System.out.println("放人,得到500万");
}
}
//这里释放gril
}
}
四、面试题
1、sleep()和wait()的区别?
(1)sleep()是在Thread类中声明的静态方法。
由Thread类调用。
wait()是在Object类中声明的非静态方法。
必须由“锁对象”来调用
(2)sleep()是不会释放锁的,当前这个线程休眠了,也持有锁,其他线程抢到CPU也没用,
必须等我sleep()恢复运行完成之后,释放锁,才有机会。
好比:贾子强抢到了卫生间,在里面睡着了,不开门。
wait()是会释放锁,当前线程因为某个条件不满足,从可运行状态到达阻塞状态,释放锁,让出CPU,
让其他线程执行,并唤醒我。
好比:贾子强抢到了卫生间,进去之后,发现没感觉,出来等着,让别人先上。
(3)sleep()一般是时间到了,自动回复,或者被interrupt()
而wait()可以指定时间,也可以不限时等待。