java 多线程

news2024/11/13 13:51:26

1.什么是进程?什么是线程?

进程是:一个应用程序(1个进程是一个软件)。

线程是:一个进程中的执行场景/执行单元。

注意:一个进程可以启动多个线程。
我们在启动java程序的时候,会先启动JVM,而JVM就是一个进程。

JVM再启动一个主线程调用main方法(main方法就是主线程)。
同时再启动一个垃圾回收线程(GC)负责看护,回收垃圾。
在java程序中至少有两个线程并发,一个是 垃圾回收线程,一个是 执行main方法的主线程

2.进程和线程是什么关系?

进程: 可以看做是现实生活当中的公司。

线程: 可以看做是公司当中的某个员工。

注意:
进程A和进程B的 内存独立不共享。

例如:
QQ是一个进程,微信是一个进程,这两个进程是独立的,不共享资源。

线程A和线程B是什么关系?

在java语言中:

线程A和线程B,堆内存方法区 内存共享。但是 栈内存 独立,一个线程一个栈。

eg.
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

eg.
火车站,可以看做是一个进程。
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。


java中之所以有多线程机制,目的就是为了 提高程序的处理效率。

3.什么是真正的多线程并发?

1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

4.使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?

main方法结束只是主线程结束了,主栈空了,其它的线程可能还在压栈弹栈。

5.对于单核的CPU来说,真的可以做到真正的多线程并发吗?

对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。

对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!!

eg.
线程A:播放音乐

线程B:运行魔兽游戏

线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
给我们的感觉是同时并发的。(因为计算机的速度很快,我们人的眼睛很慢,所以才会感觉是多线程!)

6.线程的生命周期(附图)

  1. 新建状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

在这里插入图片描述

7.多线程的创建方法

在java中,如果要实现多线程,就必须依靠线程主体类,而java.lang.Thread是java中负责多线程操作类,只需继承Thread类,就能成为线程主体类,为满足一些特殊要求,也可以通过实现Runnable接口或者Callable接口来完成定义。
具体方式如下:

1.继承Thread类,重写run方法(无返回值)

2.实现Runnable接口,重写run方法(无返回值)

3.实现Callable接口,重写call方法(有返回值且可以抛出异常)

7.1Thread类实现多线程

通过继承Thread类,并重写父类的run()方法实现

public void run()

定义线程类:

public class MyThread extends  Thread{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

多线程启动:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        t1.start();
        t2.start();
    }
}

注意:

  • t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)

  • t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
    这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
    run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

7.2Runnable接口实现多线程【比较常用】

编写一个类,实现 java.lang.Runnable 接口,实现run方法
一个类实现了接口,它还可以去继承其它的类,更灵活。


public class MyRunnable implements Runnable{
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        //设置线程名称
        t.setName("myRunnable");
        t.start();
        for (int i = 0; i < 50; i++) {
            try {
            //线程睡眠
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("main"+"-"+i);
        }
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"-"+i);
        }
    }
}

采用匿名内部类创建:

public class ThreadTest04 {
    public static void main(String[] args) {
        // 创建线程对象,采用匿名内部类方式。
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                for(int i = 0; i < 100; i++){
                    System.out.println("t线程---> " + i);
                }
            }
        });

        // 启动线程
        t.start();

        for(int i = 0; i < 100; i++){
            System.out.println("main线程---> " + i);
        }
    }
}

7.3Callable接口实现多线程

使用Runnable接口实现的多线程可以避免单继承的局限,但是还有一个问题就是run方法没有返回值,为了解决这个问题,所以提供了一个Callable接口java.util.concurrent.Callable

FutureTask类常用方法:

import java.util.concurrent.ExecutionException; // 导入ExecutionException异常包
public FutureTask(Callable<T> callable) // 构造函数:接收Callable接口实例
public FutureTask(Runable runnable,T result) // 构造函数:接收Runnable接口实例,同时指定返回结果类型 
public T get() throws InterruptedException,ExecutionException // 取得线程操作返回结果

Thread类的一个构造方法:

public Thread(FutureTask<T> futuretask) //构造方法:接收FutureTask实例化对象

定义线程主体类:

public class CallableDemo implements Callable <Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--"+i);
            sum+=i;
        }
        return sum;
    }
}

多线程启动:


