多线程

  • 在一个程序中同时执行多个线程,每个线程独立执行自己的代码片段,并且共享该程序的内存空间和系统资源

相关概念

什么是程序

  • 程序是一组指令和数据的集合,用于描述计算机执行特定任务的过程

什么是进程

  • 进程就是正在运行的程序(进程是驻留在内存中的)
  • 每一个进程都有自己的PID

什么是线程

  • 一个进程至少有一个线程。线程是程序内部的一条执行路径

什么是多线程

  • 单线程

    • 吃饭的时候 先接电话 接完了以后在继续吃饭。(优雅)
    • 程序只有一个执行路径,所有的代码都是顺序执行的
  • 多线程

    • 人一边听歌一边抽烟一边打游戏 一心三用。(牛逼)
    • 每个线程都是独立执行的代码片段,程序通过启动多个线程来并发执行不同的任务或操作

多线程的优点

  • 提高程序的执行效率和响应速度
  • 提高用户体验和交互性
  • 支持复杂的程序逻辑和业务需求
  • 充分利用计算机的多核 CPU 和并发处理能力
  • 提高程序的可靠性和安全性

什么是并行、并发

  • 并行

    • 多个任务或操作同时执行,并在同一时间段内完成(多个事件在同一时刻发生)
  • 并发

    • 执行多个独立的任务或线程(100个人抢10张火车票、1000个人一起访问你的网站)

线程的创建和启动

1.1继承thread类

以下示例中one类来继承Thread类并重写run()方法 表示创建了一个线程

通过实例化调用start方法进行启动线程

当运行这段程序时,就可以看到两个线程分别交替打印出1到100的奇数和偶数了

Thread.currentThread().getName()Thread类的静态方法,调用该方法可以获取当前执行线程的名称

this.getName()是实例方法,只能在当前线程对象中使用

总之 都可以用于获取当前线程的名称

public class TestDemo {
    public static void main(String[] args) {
        //将线程进行实例化
        one o = new one();
        two t = new two();

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

    }
}

//打印偶数
class one extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <= 50; i++) {
            if (i % 2 ==0) {
                //获取线程的名称
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
}

//打印奇数
class  two extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <= 50; i++) {
            if (i % 2 != 0) {
                //获取线程的名称
                System.out.println(this.getName() + ">>>" + i);
            }
        }
    }
}

1.2通过匿名内部类创建线程

有很多时候我们在测试的时候没有时间创建好几个类进行测试 可以使用匿名内部类 这样比较方便

new Thread(){}.start();大括号中重写run方法 在写代码块

public class TestDemo2 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <= 10; i++) {
                    if (i % 2 != 0) {
                        System.out.println(this.getName() + "奇数" + i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i <= 10; i++) {
                    if (i % 2 == 0){
                        System.out.println(this.getName() + "偶数:" + i);
                    }
                }
            }
        }.start();

        new Thread(){}.start();
    }
}

2.1实现Runnable接口

以下示例中TestDemo4类实现了Runnable接口重写run方法

TestDemo4 当前实现类的对象

将实现类的对象作为参数传递到Thread构造器当中

public class TestDemo4 implements Runnable{
    public static void main(String[] args) {
        //创建实现类对象
        TestDemo4 td = new TestDemo4();
        
        //将对象作为参数传递到Thread类的构造器中 创建Thread的实例 线程0
        Thread t = new Thread(td);
        t.start();


        //主线程
        for (int i = 0; i <= 10; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
        //在创建一个线程用来遍历偶数 线程1
        Thread t1 = new Thread(td);
        t1.start();
    }

    @Override
    public void run() {
        for (int i = 0; i <= 10; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

2.2通过匿名内部类创建线程

new Thread(new Runnable() {}).start();在括号内添加new Runnable()并重写run方法就可以了

public class RunnableTest {
    public static void main(String[] args) {
        //线程0 奇数
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 35; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + "奇数" + i);
                    }
                }
            }
        }).start();


        //线程1 偶数
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 20; i++) {
                    if (i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + "偶数" + i);
                    }
                }
            }
        }).start();

    }
}

