Java线程

本文详细介绍了Java中的多线程概念,包括进程与线程的区别、线程的创建(通过extends Thread、implements Runnable和Callable接口)、线程调度、线程池的原理与API,以及线程安全、死锁、ThreadLocal和生产者消费者模型的应用。此外,还分析了ThreadLocal的源码和仓储模型的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程

一、进程和线程

1.1进程

进程:程序是静止的,只有真正运行时的程序,才被称为进程。

特点:

  • 单核CPU在任何时间点上。
  • 只能运行一个进程。
  • 宏观并行、微观串行。

1.2线程

线程:又称轻量级进程(Light Weight Process)。

  • 程序中的一个顺序控制流程,同时也是CPU的基本调度单位。
  • 进程由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。

比如:

  • 迅雷是一个进程,当中的多个下载任务即是多个线程。
  • Java虚拟机是一个进程,默认包含主线程(main),通过代码创建多个独立线程,与main并发执行。

任何一个线程都具有基本的组成部分:

  • CPU时间片:操作系统(OS)会为每个线程分配执行时间。
  • 运行数据:
    堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象。
    栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈。

1.3创建线程

线程是对象–线程类–创建线程

创建有4种方式:

本质上是一种–就implements Runtable

1.3.1extends Thread

创建:

public class TestThread extends Thread{

	// 必须重写run方法 
	@Override
	public void run() {
		for (int i = 1; i <+ 100; i++) {
			System.out.println(Thread.currentThread().getName() +"--"+ i);
		}
	}
}

使用:

public static void main(String[] args) {
    TestThread t1 = new TestThread();
    TestThread t2 = new TestThread();

    t1.start();
    t2.start();
}

1.3.2implements Runnable

最常用的 – 适合共享数据

public class MyThread implements Runnable{

	@Override
	public void run() {
		for (int i = 1; i <= 10; i++) {
			System.out.println(Thread.currentThread().getName() +"--"+ i);
		}
	}
}

public class TestRun {

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

案例:

定义一个集合,里面存的是1-100,用两个线程随机输出集合中的十个元素,输出完就删除这个元素,不考虑线程安全的问题,用实现接口和继承两种方式 实现,最后的要求是输出剩余元素

1.extends Thread:

public class ExT extends Thread{

	ArrayList<Integer> list = new ArrayList<>();
	
	public ExT(ArrayList<Integer> list) {
		this.list = list;
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			int index = (int)(Math.random()*list.size()); // 随机生成集合范围里的下标
			// 打印信息
			System.out.println(Thread.currentThread().getName() +"-删除的元素为:-"+ list.get(index));
			// 删除对用元素
			list.remove(index);
		}
	}	
}

public static void main(String[] args) throws InterruptedException {

    ArrayList<Integer> list = new ArrayList<>();

    for (int i = 1; i <= 100; i++) {
        list.add(i);
    }

    System.out.println(list);

    ExT e1 = new ExT(list);
    ExT e2 = new ExT(list);

    e1.start();
    e2.start();

    Thread.sleep(500);
    System.out.println(list);

}

2.Implements Runtable:

public class ImRun implements Runnable{

	ArrayList<Integer> list = new ArrayList<>();
	{
		for (int i = 1; i <= 100; i++) {
			list.add(i);
		}
	}
	
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			int index = (int)(Math.random()*list.size()); // 随机生成集合范围里的下标
			// 打印信息
			System.out.println(Thread.currentThread().getName() +"-删除的元素为:-"+ list.get(index));
			// 删除对用元素
			list.remove(index);
		}
	}

}

public static void main(String[] args) throws InterruptedException {

    ImRun i = new ImRun();

    Thread t1 = new Thread(i);
    Thread t2 = new Thread(i);

    t1.start();
    t2.start();
    Thread.sleep(500);
    System.out.println(i.list);
}

这种是会存在异常(当):
在这里插入图片描述

1.3.3Callable接口

1.3.3.1Callable

public interface Callable< V >{
public V call() throws Exception;
}

案例:

计算任务,一个包含了2万个整数的数组,
分拆了多个线程来进行并行计算,最后汇总出计算的结果。