public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        CallableDemo callableDemo1 = new CallableDemo();
        CallableDemo callableDemo2 = new CallableDemo();
        FutureTask<Integer> ft1 = new FutureTask<Integer>(callableDemo1);
        FutureTask<Integer> ft2 = new FutureTask<Integer>(callableDemo2);
        new Thread(ft1).start();
        new Thread(ft2).start();
        System.out.println(ft1.get());
        System.out.println(ft2.get());

    }
}

Callable接口实现采用泛型技术实现,继承需要重写call方法,再通过FutureTask包装器包装,传入后实例化Thread类实现多线程。
其中FutureTask类是Runnable接口的子类,所以才可以利用Thread类的start方法启动多线程,读者可以将call方法假设为有返回值的run方法。

8. 多线程常用操作方法

8.1 获取线程对象名字、修改线程对象名字

线程是不确定的运行状态,名称就是线程的主要标记。因此,需要注意的是,对于线程的名字一定要在启动之前设置进程名称,不建议对已经启动的线程,进行更改名称,或者为不同线程设置重名。

方法名作用
static Thread currentThread()获取当前线程对象
String getName()获取线程对象名字
void setName(String name)修改线程对象名字
public class MyRunnable implements Runnable {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        //修改线程对象名字
        t.setName("myRunnable");
        t.start();
    }

    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
        	//获取线程对象名字
            System.out.println(Thread.currentThread().getName() + "-" + i);
        }
    }
}

当线程没有设置名字的时候,默认的名字是什么?

  • Thread-0
  • Thread-1
  • Thread-2

8.2 线程休眠sleep方法

  1. 静态方法:Thread.sleep(1000);

  2. 参数是毫秒

  3. 作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
    这行代码出现在A线程中,A线程就会进入休眠。
    这行代码出现在B线程中,B线程就会进入休眠。

  4. Thread.sleep()方法,可以做到这种效果:
    间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

public class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i = 0 ; i<90 ; i++) {
			System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i );
			if(i == 20) {
				try {
					System.out.println(Thread.currentThread().getName() + " 等一会儿就要休息五秒钟了……");
					Thread.sleep(5000); // 当前线程休眠五秒钟
					 System.out.println(Thread.currentThread().getName() + " 已经休息五秒钟了……");
				}catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName() + " 休眠被打扰了……");
				}
			}
		}
	}
}

线程中断
interrupt方法定义在java.lang.Thread中,由Thread.interrupt()调用实现。该方法将会设置该线程的中断状态位,即设置为true,中断的结果线程是终止状态、还是阻塞状态或是继续运行至下一步,就取决于该程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(即中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。

方法名作用
boolean isInterrupted()判断线程是否被中断
void interrupt()中断线程执行

定义线程主体类:

public class MyThread implements Runnable {
	@Override
	public void run() {
		int i = 0;
		while(true) {
			System.out.println(Thread.currentThread().getName() + " 正在努力工作中……" + i++);
			try {
				System.out.println(Thread.currentThread().getName() + " 准备休息5秒钟了……");
				Thread.sleep(5000);  // 休眠5秒钟
				System.out.println(Thread.currentThread().getName() + " 已经休息5秒钟了……");
			}catch(InterruptedException e) {
				System.out.println(Thread.currentThread().getName() + " 被打扰了,不想工作了……");
				break;
			}
		}
	}
}

测试中断:

public class ThreadDemo {
	public static void main(String[] args) throws Exception{
		Runnable mt1 = newyThread();
		Runnable mt2 = newyThread();
		Runnable mt3 = newyThread();
		
		Thread thread1 = new Thread(mt1,"线程一"); //线程一就绪
		Thread thread2 = new Thread(mt2,"线程二"); //线程二就绪
		Thread thread3 = new Thread(mt3,"线程三"); //线程三就绪
		thread1.start(); //线程一启动
		thread2.start(); //线程二启动
		thread3.start(); //线程三启动
		
		// 以下通过利用main线程控制 线程一 中断
		Thread.sleep(6000); //使main方法先休眠6秒钟,即让子线程先运行6秒钟
		if(!thread1.isInterrupted()) {
			System.out.println("吵闹~~~");
			thread1.interrupt(); //中断线程一的执行
		}
	}
}

在这里插入图片描述
线程一在休眠期间被中断,然后直接break,也就是说以后就只有线程二和三工作了。这里每当中断执行都会产生InterruptedException异常。

8.3 线程让出yield

yield方法定义在java.lang.Thread中,由Thread.yield()调用实现。多线程在彼此交替执行的时候往往需要进行资源的轮流抢占,如果某些不是很重要的线程抢占到资源但是又不急于执行时,就可以将当前的资源暂时让步出去,交给其它资源先执行。但是,因为yeild是将线程由“运行状态”转别为“就绪状态”,这样并不能保证在当前线程调用yield方法之后,其它具有相同优先级的线程就一定能获得执行权,也有可能是当前线程又进入到“运行状态”继续运行,因为还是要依靠CPU调度才可以.

定义线程主体类:

public class MyThread implements Runnable{
	private Thread thread = null;
	public MyThread () {}
	public MyThread(Thread thread) {
		this.thread = thread;
	}
	@Override
	public void run() {
		for(int i = 0 ; i<50 ; i++) {
			System.out.println(Thread.currentThread().getName()  + " 正在工作中……" + i);
			if( i  == 30) {
				System.out.println(Thread.currentThread().getName() + " 打算将工作交给 "+thread.getName() + "了……");
				Thread.yield(); // 当前线程让步出去
				System.out.println(Thread.currentThread().getName() + " 又想自己工作了……");
			}
		}
	}
}


观察线程让出操作:

public class ThreadDemo {
	public static void main(String[] args) {
		Thread mainThread = Thread.currentThread();
		Runnable mt1 = new MyThread(mainThread);
		Thread thread1 = new Thread(mt1,"子线程");
		
		thread1.start();
		for(int i = 0 ; i < 40 ; i++) {
 			System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);
		}
	}
}