3.1实现Callable接口

  • 需要实现Callable接口并且重写call方法 优点是可以获取到线程的结果 缺点是分线程在执行中。当前线程会受到阻塞 效率低
public class CallableTest implements Callable{

    @Override
    public Object call() throws Exception {
        System.out.println("go go go!");
        Thread.sleep(1000);
        System.out.println("come on!");

        int a = 10;
        int b = 20;
        return a+b;
    }

    public static void main(String[] args) {
        //实例化当前类
        CallableTest c = new CallableTest();

        //实例化FutureTask类将当前类对象传递过去
        FutureTask<Integer> f = new FutureTask<>(c);

        //实例化Thread类
        Thread t = new Thread(f);

        //这里启动线程只会输出两句话
        t.start();

        Integer integer = null;
        try {
            //获取线程返回值
            integer = f.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("线程执行结果" + integer);

    }
}

Thread和Runnable的区别

  • Thread是一个类,而Runnable是一个接口。因此,如果想要创建一个新的线程,可以继承Thread类并重写run()方法,也可以实现Runnable接口并实现run()方法。
  • 继承Thread类会限制了进一步继承其他类的能力,而实现Runnable接口则没有这个限制,可以继续继承其他类或实现其他接口。
  • 使用Runnable接口实现的多线程应用程序更加灵活,因为可以通过传递Runnable对象给Thread构造函数来创建线程,从而实现代码的分离和重用。
  • 实现Runnable接口还可以避免单继承的限制,因为Java不支持多重继承,但是可以同时实现多个接口。
  • 在运行效率方面,使用Runnable接口实现多线程时,多个线程可以共享同一个Runnable实例,而继承Thread类时每个线程创建一个独立的线程对象,因此Runnable通常比Thread更高效。

Thread类的常用方法

1.1Thread类中的构造器

属性说明
Thread()创建一个新的线程对象
Thread(Runnable target)创建一个新的线程对象,并指定该线程要执行的任务
Thread(String name)创建一个新的线程对象,并指定该线程的名称
Thread(Runnable target, String name)创建一个新的线程对象,并指定该线程要执行的任务和名称

Thread

public class MyThread extends Thread{
    public static void main(String[] args) {
        //Thread():创建一个新的线程对象
        new Thread(){
            @Override
            public void run() {
                //获取线程名
                System.out.println(this.getName() + " HelloWorld!");
            }
        }.start();

        
        //Thread(String name):创建一个新的线程对象,并指定该线程的名称
        new Thread("我是分线程:"){
            @Override
            public void run() {
                //在运行的时候会提示 我是分线程: HelloWorld!
                System.out.println(this.getName() + " HelloWorld!");
            }
        }.start();
    }
}

Runnable

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 阿乐的小屋");
    }

    public static void main(String[] args) {
        //Thread(Runnable target) 构造方法用于创建一个新的 Thread 对象,并指定该线程要执行的任务
        MyRunnable m = new MyRunnable();
        Thread thread = new Thread(m);
        thread.start();

        //Thread(Runnable target, String name) 构造方法用于创建一个新的 Thread 对象,并指定该线程要执行的任务和线程的名称
        Thread t = new Thread(m,"分线程");
        t.start();

    }
}

1.2Thread中的常用方法