public class CalcCall implements Callable<Integer>{

    int[] nums;

    public CalcCall() {}

    public CalcCall(int[] nums) {
        super();
        this.nums = nums;
    }

    public Integer call(){
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
        }
        return sum;
    }

}

public class TestCall {
	public static void main(String[] args) throws Exception{		
		int[] num = new int[20000];
		for (int i = 0; i < num.length; i++) {
			num[i] = i + 1;
		}		
		int count = 4;
		int sum = 0;		
		List<FutureTask<Integer>> fts = new ArrayList<FutureTask<Integer>>();	
		for (int i = 0; i < count; i++) {
			int[] subNums = new int[num.length/count];
			for (int j = 0; j < subNums.length; j++) {
				subNums[j] = num[j+num.length/count*i];
			}
			CalcCall c = new CalcCall(subNums);
			// 转Runnable
			FutureTask<Integer> f = new FutureTask<Integer>(c);
			Thread t = new Thread(f);
			t.start();
			fts.add(f);
		}
		for (int i = 0; i < fts.size(); i++) {
			sum += fts.get(i).get();
		}
		System.out.println("总和为: " + sum);
	}	
}

Runnable接口和Callable接口的区别:

  • Callable接口中call方法有返回值,Runnable接口中run方法没有返回值。
  • Callable接口中call方法有声明异常,Runnable接口中run方法没有异常。
1.3.3.2Future
  • Future接口表示将要执行完任务的结果。
  • get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。
  • 常用于把CalcCall转换为Runnable
  • 相当于一个中介
CalcCall c = new CalcCall(subNums);
FutureTask<Integer> f = new FutureTask<Integer>(c);

1.3.4线程池*

1.3.4.1为什么需要线程池?
  • 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
  • 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用。
1.3.4.2线程池原理

线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。

1.3.4.3线程池API

常用的线程池接口和类(所在包java.util.concurrent)。

Executor:线程池的顶级接口。

ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。

Executors工厂类:通过此类可以获得一个线程池。

方法名描述
newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。
newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,无上限。
newSingleThreadExecutor()创建单个线程的线程池,只有一个线程。
newScheduledThreadPool()创建固定大小的线程池,可以延迟或定时执行任务。

4 种线程池

// SingleThreadExecutor 单个线程的线程池
// FixedThreadPool  固定大小的线程池
// CachedThreadPool  带缓存的线程池
// ScheduledThreadPool 定时调度的线程池

单个线程的线程池:
ExecutorService pool = Executors.newSingleThreadExecutor();
    public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

特点
    是核心线程=最大线程=1
    队列无限大
    OOM:

固定大小的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}


//带缓存的线程池
public static ExecutorService newCachedThreadPool() {
     return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
}
特点:核心线程为0,最大线程无界
    队列的特点是同步队列,不做任何缓存
    
定时调度的线程池 
ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);
		//pool.execute(new MyTask(888));
		//pool.schedule(new MyTask(8888), 2, TimeUnit.SECONDS);
		pool.scheduleAtFixedRate(new MyTask(888), 2, 3, TimeUnit.SECONDS);
		pool.shutdown();  
    
核心线程数由我们自己固定
    最大线程无界
    
    
// execute源码
public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         	如果运行的线程少于corePoolSize,请尝试
			以给定的命令作为第一个线程,启动一个新线程
			任务。对addWorker的调用会自动检查运行状态和
			workerCount,从而防止会增加
			当它不应该时,返回false。
         
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         
        	如果任务可以成功排队,那么我们仍然需要
		 	再次检查我们是否应该添加一个线程
			(因为自上次检查以来,已有的已死亡)或
		 	自进入此方法以来,池已关闭。所以我们
			重新检查状态,必要时回滚排队
			已停止,如果没有线程,则启动新线程         
         
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         
           如果我们无法将任务排队,那么我们会尝试添加一个新的
		   螺纹。如果失败了,我们知道我们已经关闭或饱和了
		   所以拒绝这个任务。         
         */
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

关闭线程:

shutDown(): void shutdown();
执行完正在执行的任务就关闭
shutDownNow(): List shutdownNow();
立即关闭,把没有执行完的任务打包作为集合返回给调用者