在这里插入图片描述
由以上结果容易了解到,会短暂地将资源调度让给其它的线程,当然这并不是严格的,因为还是要CPU调度的。

8.4 线程优先级

所有创造的线程都是子线程,所有的子线程在启动时都会保持同样的优先级权限,但是如果现在某些重要的线程希望可以优先抢占到资源并且先执行,就可以修改优先级权限来实现。
记住当线程的优先级没有指定时,所有线程都携带普通优先级。

需要理解的是:
优先级用从1到10的范围的整数指定。10表示最高优先级,1表示最低优先级,5是普通优先级,也就是默认优先级。
优先级相对最高的线程在执行时被给予优先权限。但是不能保证线程在启动时就进入运行状态。
优先级越高越有可能先执行。
其主要方法以及常量:

public static final int MAX_PRIORITY // 静态常量:最高优先级,数值为10
public static final int NORM_PRIORITY //静态常量:普通优先级,数值为5
public static final int MIN_PRIORITY // 静态常量:最低优先级,数值为1
public final void setPriority(int newPriority) // 普通函数:设置优先级
public final int getPriority() //普通函数:获取优先级

定义线程主体类:

public class MyThread implements Runnable{
	@Override
	public void run() {
		for(int i  = 0 ; i<20 ; i++) {
			System.out.println(Thread.currentThread().getName() + " 正在工作中……" + i);
		}
	}
}

观察线程优先级操作:

public class ThreadDemo {
	public static void main(String[] args) {
		Runnable mt1 = new MyThread();
		Runnable mt2 = new MyThread();
		
		Thread thread1  = new Thread(mt1,"线程一");
		Thread thread2  = new Thread(mt2,"线程二");
		
		// 设置优先级
		thread2.setPriority(Thread.MAX_PRIORITY);
		thread1.setPriority(Thread.MIN_PRIORITY);
		
		// 启动
		thread1.start();
		thread2.start();
	}
}

在这里插入图片描述
线程二执行的概率高于线程一的执行概率,前面的执行情况大致为交替执行,是因为它们的优先级均相等,默认都等于5。

9. 线程的同步和死锁

当这样也导致了一个问题:在某一时刻,这一份资源在某一个线程发生改变时,其它线程正在执行的操作也会受到其影响。

如下程序为售票员售票代码以及结果:

定义线程主体类:

public class MyThread implements Runnable {
    private static int ticket = 10;

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            if (ticket > 0) {
                ticket--;
                try {
                    Thread.sleep(300); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 正在售票剩余票数--" + ticket + "张");
            }
        }
    }

}