属性说明
start()启动线程并调用线程的run
Thread.sleep(long millis)使当前线程暂停执行指定的时间(以毫秒为单位)
getName()获取该线程的名称
setName()设置该线程的名称
Thread.yield()提示调度程序可以将CPU时间分配给其他线程。(满足一定条件可能会分给其他线程执行)
join()等待该线程完成执行,并阻塞当前线程直到它完成为止
isAlive()检查该线程是否处于活动状态
getPriority()获取该线程的优先级
setPriority()设置该线程的优先级
seDaemon()守护线程
Thread.MAX_PRIORITY(10)最高优先级
Thread.MIN_PRIORITY(1)最低优先级
Thread.NORM_PRIORITY(5)普通优先级 默认main具有普通优先级
  • Thread.sleep线程暂停多少秒执行(运行状态
  • getName()获取线程的名称
  • setPriority()设置线程的优先级
  • getPriority()获取线程的优先级
public class MyThread {
    public static void main(String[] args) {
        //线程一的优先级为10
        new one("线程一",10).start();
        //线程二的优先级为一
        new one("线程二",1).start();

    }
}
class one extends Thread{
    public one() {
    }

    public one(String name,int pro){
        //调用父类构造方法
        super(name);
        //设置优先级
        this.setPriority(pro);
    }

    @Override
    public void run() {
        for (int i = 0; i <= 10; i++) {
            try {
                //线程延迟1秒执行
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取线程的名称和优先级打印到控制台
            System.out.println("我是" + getName() + "我的优先级是:" + getPriority() + "\t" + i);
        }
    }
}
  • Thread.yield()调用这个方法可以将cpu给其他线程
public class MyThread {
    public static void main(String[] args) {
        new one("线程一:",5).start();
        new two("线程二:",5).start();

    }
}
class one extends Thread{
    public one(String name,int pro) {
        super(name);
        this.setPriority(pro);
    }

    @Override
    public void run() {
        for (int i = 2; i <= 100; i+=2) {
            
            //如果i模2是偶数 那么cpu的执行权限就给线程二
            if (i %2 == 0) {
                Thread.yield();
                System.out.println("执行权先给线程二");
            }
            System.out.println(getName() + i);
        }
    }
}
class two extends Thread{
    public two(String  name,int pro) {
        super(name);
        this.setPriority(pro);
    }

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(getName() + i);
        }
    }
}
  • join() 等待该线程完成执行,并阻塞当前线程直到它完成为止(阻塞状态
public class MyThread02 {
    public static void main(String[] args) {
        Thread001 t = new Thread001();
        t.setName("分线程");
        t.start();

        //主线程main 当i=10的时候会将cpu的权限给分线程 main线程会进入阻塞状态 当分线程运行完了以后才开始跑主线程
        for (int i = 0; i < 50; i++) {
            if (i == 10) {
                try {
                    t.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main --->" + i);
        }

    }
}
class Thread001 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(getName() + i);
        }
    }
}

守护线程

守护线程:比如一个软件打开以后会有一个线程专门来写入log日志 会持续10分钟写入一次 当软件关闭以后 那么这个线程也会跟随软件关闭

public class MyThread03 {
    public static void main(String[] args) {
        Thread003 t = new Thread003();
        Thread0031 t1 = new Thread0031();
        t1.start();

        //将Thread003实例化的对象设置为守护线程 当t线程运行结束后 守护线程会跟随t线程死亡
        t.setDaemon(true);
        t.start();

    }
}
class Thread0031 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i <= 10; i++) {
            System.out.println("执行" + i + "次");
        }
    }
}
class Thread003 extends Thread{
    public int i = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("我在向本地写入" + (i++) + "日志");
        }
    }
}

多线程的生命周期

生命周期

Thread 类的生命周期可以分为 5 个阶段:

  1. 新建状态(New)当我们创建一个 Thread 对象时,线程处于新建状态。此时,它并没有开始执行,只是一个刚刚创建的对象。
  2. 就绪状态(Runnable):当调用 start() 方法启动线程后,线程进入了就绪状态。此时,线程已经准备好运行,等待系统分配 CPU 时间片来执行它的任务。在多线程环境中,有多个线程处于就绪状态,等待 CPU 的调度。
  3. 运行状态(Running):当线程被系统分配到 CPU 时间片并开始执行任务时,它就进入了运行状态。此时,线程正在执行其 run() 方法中的代码。
  4. 阻塞状态(Blocked):在线程执行过程中,可能会遇到一些耗时的操作(例如等待输入输出、获取锁等),这时线程就会进入阻塞状态。在这种状态下,线程暂停执行,直到满足某些条件才能继续执行。当条件满足时,线程将从阻塞状态转换为就绪状态,等待系统重新分配 CPU 时间片。
  5. 死亡状态(Terminated):当线程执行完其 run() 方法中的所有代码后,或者由于异常而意外终止时,线程就进入了终止状态。此时,线程已经完成了自己的任务,不再占用任何资源。

线程安全问题

以下示例中one类实现了Runnable接口并模拟了多个线程共享访问a资源。会导致数据不一致问题,线程会不安全。

