14.1 线程的基本概念
- 程序:为了完成某个任务和功能,选择一种编程语言编写的一组指令的集合。
- 软件:1个或多个应用程序+相关的素材和资源文件等构成一个软件系统。
- 进程:是指一个内存中运行的应用程序,每个进程都有一份独立的内存空间,进程也时程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即使一个进程从创建、运行到消亡的过程。
- 线程:线程是进程中的一个执行单元,负责当前进程中程序的其中一个任务的执行,一个进程中至少有一个线程。一个进程中事一个可以有多个线程的,这个应用程序称之为多线程程序。
简而言之:一个软件中至少有一个应用程序,应用程序的一次运行就是一个进程,一个进程中至少有一个线程。
14.2 线程的创建方式
14.2.1 继承Thread类
1、开启main线程以外的其他线程的方式:
(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
(4)线程池
2、继承Thread类
步骤:
(1)编写一个线程类(有名或匿名)继承Thread类
(2)重写父类的一个方法public void run(){}
run方法的方法体又被称为线程体,就是该线程要独立完成的任务。
(3)创建该线程类的对象
(4)调用该线程类对象的start方法
注意:这里不是直接调用run方法,而是start,
start方法的作用是启动线程,之后让CPU决定什么时候调用run方法。
如果我们程序员手动调用run方法,就不再是多线程,还是main方法单线程。
理解为:start方法是通知“线程调度器”帮你调用你写的run方法。
public class MyThread extends Thread{
@Override
public void run() {
//该线程的对象要完成打印1-100的偶数
for(int i=2; i<=100; i+=2){
System.out.println("自定义线程:" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestMyThread {
public static void main(String[] args) {
MyThread my = new MyThread();
// my.run();//手动调用run,线性执行,先执行完my.run(),才能执行下面的输出1-100的奇数
my.start();
//在main中打印1-100的奇数
for(int i=1; i<=100; i+=2){
System.out.println("main线程:" + i);
}
}
}
14.2.2 实现Runnable接口
3、实现Runnable接口
步骤:
(1)编写一个线程类(有名或匿名)实现Runnable接口
(2)重写父接口的一个抽象方法public void run(){}
run方法的方法体又被称为线程体,就是该线程要独立完成的任务。
(3)创建该线程类的对象
(4)创建一个Thread类的对象,并且让它代理我们Runnable的线程类对象
(5)调用Thread类对象的start方法
Thread t = new Thread(my);//my给t对象的target属性赋值
t.start(); //t线程启动后,线程调度器会调用就t对象的run方法
分析:Thread类的run方法源码
public void run() {
if (target != null) {
target.run();
}
}
理解为:start方法是通知“线程调度器”帮你调用你写的run方法。
public class MyRunnable implements Runnable{
@Override
public void run() {
//该线程的对象要完成打印1-100的偶数
for(int i=2; i<=100; i+=2){
System.out.println("自定义线程:" + i);
}
}
}
public class TestMyRunnable {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
// my.run();//错误
Thread t = new Thread(my);
t.start();
System.out.println("----------------");
//在main中打印1-100的奇数
for(int i=1; i<=100; i+=2){
System.out.println("main线程:" + i);
}
}
}
14.2.3 匿名内部类写法
import org.junit.Test;
public class TestCreateThread {
public static void main(String[] args) {
//匿名内部类继承Thread类
new Thread(){
@Override
public void run() {
for(int i=2; i<=1000; i+=2){
System.out.println("自定义线程偶数:" + i);
}
}
}.start();
//匿名内部类实现Runnable接口
new Thread(new Runnable(){
@Override
public void run() {
for(int i=1; i<=1000; i+=2){
System.out.println("自定义线程奇数:" + i);
}
}
}).start();
//这里有几个线程:3个
}
//测试多线程代码,不要用JUnit,使用main线程测试
@Test
public void test(){
//匿名内部类继承Thread类
new Thread(){
@Override
public void run() {
for(int i=2; i<=1000; i+=2){
System.out.println("自定义线程偶数:" + i);
}
}
}.start();
//匿名内部类实现Runnable接口
new Thread(new Runnable(){
@Override
public void run() {
for(int i=1; i<=1000; i+=2){
System.out.println("自定义线程奇数:" + i);
}
}
}).start();
}
}
14.3 Thread类的API
14.3.1 构造方法
- public Thread() :分配一个新的线程对象。
- public Thread(String name) :分配一个指定名字的新线程对象。
- public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
- public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
14.3.2 常用方法
-
public void run() :此线程要执行的任务在此处定义代码。
-
public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。
-
public String getName() :获取当前线程名称。
-
public static Thread currentThread() :返回当前正在执行的线程对象的引用。
-
public final boolean isAlive():测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。
-
public final int getPriority() :返回线程优先级
-
public final void setPriority(int newPriority) :改变线程的优先级
- 每个线程都有一定的优先级,优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级,其中setPriority方法需要一个整数,并且范围在[1,10]之间,通常推荐设置Thread类的三个优先级常量:
- MAX_PRIORITY(10):最高优先级
- MIN _PRIORITY (1):最低优先级
- NORM_PRIORITY (5):普通优先级,默认情况下main线程具有普通优先级。
-
public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
-
public static void yield():yield只是让当前线程暂停一下,让系统的线程调度器重新调度一次,希望优先级与当前线程相同或更高的其他线程能够获得执行机会,但是这个不能保证,完全有可能的情况是,当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
-
void join() :等待该线程终止。
void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。如果millis时间到,将不再等待。
void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。
public class TestThreadMethod1 {
public static void main(String[] args) {
try {
Thread.sleep(1000);//让当前线程休眠1000毫秒再继续
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("继续");
}
}
public class TestThreadMethod2 {
public static void main(String[] args) {
MyThread m1 = new MyThread();
m1.start();
MyThread m2 = new MyThread();
m2.start();
MyThread m3 = new MyThread();
m3.setName("线程");
m3.start();
System.out.println(Thread.currentThread().getName()+"线程对象名");
//这个语句一定是main方法执行,因为它在main方法体中
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println(getName()+"在执行...");
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"在执行****");
}
}
public class TestThreadMethod3 {
public static void main(String[] args) {
/* System.out.println(Thread.MAX_PRIORITY);//10
System.out.println(Thread.MIN_PRIORITY);//1
System.out.println(Thread.NORM_PRIORITY);//5*/
ThreadDemo t = new ThreadDemo();
// t.setPriority(1000);//java.lang.IllegalArgumentException(非法参数错误)
t.setPriority(Thread.MAX_PRIORITY);
t.start();
ThreadExample e = new ThreadExample();
e.setPriority(Thread.MIN_PRIORITY);
e.start();
/*Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for(int i=1; i<=100; i+=2){
System.out.println("主方法中的:"+Thread.currentThread().getName() + "线程偶数:" + i);
}*/
}
}
class ThreadDemo extends Thread{
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println(getName() + "线程奇数:" + i);
}
}
}
class ThreadExample extends Thread{
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println(getName() + "线程偶数:" + i);
}
}
}
public class TestThreadMethod4 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
for(int i=1; i<=500; i+=2){
System.out.println(getName() + "线程奇数:" + i);
}
}
};
t.start();
while(true){
if(t.isAlive()){
System.out.println("t线程还活着");
}else{
System.out.println("t线程已经停止");
break;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestThreadMethod5 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for(int i=2; i<=30; i+=2){
System.out.println("偶数:" + i);
//要求每隔1秒打印1个偶数
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread t2 = new Thread(){
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
if(i==5){
// Thread.yield();
try {
// t1.join();
/*
当前线程是t2线程,被加塞,被阻塞
t1加入进来,t1先执行
*/
t1.join(10*1000);
/*
当前线程是t2线程,被加塞,被阻塞
t1加入进来,t1先执行10秒,然后t1,t2再一起
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
};
t2.start();
}
}
14.3.3 线程停止
一个线程如何让另一个线程提前结束呢?
线程的死亡有两种:
自然死亡:当一个线程的run方法执行完,线程自然会停止。
意外死亡:当一个线程遇到未捕获处理的异常,也会挂掉。
我们肯定希望是让线程自然死亡更好。
- public final void stop():强迫线程停止执行。 该方法具有固有的不安全性,已经标记为@Deprecated==(已过时、已废弃)==不建议再使用,那么我们就需要通过其他方式来停止线程了,其中一种方式是使用变量的值的变化来控制线程是否结束。
- 标记法
public class TestThreadMethod6 {
public static void main(String[] args) {
PrintEvenThread even = new PrintEvenThread();
// PrintOddThread odd = new PrintOddThread(even);
PrintOddThread odd = new PrintOddThread();
even.start();
odd.start();
//如果odd线程完事了,让even停下来
try {
odd.join();
/*
被阻塞的线程是main线程
加入的线程是odd线程
main线程让odd线程先执行,注意,此时和even无关。
即odd和even是并列关系。
main线程要等odd完全结束之后,下面的代码才能走
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
even.setFlag(false);
}
}
class PrintEvenThread extends Thread{
private boolean flag = true;
@Override
public void run() {
for(int i=2; i<=100 && flag; i+=2){
System.out.println("偶数:" + i);
//要求每隔1毫秒打印1个偶数
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class PrintOddThread extends Thread{
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
}
}
}
/*
class PrintOddThread extends Thread{
private PrintEvenThread evenThread;
public PrintOddThread(PrintEvenThread evenThread) {
this.evenThread = evenThread;
}
@Override
public void run() {
for(int i=1; i<=100; i+=2){
System.out.println("奇数:" + i);
}
evenThread.setFlag(false);
}
}*/
public class TestThreadMethod7 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
for(int i=2; i<=100; i+=2){
System.out.println("偶数:" + i);
//要求每隔1毫秒打印1个偶数
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 1; i <= 100; i += 2) {
System.out.println("奇数:" + i);
}
}
};
t2.start();
try {
t2.join();
/*
被阻塞的线程是main线程
加入的线程是odd线程
main线程让odd线程先执行,注意,此时和even无关。
即odd和even是并列关系。
main线程要等odd完全结束之后,下面的代码才能走
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();//中断,会发生意外
}
}
14.3.4 守护线程
有一种线程,它是在后台运行的,它的任务是为其他线程提供服务的,这种线程被称为“守护线程”。JVM的垃圾回收线程就是典型的守护线程。
守护线程有个特点,就是如果所有非守护线程都死亡,那么守护线程自动死亡。
调用setDaemon(true)方法可将指定线程设置为守护线程。必须在线程启动之前设置,否则会报IllegalThreadStateException异常。
调用isDaemon()可以判断线程是否是守护线程。
public class TestThreadMethod8 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
for(int i=2; i<=200 ; i+=2){
System.out.println("偶数:" + i);
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
MyDaemon my = new MyDaemon();
my.setDaemon(true);//让my线程称为守护线程的角色
my.start();
}
}
class MyDaemon extends Thread{
@Override
public void run() {
while(true){
System.out.println("我默默的守护你,不独活于世界!!!");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
14.4 线程安全
14.4.1 什么是线程安全问题
1、什么是线程安全问题?
当多个线程同时操作一个共享数据(这个共享数据可以是一个变量、一个对象、一个文件、一条数据库记录等),
并且这多个线程对这个共享数据有读有写操作,就会有线程安全问题。
涉及问题:
(1)什么样的数据可以共享?
讨论哪些变量,哪些对象可以共享?
局部变量不共享。
不同线程使用不同对象的实例变量,它们是无法共享。
不同线程使用同一个对象的实例变量,它们是可以共享。
不同线程访问同一个类的静态变量可以共享。
(2)线程安全问题的表现是什么?
多个线程访问共享数据的情况有点错乱。
经典案例:卖票问题
3个窗口同时卖票,总票数是10张。
1、局部变量不共享
public class TestUnShare {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
@Override
public void run() {
int total = 10;//局部变量不共享
while(total>0){
total--;
System.out.println(getName()+"卖出1张票,剩余:" + total);
}
}
}
2、多个线程使用不同对象的实例变量(不共享)
public class TestUnShare2 {
public static void main(String[] args) {
TicketWindow t1 = new TicketWindow();
TicketWindow t2 = new TicketWindow();
TicketWindow t3 = new TicketWindow();
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow extends Thread{
private int total = 10;//实例变量,3个TicketWindow对象的total是独立的
@Override
public void run() {
while(total>0){
total--;
System.out.println(getName()+"卖出1张票,剩余:" + total);
}
}
}
3、同一个类的静态变量(可以共享)
public class TestUnsafe {
public static void main(String[] args) {
TicketWindow t1 = new TicketWindow();
TicketWindow t2 = new TicketWindow();
TicketWindow t3 = new TicketWindow();
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow extends Thread{
private static int total = 10;//静态变量
@Override
public void run() {
while(total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName()+"卖出1张票,剩余:" + total);
}
}
}
4、多个线程使用同一个对象的实例变量(可以共享)
public class TestUnSafe2 {
public static void main(String[] args) {
TicketRunnable runnable = new TicketRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
}
class TicketRunnable implements Runnable{//实现接口
private int total = 10;//实例变量,TicketRunnable的对象只有1个
@Override
public void run() {
while(total>0){
total--;
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
}
}
}
14.4.2 解决线程安全问题
2、如何解决线程安全问题?
生活中:
大小李子共用同一个卫生间,会有线程安全。
解决:加锁
代码中:
解决:加锁,给代码加锁
某一段代码当一个线程在执行时,其他线程只能等待。
同步锁:synchronized,给代码加锁的关键字
形式:
(1)同步方法:锁整个方法
(2)同步代码块:锁方法体中的一小段代码
同步锁的原理:
锁是什么?
在Java中一切皆对象,同步锁也是一个对象。
每一个Java对象在对象头中都有一个“锁”标记位,标记哪个线程占用这段代码。
所以,必须保证使用共享数据的这多个线程使用“同一个”同步锁对象。
锁对象是谁?
如果是同步方法,静态方法的话,默认选择当前类的Class对象当同步锁对象。
非静态方法的话,默认选择this对象当前同步锁对象。
注意:
(1)必须保证使用共享数据的这多个线程使用“同一个”同步锁对象。
(2)锁的代码范围不能太大,别的线程没有机会
(3)锁的代码范围不能太小,安全性没有彻底解决
代码范围原则:一次任务的完整代码
比如:卖票:
检查票数,减票数等都是一次任务的代码
1、同步锁原理
2、静态同步方法
public class TestSafe1 {
public static void main(String[] args) {
TicketWindow t1 = new TicketWindow();
TicketWindow t2 = new TicketWindow();
TicketWindow t3 = new TicketWindow();
t1.start();
t2.start();
t3.start();
}
}
class TicketWindow extends Thread{
private static int total = 1000;//静态变量
@Override
public void run() {
while (total>0) {
saleOneTicket();
}
}
public synchronized static void saleOneTicket() {
if (total > 0) {
/* try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
total--;
System.out.println(Thread.currentThread().getName() + "卖出1张票,剩余:" + total);
}
}
}
/*class TicketWindow extends Thread{
private static int total = 100;//静态变量
@Override
public void run() {
while (total>0) {//锁的范围太小了
saleOneTicket();
}
}
public synchronized static void saleOneTicket(){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
}
}*/
/*class TicketWindow extends Thread{
private static int total = 10;//静态变量
@Override
public void run() {
saleOneTicket();
}
// saleOneTicket()方法是静态方法,默认的锁对象是TicketWindow类的Class对象。
// 每一个类被加载到内存中之后,JVM都会用一个Class对象来表示这个类。
// 只要是同一个类,Class就只有一个。
//锁的范围太大了
public synchronized static void saleOneTicket(){
while(total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
}
}
}*/
/*
class TicketWindow extends Thread{
private static int total = 10;//静态变量
// run()方法是非静态方法,默认锁对象是this,
// 这里三个TicketWindow的this对象不是同一个
@Override
public synchronized void run() {
while(total>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
total--;
System.out.println(getName()+"卖出1张票,剩余:" + total);
}
}
}*/
3、非静态同步方法
public class TestSafe2 {
public static void main(String[] args) {
TicketRunnable runnable = new TicketRunnable();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
}
class TicketRunnable implements Runnable{//实现接口
private int total = 1000;//实例变量
//run方法是非静态方法,默认锁对象是this
//这里TicketRunnable类的对象只有1个,同一个锁对象
@Override
public void run() {
while(total>0){
saleOneTicket();
}
}
public synchronized void saleOneTicket(){
if(total>0){
total--;
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
}
}
}
/*
class TicketRunnable implements Runnable{//实现接口
private int total = 1000;//实例变量
//run方法是非静态方法,默认锁对象是this
//这里TicketRunnable类的对象只有1个,同一个锁对象
//锁的范围太大了
@Override
public synchronized void run() {
while(total>0){
total--;
System.out.println(Thread.currentThread().getName()+"卖出1张票,剩余:" + total);
}
}
}*/
4、同步代码块
3、同步方法
【其他修饰符】 synchronized 返回值类型 方法名(【形参列表】)【throws 异常类型列表】{
}
4、同步代码块
synchronized(锁对象){
需要加锁的一小段代码
}
public class TestSafe3 {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int total = 1000;
private static Object lock = new Object();
@Override
public void run() {
while(total>0){
synchronized (Window.class) {
// synchronized (lock) {
// synchronized ("") {
if(total>0) {
total--;
System.out.println(getName() + "卖了一张票,剩余:" + total);
}
}
}
}
}
package com.atguigu.safe.safe;
public class TestSafe4 {
public static void main(String[] args) {
SaleRunnable s1 = new SaleRunnable(10,"Java讲座");
SaleRunnable s2 = new SaleRunnable(5,"ChatGPT讲座");
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
Thread t4 = new Thread(s2);
Thread t5 = new Thread(s2);
Thread t6 = new Thread(s2);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}
class SaleRunnable implements Runnable{
private int total;
private String title;
public SaleRunnable(int total, String title) {
this.total = total;
this.title = title;
}
@Override
public void run() {
while(total>0){
// synchronized (SaleRunnable.class) {
synchronized (this) {
if(total>0) {
total--;
System.out.println(Thread.currentThread().getName() + "卖了一张"+title+"票,剩余:" + total);
}
}
}
}
}
5、抽取资源类
public class Ticket {
private String title;
private int total;
public Ticket(String title, int total) {
this.title = title;
this.total = total;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getTotal() {
return total;
}
public void setTotal(int total) {
this.total = total;
}
@Override
public String toString() {
return "Ticket{" +
"title='" + title + ''' +
", total=" + total +
'}';
}
//锁对象:this
public synchronized void saleOneTicket(){
if(total>0) {
total--;
System.out.println(this);
}
}
}
public class TestTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket("JavaSE课程",10);
MyTicketThread ticketThread = new MyTicketThread(ticket);
Thread t1 = new Thread(ticketThread);
Thread t2 = new Thread(ticketThread);
Thread t3 = new Thread(ticketThread);
t1.start();
t2.start();
t3.start();
}
}
class MyTicketThread implements Runnable{
private Ticket ticket;
public MyTicketThread(Ticket ticket) {
this.ticket = ticket;
}
@Override
public void run() {
while(ticket.getTotal()>0){
/*synchronized (ticket) {//共享资源对象当锁对象
if(ticket.getTotal()>0) {
ticket.setTotal(ticket.getTotal() - 1);
System.out.println(ticket);
}
}*/
ticket.saleOneTicket();
}
}
}
14.5 线程通信
1、什么是生产者与消费者问题?
当多个线程使用同一个共享的数据缓冲区,
其中一个/些线程是给增加共享数据缓冲区添加数据,(比喻生产过程)==>生产者线程
而另一个/些线程是从共享的数据缓冲区取走数据。(比喻消费过程)==>消费者线程
问题:
(1)多个线程,有共享数据,有读和写 ==> 线程安全问题 ==> JavaSE加同步锁
(2)数据缓冲区通常会有大小的限制,
当数据缓冲区满的时候,生产过程就要“暂停等待”,当消费者线程消费了一些数据之后,再“通知”生产者线程再继续。
反过来,
当数据缓冲区空的时候,消费过程就要“暂停等待”,当生产者线程生产了一些数据之后,再“通知”消费者线程再继续。
案例:
李世杰在附近开了一个餐馆,一开始,店比较小。
李世杰找了对象,一起干。
一个在厨房,一个在大堂。
厨房和大堂之间有个小窗口,传菜的。这个窗口上有一个小平台,可以放5份菜。
2、等待与唤醒机制:线程通信机制
依赖于Object类中的几个方法:
void wait()
void wait(long mill)
void wait(long mill, long na)
void notify()
void notifyAll()
以上几个方法,必须由“同步锁/线程的监视器”对象调用,否则就会报IllegalMonitorStateException
一个生产者一个消费者
public class Window {
private int total;//记录窗台上面放的菜的数量
private Object lock = new Object();
//put方法是非静态方法,默认锁对象是this
/*public void put(){
synchronized(lock) {
if (total >= 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
}
}*/
/* public void put(){
synchronized(this) {
if (total >= 5) {
try {
wait();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
}
}*/
public synchronized void put() {
if (total >= 5) {
try {
wait();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
notify();
}
public synchronized void take() {
if (total <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total--;
System.out.println(Thread.currentThread().getName() + "取走一份菜,台上:" + total);
notify();
}
}
public class TestWindow {
public static void main(String[] args) {
Window w = new Window();
new Thread(){
@Override
public void run() {
while(true){
/* try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
w.put();
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
w.take();
}
}
}.start();
}
}
多个生产者多个消费者
public class Window {
private final int MAX_TOTAL = 5;
private int total;//记录窗台上面放的菜的数量
private Object lock = new Object();
//put方法是非静态方法,默认锁对象是this
/*public void put(){
synchronized(lock) {
if (total >= 5) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
}
}*/
/* public void put(){
synchronized(this) {
if (total >= 5) {
try {
wait();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName() + "炒了一份菜,台上:" + total);
}
}*/
public synchronized void put() {
while (total >= MAX_TOTAL) {
try {
wait();//this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total++;
System.out.println(Thread.currentThread().getName()
+ "炒了一份菜,台上:" + total);
// notify();
notifyAll();
}
public synchronized void take() {
while (total <= 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
total--;
System.out.println(Thread.currentThread().getName()
+ "取走一份菜,台上:" + total);
// notify();
notifyAll();
}
}
public class TestWindow {
public static void main(String[] args) {
Window w = new Window();
Cook c1 = new Cook("光头强",w);
Cook c2 = new Cook("翠花",w);
Waiter w1 = new Waiter("熊大",w);
Waiter w2 = new Waiter("熊二",w);
c1.start();
c2.start();
w1.start();
w2.start();
}
}
class Cook extends Thread{
private Window w;
public Cook(String name, Window w) {
super(name);
this.w = w;
}
@Override
public void run() {
while(true){
w.put();
}
}
}
class Waiter extends Thread{
private Window w;
public Waiter(String name, Window w) {
super(name);
this.w = w;
}
@Override
public void run() {
while(true){
w.take();
}
}
}