观察售票状态:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread s1 = new MyThread ();
        Thread t1 = new Thread(s1,"售票员1");
        Thread t2 = new Thread(s1,"售票员2");
        Thread t3 = new Thread(s1,"售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
以上结果就很好地说明了多线程的不安全问题,提现了数据丢失问题

9.1线程同步

解决数据共享问题必须使用同步,所谓的同步就是指多个线程在同一个时间段内只能有一个线程执行指定的代码,其他线程要等待此线程完成之后才可以继续进行执行,在Java中提供有synchronized关键字以实现同步处理,同步的关键是要为代码加上“锁”。
而锁的操作有三种:
1.同步代码块
2.同步方法
3.Lock实现

9.1.1 同步代码块实现

使用方式:

synchronized(需要同步的对象){
    需要同步的操作
}

定义线程主体类:

package thread;

/**
 * 同步锁方法
 */
public class MyThread implements Runnable {
    private static int ticket = 10;

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            synchronized (this) {
                if (ticket > 0) {
                    ticket--;
                    try {
                        Thread.sleep(300); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 正在售票剩余票数--" + ticket + "张");
                }
            }
        }
    }

}

观察售票状态:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread s1 = new MyThread ();
        Thread t1 = new Thread(s1,"售票员1");
        Thread t2 = new Thread(s1,"售票员2");
        Thread t3 = new Thread(s1,"售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}

在这里插入图片描述
虽然它的确起到了安全的作用,但是执行的效率却下降了,因为每次都只有一个线程才能访问同步代码块。

9.1.2 同步方法实现

使用方式:

利用函数包装的形式实现,如下:
修饰符 synchronized 返回类型 函数名()

定义线程主体类:


public class MyThread implements Runnable {
    private static int ticket = 10;

    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            method();
        }
    }

    private synchronized static void method() {
        if (ticket > 0) {
            ticket--;
            try {
                Thread.sleep(300); // 延迟1秒,使得ticket可以被其它线程充分改变(可能此时的ticket小于等于0了)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 正在售票剩余票数--" + ticket + "张");
        }
    }

}