public class RunnableTest{
    public static void main(String[] args) {
        one o = new one();

        
        Thread t = new Thread(o,"线程一");
        Thread t2 =  new Thread(o,"线程二");
        Thread t3 =  new Thread(o,"线程三");

        t2.start();
        t.start();
        t3.start();
    }
}
class one implements Runnable{
    private int a = 100;

    @Override
    public void run() {
        while (true) {
            if (a > 0) {
                System.out.println(Thread.currentThread().getName() + "取票:" + a--);
            }
        }
    }
}

synchronized同步代码块

  • 同步代码块:synchronized(this){方法体} (synchronized括号后的数据必须是多线程共享的数据,才能达到多线程排队
public class RunnableTest{
    public static void main(String[] args) {
        one o = new one();

        //模拟两个线程并发访问 会导致数据不安全
        Thread t = new Thread(o,"线程一");
        Thread t2 =  new Thread(o,"线程二");
        Thread t3 =  new Thread(o,"线程三");

        t.start();
        t2.start();
        t3.start();
    }
}
class one implements Runnable{
    private int a = 100;

    //lock是一个对象锁,只有获取了该锁的线程才能进入同步代码块执行,其他线程需要等待锁被释放后才能进入。
    //Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            //可以使用类名.class当对象锁
            synchronized (one.class) {
                if (a > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "取票:" + a--);
                } else {
                    break;
                }
            }
        }
    }
}

synchronized同步方法

  • 创建建了一个实现了Runnable接口的类one,并在main方法中创建了三个线程分别用于调用one类中的run方法。使用while循环来保证线程一直运行。show方法被声明为synchronized,表示该方法只能同时由一个线程执行。在show方法中,使用while循环来判断是否还有票可取,如果有,则当前线程取票并打印信息,否则将b置为false表示没有票可取,结束循环。
public class RunnableTest {
    public static void main(String[] args) {
        one o = new one();
        Thread t = new Thread(o,"线程一");
        Thread t1 = new Thread(o,"线程一");
        Thread t2 = new Thread(o,"线程一");

        t.start();
        t1.start();
        t2.start();
    }
}
class one implements Runnable{
    int ticket = 10;
    boolean b = true;

    @Override
    public void run() {
        while (b) {
            show();
        }
    }
    //同步方法
    public synchronized void show(){
            while (b){
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + "取票:" + ticket--);
                }else {
                    b = false;
            }
        }
    }
}

synchronized优点和缺点

优点:可以避免多个线程同时对共享资源进行修改而导致的数据不一致问题,保证程序的正确性。

缺点:会影响程序的性能,因为每当一个线程访问一个被synchronized修饰的方法或代码块时,都必须等待其他线程释放该锁才能执行。其次,如果不正确地使用synchronized,可能会导致死锁或竞争条件等问题

  • 银行有一个账户,有两个储户向同一个账户存3000元,每次存1000存3次,每次存完打印账户余额。
public class AccountTest {
    public static void main(String[] args) {
        Account act = new Account();

        Customer c = new Customer(act);
        Customer c1 = new Customer(act);

        Thread t = new Thread(c,"甲");
        Thread t1 = new Thread(c1,"乙");

        t.start();
        t1.start();

    }
}

//账户类
class  Account{
    private double balacnce; //余额
    //
    public synchronized void deposit(double amt){
        if (amt > 0) {
            balacnce+=amt;
        }
        System.out.println(Thread.currentThread().getName() + "存钱1000块:" + "余额为:" + balacnce);
    }
}


//用户类
class Customer implements Runnable{
    Account account;