案例

计算任务,一个包含了2万个整数的数组,
分拆了多个线程来进行并行计算,最后汇总出计算的结果。

public static void main(String[] args) throws InterruptedException, ExecutionException {
		int[] nums = new int[20000];
		for (int i = 1; i <= 20000; i++) {
			nums[i-1] = i;
		}	
		// 使用线程池								 确定个数
		ExecutorService es = Executors.newFixedThreadPool(4);
		Callable<Integer> c1 = new Callable<Integer>() {
			int sum;
			public Integer call() throws Exception {
				for (int i = 0; i < nums.length; i++) {
					sum += nums[i];
				}
				return sum;
			}
		};
		FutureTask<Integer> f1 = new FutureTask<>(c1);
		// 提交
		es.submit(f1);
		System.out.println(f1.get());
		// 关闭线程池
		es.shutdown();
	}

1.4线程的调度

常用方法:

方法名说明
public static void sleep(long millis)当前线程主动休眠 millis 毫秒。
public static void yield()当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
public final void join()允许其他线程加入到当前线程中。
public void setPriority(int)线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多。
public void setDaemon(boolean)设置为守护线程线程有两类:用户线程(前台线程)、守护线程(后台线程)

1.4.1setPriority()设置优先级

只是一个概率问题

​ 取值是在1-10,默认是5

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

public static void main(String[] args) {
    TestThread t1 = new TestThread("线程1");
    TestThread t2 = new TestThread("线程2");
    t1.setPriority(Thread.MAX_PRIORITY); // 也可以是 1-10的整数
    t2.setPriority(Thread.MIN_PRIORITY);
    t1.start();
    t2.start();
}

1.4.2更改线程名

  1. getName()、setName()方法

    th.setName();

  2. 继承的方式:

​ 构造:

​ public MyThread(String name){

​ super(name);

​ }

​ 实现接口的方式

​ Thread th = new Thread(r,name);

​ public MyThread(String name){

​ super(name);

​ }

​ 实现接口的方式

​ Thread th = new Thread(r,name);

public class TestThread extends Thread{
    public TestThread(String name) {
        super(name);
    }
    // 必须重写run方法 
    @Override
    public void run() {
        for (int i = 1; i <+ 100; i++) {
            System.out.println(Thread.currentThread().getName() +"--"+ i);
        }
    }
}