观察售票状态:

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread s1 = new MyThread ();
        Thread t1 = new Thread(s1,"售票员1");
        Thread t2 = new Thread(s1,"售票员2");
        Thread t3 = new Thread(s1,"售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}

9.1.3Lock锁实现

定义线程主体类:

public class ThreadLock implements Runnable {
    ReentrantLock lock = new ReentrantLock();
    private static int ticket = 10;


    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            method();
        }
    }
    private void method() {
        lock.lock();
        try {
            if (ticket > 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ticket--;
                System.out.println(Thread.currentThread().getName() + " 正在售票剩余票数--" + ticket + "张");

            }
        }catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

观察售票状态:

public class ThreadDemo {
    public static void main(String[] args) {
        ThreadLock s1 = new ThreadLock();
        Thread t1 = new Thread(s1,"售票员1");
        Thread t2 = new Thread(s1,"售票员2");
        Thread t3 = new Thread(s1,"售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}

9.2 我们以后开发中应该怎么解决线程安全问题?

是一上来就选择线程同步吗?synchronized

不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

  • 第一种方案:尽量使用局部变量 代替 “实例变量和静态变量”。

  • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

  • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制。

9.3 线程死锁

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去,死锁的操作一般是在程序运行时候才有可能出现,死锁是在多线程开发中较为常见的一种问题,过多的同步就有可能出现死锁。

观察死锁状态:


class firstCorssBridge{
	public synchronized void tell(secondCorssBridge scb) {
		System.out.println("张三告诉王五:我先过,你后过,否则你别想过这桥!");
		scb.cross();
	}
	// 以下函数不会执行
	public synchronized void cross() {
		System.out.println("张三快快乐乐地过桥了……");
	}
}
class secondCorssBridge{
	public synchronized void tell(firstCorssBridge fcb) {
		System.out.println("王五告诉张三:我先过,你后过,否则你别想过这桥!");
		fcb.cross();
	}
	// 以下函数不会执行
	public synchronized void cross() {
		System.out.println("王五快快乐乐地过桥了……");
	}
}


public class DeadLock implements Runnable{
	private firstCorssBridge fcb = new firstCorssBridge();
	private secondCorssBridge scb = new secondCorssBridge();
	
	public DeadLock() {
		// 启动线程 并执行以下语句
		new Thread(this).start(); // 会运行run函数
		fcb.tell(scb); // 运行到里面时 fcb会等待scb
	}
	@Override
	public void run() {
		scb.tell(fcb); // 运行到里面时 scb会等待fcb
	}
	
	public static void main(String[] args) {
		new DeadLock();
	}
}

运行情况:
在这里插入图片描述
两者已经处于相互等待状态,后续代码并不会执行。

如果读者不太理解上面的代码,可以在new Thread(this).start(); // 会运行run函数语句的下面加上:

try {
		Thread.sleep(100); // 休眠0.1秒钟
	}catch(InterruptedException e) {
		e.printStackTrace();
	}

在这里插入图片描述
结果就为:
在这里插入图片描述
这实际上是“锁中有锁”的情况。

10. 守护线程

Java中的线程分为两类,用户线程和守护线程。守护线程(Daemon)是一种运行在后台的线程服务线程,当用户线程存在时,守护线程可以同时存在,但是,当用户线程不存在时,守护线程会全部消失。
其中具有代表性的就是:垃圾回收线程(守护线程)
注意:主线程main方法是一个用户线程。

主要操作方法:

public final setDaemon(boolean on) throws Exception // 普通函数:是否设置为守护线程
public  final boolean isDaemon() //普通函数:  判断是否为

观察守护线程操作:

class MyThread implements Runnable{
	private int times;
	public MyThread(int times) {
		this.times = times;
	}
	@Override
	public void run() {
		for(int i = 0 ; i<times;i++) {
			if(Thread.currentThread().isDaemon()) {
				try {
					Thread.sleep(10); // 如果是守护线程,则休眠0.01秒钟
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + " 正在工作中……"+i);
		}
	}
}


public class testDemo {
	public static void main(String[] args) {
		MyThread mt1 = new MyThread(4);
		MyThread mt2 = new MyThread(100); //守护线程的循环次数远多于用户线程
		
		Thread thread1 = new Thread(mt1,"用户线程");
		Thread thread2 = new Thread(mt2,"守护线程");
		thread2.setDaemon(true); //thread2设置为守护线程
		
		thread1.start();
		thread2.start();
	}
}

在这里插入图片描述

由以上可以了解到,守护线程已经提前结束了,原因是main线程等用户线程全部消失了。

守护线程的应用场景

在主线程关闭后无需手动关闭守护线程,因为会自动关闭,避免了麻烦,Java垃圾回收线程就是一个典型的守护线程,简单粗暴地可以理解为所有为线程服务而不涉及资源的线程都能设置为守护线程。

11. Object类的wait()、notify()、notifyAll()方法

主要方法:

void wait()	让活动在当前对象的线程无限等待(释放之前占有的锁)
void notify()	唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
void notifyAll()	唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)

1、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

2、wait方法和notify方法建立在 线程同步 的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

3、wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。

4、notify方法作用:o.notify() 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。

12. 生产者消费者模式(wait()和notify())

12.1 什么是“生产者和消费者模式”?

  1. 生产线程负责生产,消费线程负责消费。
  2. 生产线程和消费线程要达到均衡。
  3. 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法。

12.2模拟一个业务需求

当前我们在一家餐厅中,厨师生产菜,服务员端菜,但是我们只有一个盘子,只能是厨师生产好菜后,服务员在能端菜。
必须做到这种效果:生产1个消费1个。

使用wait方法和notify方法实现“生产者和消费者模式”


public class ProductionConsume {
    public static void main(String[] args) {
        Foot foot = new Foot();
        //服务员和厨师共用一个对象
        Production production = new Production(foot);
        Consume consume = new Consume(foot);
        Thread t1 = new Thread(production);
        Thread t2 = new Thread(consume);
        t1.start();
        t2.start();
    }
}

class Production implements Runnable {

    private Foot foot;

    public Production(Foot foot) {
        this.foot = foot;
    }

    @Override
    public void run() {
    //生产20个菜
        for (int i = 0; i < 20; i++) {
            if (i % 2 == 0) {
                foot.set("好吃的", "很好吃", i);

            } else {
                foot.set("锅包肉", "酸甜口", i);
            }
        }
    }
}

class Consume implements Runnable {
    private Foot foot;

    public Consume(Foot foot) {
        this.foot = foot;
    }

    @Override
    public void run() {
    //服务员获取菜的方法
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            foot.get();
        }
    }
}

class Foot {
    private String name;
    private String desc;

    private int num;

    private boolean flag = true;

    public synchronized void set(String name, String desc, int num) {
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        this.name = name;
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        this.desc = desc;
        this.num = num;
        flag = false;

        this.notify();

    }

    public synchronized void get() {
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

        }
        System.out.println(this.name + "----" + this.desc + "----" + this.num);
        flag = true;
        this.notify();
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/358642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【2】MYSQL数据的导入与导出

文章目录 MYSQL-库(相同库名称)的导入导出MYSQL-库(不同库名称)的导入导出MYSQL-表的导入导出MYSQL-表的指定查询记录导入导出前提: 客户端工具是:SQLyog MYSQL-库(相同库名称)的导入导出 1、选中指定库——右键,选择【将数据库复制到不同的主机/数据库】 2、选中指…

分布式之Raft共识算法分析

写在前面 在分布式之Paxos共识算法分析 一文中我们分析了paxos算法&#xff0c;知道了其包括basic paxos和multi paxos&#xff0c;并了解了multi paxos只是一种分布式共识算法的思想&#xff0c;而非具体算法&#xff0c;但可根据其设计具体的算法&#xff0c;本文就一起来看…

SORT与DeepSORT简介

一、MOT( mutil-object tracking)步骤 在《DEEP LEARNING IN VIDEO MUTIL-OBJECT TEACKING: A SURVEY》这篇基于深度学习多目标跟踪综述中&#xff0c;描绘了MOT问题的四个主要步骤 1.跟定视频原始帧 2.使用目标检测器如Faster-rcnn, YOLO, SSD等进行检测&#xff0c;获取目标…

vue 3.0 Vue Router导航守卫的使用

目录前言&#xff1a;安装路由快速使用1. 创建路由模块2.规定路由模式3.使用路由规则4.声明路由链接和占位符5.重定向路由6.嵌套路由7.路径参数8. 声明式和编程式导航8.1 导航到不同的位置8.2 替换当前位置8.3 路由历史9.导航守卫9.1 全局前置守卫9.2全局路由守卫的语法参数9.3…

初阶C语言——操作符【详解】

文章目录1.算术操作符2.移位操作符2.1 左移操作符2.2 右移操作符3.位操作符按位与按位或按位异或4.赋值操作符复合赋值符5.单目操作符5.1单目操作符介绍6.关系操作符7.逻辑操作符8.条件操作符9.逗号表达式10.下标引用、函数调用和结构成员11表达式求值11.1 隐式类型转换11.2算术…

关于Java的深拷贝和浅拷贝

文章目录1.拷贝的引入1.1引用拷贝1.2对象拷贝2.深拷贝与浅拷贝2.1浅拷贝2.2深拷贝1.拷贝的引入 1.1引用拷贝 创建一个指向对象的引用变量的拷贝 Teacher teacher new Teacher("Taylor",26); Teacher otherteacher teacher; System.out.println(teacher); System…

vim常用命令

vim常用三种模式 命令模式&#xff08;Command mode&#xff09; 插入模式&#xff08;Insert mode&#xff09; 末行模式&#xff08;Last line mode&#xff09; &#xff08;一&#xff09;进入命令模式 vi 或者 vim&#xff08;二&#xff09;命令模式 -> 插入模式 &…

服务器部署流程与经验记录

服务器部署流程1.项目部署1.1 重置实例密码1.2 配置安全组规则1.3 远程连接服务器1.4 安装所需软件1.5 安装Tomcat1.6 配置宝塔安全组1.7 导入数据库和项目2. 域名注册3. 网站备案1.项目部署 1.1 重置实例密码 1.2 配置安全组规则 1.3 远程连接服务器 使用VNC远程连接&#…

实用调试技巧——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰的内容是实用调试技巧&#xff0c;其实小雅兰一开始&#xff0c;也不知道调试到底是什么&#xff0c;一遇到问题&#xff0c;首先就是观察程序&#xff0c;改改这里改改那里&#xff0c;最后导致bug越修越多&#xff0c;或者是问别…

51单片机——定时器中断实验,小白讲解,相互学习

定时器介绍 1&#xff0c;CPU时序的有关知识 震荡周期&#xff1a;为单片机提供定时信号的震荡源的周期&#xff08;晶振周期或外加震荡周期&#xff09;。状态周期&#xff1a;2个震荡周期为1个状态周期&#xff0c;用S表示。震荡周期又称S周期或时钟周期。机器周期&#xff…

Java-多线程并发-线程的实现、调度和状态转换

线程的实现 线程是比进程更轻量级的调度执行单位&#xff0c;线程的引入&#xff0c;可以把一个进程的资源分配和执行调度分开&#xff0c;各个线程既可以共享进程资源( 内存地址、文件I/O等 )&#xff0c;又可以独立调度( 线程是CPU调度的基本单位 )。 Java语言则提供了在不…

300行代码手写spring初体验v1.0版本

70%猜想30%验证 spring&#xff1a;IOC 、DI、AOP、MVC MVC作为入口 web.xml 内部依赖一个DispathcheServlet这样一个接口 先来说一下springMVC的一些基础知识 整体的一个思路&#xff1a; 在web.xml里面进行了一个核心servlet的一个配置 核心就是这个DispatcherServlet …

用列表y表示序列x中每个元素是否被选中 根据列表y返回序列x中被选中的元素 itertools.compress(x,y)

【小白从小学Python、C、Java】 【计算机等级考试500强双证书】 【Python-数据分析】 用列表y表示序列x中每个元素是否被选中 根据列表y返回序列x中被选中的元素 itertools.compress(x,y) [太阳]选择题 以下关于python代码表述错误的一项是? from itertools import compress …

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A模块(4)

目录 二、竞赛注意事项 &#xff08;本模块20分&#xff09; 一、项目和任务描述&#xff1a; 二、服务器环境说明 三、具体任务&#xff08;每个任务得分以电子答题卡为准&#xff09; A-1任务一 登录安全加固&#xff08;Windows&#xff09; 1.密码策略 a.更改或创建…

未来的城市:智慧城市定义、特征、应用、场景

智慧城市是通过连接一个地区的物理、经济和社会基础设施&#xff0c;以创新、有效和高效的方式应用和实施技术来发展城市的概念&#xff0c;以改善服务并实现更好的生活质量。智慧城市是一个将信息和通信技术融入日常治理的城市区域&#xff0c;旨在实现效率、改善公共服务、增…

Kafka优化篇-压测和性能调优

简介 Kafka的配置详尽、复杂&#xff0c;想要进行全面的性能调优需要掌握大量信息&#xff0c;这里只记录一下我在日常工作使用中走过的坑和经验来对kafka集群进行优化常用的几点。 Kafka性能调优和参数调优 性能调优 JVM的优化 java相关系统自然离不开JVM的优化。首先想到…

消息队列(kafka简单使用)

Dubbo远程调用的性能问题Dubbo调用普遍存在于我们的微服务项目中这些Dubbo调用全部是同步的操作这里的"同步"指:消费者A调用生产者B之后,A的线程会进入阻塞状态,等待生产者B运行结束返回之后,A才能运行之后的代码Dubbo消费者发送调用后进入阻塞状态,这个状态表示该线…

再学C语言39:指针操作(2)

在编写处理int这样的基本类型的函数时&#xff0c;可以向函数传递int数值&#xff0c;也可以传递指向int的指针 通常直接传递int数值&#xff0c;只有需要在函数中修改该值时&#xff0c;才传递指针 对于处理数组的函数&#xff0c;只能传递指针&#xff0c;这样能使程序效率…

如何运行YOLOv6的代码实现目标识别?

YOLOv6是由美团视觉团队开发的1.环境配置我们先把YOLOv6的代码clone下来git clone https://github.com/meituan/YOLOv6.git安装一些必要的包pip install pycocotools2.0作者要求pytorch的版本是1.8.0,我的环境是1.7.0&#xff0c;也是可以正常运行的pip install -r requirement…

C#服务号推送微信公众号模板消息

一、准备工作微信公众平台&#xff1a;https://mp.weixin.qq.com/申请测试账号&#xff1a;https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?actionshowinfo&tsandbox/index微信推送消息模板不需要发布服务器&#xff0c;也不需要填写授权回调域名&#xff0c;只需要…