    public Customer(Account act) {
        this.account = act;

    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

多线程并发访问共享资源的情况,其中包括一个 Account 类和一个 Customer 类。在主函数中创建了两个 Customer 对象,它们共享同一个 Account 对象。

Account 类有一个私有成员变量 balance 表示余额,并且有一个 synchronized 修饰的 deposit 方法,用于往账户里存钱。在 deposit 方法中先进行参数检查,如果存款金额大于零,则将余额增加该金额,并输出当前线程名字和余额。

在 Customer 类中实现了 Runnable 接口,其构造函数接受一个 Account 对象作为参数,表示该 Customer 的操作会影响到这个账户。run 方法中使用一个 for 循环调用 Account 对象的 deposit 方法,每次存入 1000 元。由于 Customer 在不同的线程中运行,因此在多线程并发下,Account 的 deposit 方法需要被保护以避免竞态条件(race condition)的出现。

在主函数中创建了两个 Thread 对象,分别传入不同的 Customer 对象和线程名字。然后启动两个线程,它们会同时运行,各自向同一个账户存入三次 1000 元。最终输出结果将展示线程对余额的交替更改,表明了多线程对共享资源的交互访问。

Lock锁

  • Lock是一种同步机制,用于协调多个线程对共享资源的访问加锁lock(); 解锁unlock() 一般在try catch finally中使用
public class MyThread extends Thread{
    static int a = 10;

    //定义lock锁
    static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //加锁
            lock.lock();
            try {
                if (a <= 100) {
                    System.out.println(this.getName() + a++);
                }else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}
class ThreadTest{
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.setName("线程一");
        m.start();

        MyThread m1 = new MyThread();
        m1.setName("线程二");
        m1.start();

    }
}
  • 每个数字只能被一个线程输出一次,通过使用ReentrantLock实现线程安全的访问

synchronzied和lock区别

  1. Synchronized是Java中的关键字,而Lock是Java提供的一个接口。
  2. Synchronized只能使用在方法或代码块中,而Lock可以在任何时候对共享资源进行锁定和释放。
  3. Synchronized在获取锁失败时会一直等待,而Lock可以通过tryLock()方法尝试获取锁,并返回是否获取成功。
  4. Lock可以实现公平锁,即先等待的线程先获取到锁,而Synchronized只能是非公平锁。
  5. Lock可以实现多个条件变量,即不同的线程可以针对不同的条件进行等待和唤醒,而Synchronized只能有一个条件队列。

死锁

  • 两个或多个线程彼此持有对方需要的资源并相互等待,从而导致程序无法继续执行。

以下示例中LockA 中先锁住了 obj1,然后尝试去锁住 obj2,而在 LockB 中先锁住了 obj2,然后尝试去锁住 obj1。如果这两个线程以相反的顺序同时运行,就会出现死锁状态

public class LockTest {
    public static Object obj1 = new Object();
    public static Object obj2 = new Object();
    public static void main(String[] args) {
        LockA l = new LockA();
        LockB l1 = new LockB();

        Thread t = new Thread(l);
        Thread t1 = new Thread(l1);

        t.start();
        t1.start();
    }
}

class LockA implements Runnable {
    @Override
    public void run() {
        System.out.println("线程A开始执行 - - ");
        while (true) {
            synchronized (LockTest.obj1) {
                System.out.println("LockA 锁住了 obj1");

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LockTest.obj2) {
                    System.out.println("LockA 锁住了obj2");
                }
            }
        }
    }
}

class LockB implements Runnable{
    @Override
    public void run() {
        System.out.println("线程B开始执行 - - ");
        while (true) {
            synchronized (LockTest.obj2) {
                System.out.println("LockA 锁住了 obj2");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                synchronized (LockTest.obj1) {
                    System.out.println("LockB 锁住了obj1");
                }
            }
        }
    }
}

死锁产生的条件和处理措施

Java中产生死锁的条件为:

  1. 互斥条件:资源不能同时被多个线程持有。
  2. 请求与保持条件:一个线程在持有某个资源的同时,又请求其他线程持有的资源。
  3. 不剥夺条件:线程已经获得的资源只能由自己释放,不能被其他线程强制剥夺。
  4. 循环等待条件:存在一个进程资源等待序列,使得每个进程都在等待下一个进程所持有的资源。

要避免死锁,可以采取以下几种方式