public static void main(String[] args) {
    TestThread t1 = new TestThread("线程1");
    TestThread t2 = new TestThread("线程2");
//	t1.setName("线程1");
//	t2.setName("线程2");
    t1.start();
    t2.start();

1.4.3休眠sleep

线程的休眠

​ 释放cpu,但是不释放锁

练习:

编写一个抽取学员回答问题的程序,要求倒数三秒后输出被抽中的学员姓名

i. 采用数组存储6个学员姓名 String[]

ii. 生成0-5之间的随机数,确定被抽中的学员 start,end

iii. 在屏幕每隔一秒,依次输出3,2,1,然后输出学员姓名

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new Runnable() {
        String[] students = {"aa","bb","cc","dd","ee","ff"};
        public void run() {
            for (int i = 1; i <= 3; i++) {
                Random random = new Random();
                int num = random.nextInt(5);
                System.out.println("被抽中的学生第" +i+"位学生是:"+students[num]);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    for (int i = 3; i >= 1; i--) {
        System.out.println(i);
        Thread.sleep(1000);
    }
    t1.start();
}

1.4.4join

加入:必须优先把加入的线程任务执行完才会执行其余线程

案例:

医院每天放20个特需号和50个普通号,特需号看一个人需要200ms,普通号需要100ms,特需号优先高于普通号(5-8),如果每天普通号看到第10个号的时候,要求必须特需号看完后再继续看普通号,用多线程模拟该过程。

public class PuTong extends Thread{
	Thread t;	
	public PuTong(Thread t) {
		super();
		this.t = t;
	}
	public void run() {
		for (int i = 1; i <= 50; i++) {
			System.out.println(Thread.currentThread().getName()+"普通号"+i);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (i==10) {
				try {
					t.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}


public class TeXuHao extends Thread{
	@Override
	public void run() {
		for (int i = 1; i <= 20; i++) {
			System.out.println(Thread.currentThread().getName()+"特需号"+i);
			try {
				Thread.sleep(200);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public static void main(String[] args) {

    TeXuHao t = new TeXuHao();
    PuTong p = new PuTong(t);

    // 设置优先级
    p.setPriority(5);
    t.setPriority(8);

    p.start();
    t.start();

}

运行结果:
在这里插入图片描述

1.4.5中断

本质是发送一个信号量,只是改变一个信号值

1.5线程的生命周期

线程状态(基本):新建、就绪、运行、终止。
在这里插入图片描述
在这里插入图片描述

二、线程安全

  • 需求:A线程将“Hello”存入数组;B线程将“World”存入数组。
  • 线程不安全:
    • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
    • 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性。
    • 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。

2.1同步代码块

语法:

synchronized(临界资源对象){ //对临界资源对象加锁
//代码(原子操作)
}

注意:

2.2同步方法

语法:
synchronized 返回值类型 方法名称(形参列表){ //对当前对象(this)加锁
// 代码(原子操作)
}

注意:

2.3同步规则

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记。

  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可直接调用。

JDK中线程安全的类:

  • StringBuffer
  • Vector
  • Hashtable
    以上类中的公开方法,均为synchonized修饰的同步方法。

三、死锁

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。

案例:

public class Dead implements Runnable{
	Integer a;
	Integer b;	
	public Dead(Integer a, Integer b) {
		super();
		this.a = a;
		this.b = b;
	}
	public void run() {
		synchronized (a) {
			System.out.println(Thread.currentThread().getName() + "--获取了" + a);
			synchronized (b) {
				System.out.println(Thread.currentThread().getName() + "--获取了" + b);
			}
		}
	}
}


public class TestDead {
	public static void main(String[] args) {
		// 利用了Integer缓冲数组
		new Thread(new Dead(1,2)).start();
		new Thread(new Dead(2,1)).start();
	}
}

四、ThreadLocal

ThreadLocal叫做线程变量,意思是ThreadLocal中*****填充的变量**属于当前线程***,该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量

4.1案例:

有一千个时间,用多个线程输出。要求是一个线程里面只输出一个时间。

4.1.1版本1

自己创建1000个线程来输出

// 线程类
public class TimeTask implements Runnable {	//传一个i,线程帮你输出对应的时间
	private int i;	
	public TimeTask(int i) {
		this.i = i;
	}
	public void run() {
		String dateStr = DateUtil.format(i);
		System.out.println(Thread.currentThread().getName()+"--"+dateStr);
	}
}

 //格式化日期的
public class DateUtil {		
	public static String format(int i) {
		return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(i*1000));
	}
}

// 测试类 -- 生成1000个任务创建线程来完成
public class TestTime {	
	public static void main(String[] args) {
		for(int i=1;i<=1000;i++) {
			new Thread(new TimeTask(i)).start();
		}
	}
}

分析:

1.线程太多,浪费

2.SimpleDateFormat太多,对象浪费

4.1.2版本2

使用线程池解决线程多的问题,同时解决SimpleDateFormat的问题

// 测试类 - 使用线程池
public class TestTime {	
	public static void main(String[] args) {		
		//线程池的大小设置为多少?		
		ExecutorService pool = Executors.newFixedThreadPool(10);		
		for(int i=1;i<=1000;i++) {			
			pool.execute(new TimeTask(i));
		}
	}
}

// 格式化日期
public class DateUtil {	
    private static SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static String format(int i) {
        return sdf.format(new Date(i*1000));
    }
} 

分析:

会有相同的日期出现–SimpleDateFormat这个类是线程不安全的
一个类如果没有成员变量,那么这个类就是线程安全的。

4.1.3版本3

解决共用同一个SimpleDateFormat的线程安全问题

public class DateUtil {	
	private static SimpleDateFormat  sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	public static String format(int i) {
		String str;
		synchronized (DateUtil.class) {
			str = sdf.format(new Date(i * 1000));
		}
		return str;
	}
}

分析:

效率不行,每一个要过来格式化日期的线程都要在format方法这儿排队

4.1.4版本4

最终的改进

使用ThreadLocal解决线程隔离的问题—一个线程一个SimpleDateFormat

public class DateUtil {	
	private static ThreadLocal<SimpleDateFormat> th = new ThreadLocal<SimpleDateFormat>() {
		protected SimpleDateFormat initialValue() {
			return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		}
	};	
	public static String format(int i) {
		//需要用到SimpleDateFormat--从th中取出来
		SimpleDateFormat sdf = th.get();//从ThreadLocal对象取出SimpleDateFormat
		//sdf是线程隔离的,意味着每个线程有自己的sdf,不是所有线程共用
		String str = sdf.format(new Date(i * 1000));		
		return str;
	}
}

4.2源码分析

Thread类

ThreadLocal类

ThreadLocalMap类

// SimpleDateFormat这个对象是通过get方法获取的

// ThreadLocal类


//get 
public T get() {
    // 获取当前的线程
    Thread t = Thread.currentThread();
    // 根据当前的线程去获取到ThreadLocalMap
    // 这是一个ThreadLocal的内部类, map类型
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();// 这里只会在这个线程第一次调用get方法的时候去执行
}

// getMap
ThreadLocalMap getMap(Thread t) { // 
   // t是线程,返回的是当前线程里面的一个成员变量 -- ThreadLocalMap
    return t.threadLocals;
}

private T setInitialValue() {
        T value = initialValue();//调用初始化值的方法--子类的方法
        //new 的那一个SimpleDateFormat放到value里
        Thread t = Thread.currentThread();//获取到当前线程
        ThreadLocalMap map = getMap(t);//获取map
        if (map != null)
            map.set(this, value);//修改当前map的值
        else
            createMap(t, value);//创建一个ThreadLocalMap的对象
        return value;
}
void createMap(Thread t, T firstValue) {
    //底层实现--ThreadLocalMap---以ThreadLocal对象为键,值是存的对象
    //意味着每个线程都维护了一个ThreadLocalMap的集合,这个集合键都是ThreadLocal对象
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
 }

//Thread类
threadLocals 是一个ThreadLocalMap的一个集合

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}    

在这里插入图片描述

五、生产者消费者模型

引入:

需求:模拟银行存钱、取钱

// 银行卡 - 实体类 - 共用的
public class Card {
	private int money;

	public Card(int money) {
		super();
		this.money = money;
	}

	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}
	
	public void save() {
		while(money >= 2000) {
			System.out.println("卡里有钱了,等等在存吧");
			try {
				wait(); // 等待
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.setMoney(getMoney() + 1000);
		System.out.println("存钱成功,当前余额为:" + money);
		this.notify(); // 唤醒锁
	}
	 
	public void take() {
		while(money <= 0) {
			System.out.println("卡里没钱了,等等再来取钱吧");
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.setMoney(getMoney() - 1000);
		System.out.println("取钱成功,当前余额为: " + money);
		this.notify(); // 唤醒锁
	}	
}

// 存钱的线程
public class BF implements Runnable{

	Card card;

	public BF(Card card) {
		super();
		this.card = card;
	}

	public void run() {
		for (int i = 1; i <= 10; i++) {
			synchronized (card) {
				System.out.println(Thread.currentThread().getName() + "第" + i + "次存钱");
				card.save();
			}
		}
	}
}

// 取钱的线程
public class GF implements Runnable{

	Card card;

	public GF(Card card) {
		super();
		this.card = card;
	}

	public void run() {
		for (int i = 1; i <= 10; i++) {
			synchronized (card) {
				System.out.println(Thread.currentThread().getName() + "第" + i + "次取钱");
				card.take();
			}
		}
	}		
}

// 测试类
public class Teat {
	public static void main(String[] args) {		
		Card card = new Card(0);
		BF b1 = new BF(card);
		BF b2 = new BF(card);
		GF g1 = new GF(card);
		GF g2 = new GF(card);
	
		new Thread(b1,"男1").start();
		new Thread(b2,"男2").start();
		new Thread(g1,"女1").start();
		new Thread(g2,"女2").start();
	}
}

wait():由锁来调用
这个方法是Object类的,锁可能是任意的对象,因此这个方法放到Object这个类里面是合适的。
这个方法的意思是当前线程释放掉占有的锁,加入锁的等待队列。
要想出来重新抢锁必须有其他的线程去唤醒他。—notify()随机唤醒一个线程

​ notifyAll()唤醒所有线程

死等现象:

线程调用了wait()方法并且没有传参数 → 自己醒不过来,只能调用notify()方法来唤醒无限等待状态
无限等待状态 和 睡眠状态 称为 冻结状态.

解决方法

1唤醒所有等待 – notifyAll()

2.wait(等待时间)

六、仓储模型

案例:

模拟一个蛋糕店,有两个厨师,一个消费者

每个厨师每两秒生产一个蛋糕,消费者每秒钟消费一个蛋糕,蛋糕店里有一个橱柜,橱柜里能

存放6个蛋糕,模拟生产过程,要求,橱柜满了就让厨师休息,橱柜空了,就让消费者休息。

sleep不释放锁,意味着sleep方法不会影响到橱柜满还是不满

// 蛋糕类
public class Cake {
	private String brand;
	private String dateTime;	
	public Cake() {
	}
	public Cake(String brand, String dateTime) {
		super();
		this.brand = brand;
		this.dateTime = dateTime;
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public String getDateTime() {
		return dateTime;
	}
	public void setDateTime(String dateTime) {
		this.dateTime = dateTime;
	}
	@Override
	public String toString() {
		return brand + "--" + dateTime ;
	}
}

// 橱柜 - 仓库
public class CakeFactory {
	LinkedList<Cake> cakes = new LinkedList<>(); // 蛋糕容器
	int maxCapacity = 6; // 橱柜最大容量
	int currentCapacity = 0;
	
	public CakeFactory() {
	}	
	public synchronized void cook(Cake cake) {
		while (currentCapacity >= maxCapacity) {
			System.out.println("橱柜满了,等等在做蛋糕吧");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
		currentCapacity++;
		cakes.add(cake);
		System.out.println("橱柜当前蛋糕数为:" + currentCapacity);
		try {
			Thread.sleep(2000); // 生产一个休息2秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.notifyAll();// 唤醒
	}	
	public synchronized void take() {
		while (currentCapacity == 0) {
			System.out.println("蛋糕卖完了,等等在卖吧");
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		currentCapacity--;
		cakes.removeFirst();
		System.out.println("橱柜当前蛋糕数为:" + currentCapacity);
		try {
			Thread.sleep(1000); // 消费一个蛋糕休息1秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.notifyAll();
	}	
}

// 消费者
public class Consumer implements Runnable{
	CakeFactory cakeFactory;	
	public Consumer(CakeFactory cakeFactory) {
		super();
		this.cakeFactory = cakeFactory;
	}
	public void run() {
		for (int i = 1; i <= 40; i++) {
			System.out.println(Thread.currentThread().getName() + "--消费了一个蛋糕");
			cakeFactory.take();
		}
	}
}

// 生产者
public class Producer implements Runnable{
	CakeFactory cakeFactory;	
	public Producer(CakeFactory cakeFactory) {
		super();
		this.cakeFactory = cakeFactory;
	}
	public void run() {
		for (int i = 1; i <= 20; i++) {
			Cake cake = new Cake("华生蛋糕",LocalDateTime.now().toString());
			System.out.println(Thread.currentThread().getName() + "--" + cake.getdateTime() + "生产了一个" + cake.getBrand());
			cakeFactory.cook(cake);
		}
	}
}

// 测试类
public class Test {
	public static void main(String[] args) {	
		CakeFactory cakeFactory = new CakeFactory();		
		new Thread(new Consumer(cakeFactory),"青鸢").start();
		new Thread(new Producer(cakeFactory),"鸾").start();
		new Thread(new Producer(cakeFactory),"凰").start();
	}
}

生产者消费者模型:
把加锁的逻辑放在线程中
仓储模型:
把加锁的逻辑放在仓库中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值