  1. 避免互斥条件:尽量减少或避免对共享资源的独占性访问。
  2. 避免请求与保持条件:一次性获取所有需要的资源,或者释放已经持有的资源再去获取新的资源。
  3. 避免不剥夺条件:当线程持有某些资源时,如果需要获取其他资源而暂时无法获取,应该释放已经持有的资源。
  4. 避免循环等待条件:通过给资源编号,要求所有线程按照编号顺序申请资源,或者通过设置一个超时时间,强制释放已经持有的资源

线程通讯

两个或多个线程之间通过共享内存或消息传递来实现协作的过程。其中一个线程需要等待另一个线程发出信号或响应某个事件,才能继续执行或完成任务

Object类中的wait和notify方法

属性说明
wait()使当前线程进入等待状态并且会释放锁
notify()唤醒正在等待的线程(如果有多个线程正在等待,则随机选择一个)
notifyAll()唤醒所有等待中的线程
  1. Test类实现了Runnable接口,并且有一个私有成员变量number,用于表示当前计数器的值。
  2. run()方法是线程执行的主体,其中包含一个while循环,不断地执行以下操作:
  3. 通过synchronized关键字来保证同步,只允许一个线程进入临界区,防止多个线程同时修改number
  4. 调用notify()方法唤醒等待中的线程,使其进入就绪状态。
  5. 判断当前的number是否小于等于25,如果是,则打印当前线程的名称以及number的值,同时将number加1。
  6. 通过Thread.sleep()方法暂停当前线程一段时间,模拟计数过程中的延迟。
  7. 调用wait()方法使当前线程进入等待状态,并且释放锁对象,让其他线程可以进入临界区。
  8. 如果number大于25,跳出循环,线程执行结束。
  9. 利用synchronizednotify()wait()方法实现了一个多线程的计数器,并且保证了线程间的同步和互斥,从而避免了数据竞争的问题。
public class ThreadTest {
    public static void main(String[] args) {
        Test t = new Test();
        Thread td = new Thread(t,"线程一");
        Thread td1 = new Thread(t,"线程二");


        td.start();
        td1.start();

    }
}

class Test implements Runnable{
    private int number = 0;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                //唤醒等待中的线程
                notify();
                if (number <= 25) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + number++);

                    try {
                        //让线程一进入等待状态 释放锁对象 线程二还可以进来
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }else {
                    break;
                }
            }
        }
    }
}

生产者和消费者

生产者和消费者共享一个缓冲区(LinkedList<Integer>对象),生产者向缓冲区添加物品,消费者从缓冲区取出物品。当缓冲区已满时,生产者线程会进入等待状态;当缓冲区为空时,消费者线程会进入等待状态。在添加或取出物品后,线程会唤醒所有在等待中的线程。同时,为了模拟添加或取出物品的时间,我们使用了Thread.sleep()方法来让线程睡眠一段时间。

import java.awt.*;
import java.util.LinkedList;

public class TestDemo {
    public static void main(String[] args) {
        LinkedList<Integer> buffer = new LinkedList<>();

        producer p = new producer(buffer);
        consumer c = new consumer(buffer);

        Thread t = new Thread(p);
        Thread t1 = new Thread(c);

        t.start();
        t1.start();

    }
}

//生产者
class  producer implements Runnable{
    private final LinkedList<Integer> buffer;

    private  int count = 0; //计数器

    producer(LinkedList<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true){
            synchronized (buffer){
                while (buffer.size() == 10){
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //否则向缓存区添加物品
                buffer.add(count);
                System.out.println("生产了" + count + "个产品");
                count++;


                //唤醒所有等待中的线程
                buffer.notifyAll();
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class consumer implements Runnable{
    private final LinkedList<Integer> buffer;

    consumer(LinkedList<Integer> buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (buffer) {

                //如果缓存区为空就等待
                while (buffer.size() == 0) {
                    try {
                        buffer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //否则从缓存区取出物品
                int value = buffer.removeFirst();
                System.out.println("消费了" + value + "个产品");

                buffer.notifyAll();

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

wait和sleep的区别

wait属于Object类。它会使当前线程进入等待状态,并放弃对象锁。一般出现在同步代码块中或者同步方法中(线程之间的协调与通信,执行会进入阻塞)

sleepThread类的静态方法,他可以让线程暂停执行指定的时间。时间过去之后线程会自动恢复执行(控制线程执行的时间,执行会进入阻塞)

最后修改:2023 年 04 月 02 日
如果觉得我的文章对你有用,请随意赞赏