java多线程

函数执行顺序

  • 顺序执行的顺序

当被调方活动的时候,主调方是暂停运行的,直到被调方返回结束。

package 多线程机制;

public class 顺序执行的程序 {
    public static void main(String[] args) {
        System.out.println("main方法开始执行");
        System.out.println("进入m3方法");
        m3();
        System.out.println("main方法执行结束");
    }

    public static void m1() {
        System.out.println("执行m1方法");
    }
    public static void m2(){
        System.out.println("执行m2方法");
    }

    public static void m3(){
        System.out.println("m3方法开始执行");
        m1();
        m2();
        System.out.println("m3方法执行结束");
    }
}

结果:
    main方法开始执行
    进入m3方法
    m3方法开始执行
    执行m1方法
    执行m2方法
    m3方法执行结束
    main方法执行结束

分析:
在这里插入图片描述

并发执行的程序

在这里插入图片描述

线程的基本概念

  • 进程:一个应用程序的一次执行

    • 操作系统资源分配和各项资源的基本单位
    • 操作系统程序保护的基本单位
  • 线程是一个进程中某一段连续的控制流程或者执行路径,程序自身执行和调度的实体

  • 线程和进程的区别:

    • 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销
    • 线程不能独立存在,必须依附某一个进程,共享进程的某些资源
    • 线程可以看成是进程的轻量级进程,同一类线程共享代码和数据空间,每一个线程有独立的运行栈和程序计数器,线程切换的开销小
    • 多进程:在操作系统中能够同时运行多个任务
    • 多线程:在同一个应用程序中有多个顺序流同时执行
  • java的线程是通过java.lang.Thread类实现的

  • VM启动时会有一个主方法(public static void main(){})所定义的线程,该线程通过称为java程序的主线程

    • 主线程是产生其他子线程的线程
    • 主线程不一定是最后完成执行的线程,子线程可能在主线程结束之后还在运行
    • 一个.java中可以存在多个class,每一个class都可以有自己的main方法,但是VM只会进入与.java文件同名的类中的main方法中
  • 可以通过创建Thread类的实例来创建新的线程

  • 每个线程都是通过某个特定Thread对象对应的方法run()来完成其操作的,方法run()称为线程体

  • 通过**调用Thread类的start()方法来启动一个线程**

线程的创建和启动(两种方法)

第一种

: 定义线程类实现Runnable接口:

Runnable中只有一个方法
public void run()
使用Runnable接口可以为多个线程提供共享的数据
在使用Runnable接口的类的run方法中可以使用Thread的静态方法public static Thread currentThread()方法获取当前线程的引用
Thread thread = new Thread(target) // target为Runnable接口类型

  • 实例
package 多线程机制.线程的创建和启动;

class Runable1 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println("Runnable:" + i);
        }
    }
}
public class test01 {
    public static void main(String[] args) {
        Runable1 runable1 = new Runable1();
        Thread thread = new Thread(runable1);
        thread.start();

        for(int i=0;i<100;i++){
            System.out.println("main thread: " + i);
        }
    }
}

结果:
    main thread: 0
    main thread: 1
    main thread: 2
    main thread: 3
    main thread: 4
    main thread: 5
    main thread: 6
    main thread: 7
    main thread: 8
    main thread: 9
    main thread: 10
    main thread: 11
    main thread: 12
    main thread: 13
    main thread: 14
    main thread: 15
    main thread: 16
    main thread: 17
    main thread: 18
    main thread: 19
    main thread: 20
    main thread: 21
    Runnable:0
    main thread: 22
    Runnable:1
    Runnable:2
    Runnable:3
    Runnable:4
    Runnable:5
    ····

第二种

: 可以定义一个Thread的子类并重写run()方法,然后生成该类的对象

class MyThread extends Thread{
    public void run(){
        
    }
}

MyThread mythread = new MyThread()
  • 实例
package 多线程机制.线程的创建和启动;

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("myThread: " + i);
        }
    }
}
public class test02 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        for(int i=0;i<100;i++){
            System.out.println("main thread: " + i);
        }
    }
}

结果:
    main thread: 0
    main thread: 1
    main thread: 2
    main thread: 3
    main thread: 4
    main thread: 5
    main thread: 6
    main thread: 7
    main thread: 8
    main thread: 9
    main thread: 10
    main thread: 11
    main thread: 12
    main thread: 13
    main thread: 14
    main thread: 15
    main thread: 16
    main thread: 17
    main thread: 18
    main thread: 19
    main thread: 20
    main thread: 21
    main thread: 22
    main thread: 23
    main thread: 24
    main thread: 25
    main thread: 26
    main thread: 27
    main thread: 28
    main thread: 29
    main thread: 30
    main thread: 31
    main thread: 32
    main thread: 33
    main thread: 34
    main thread: 35
    main thread: 36
    main thread: 37
    main thread: 38
    main thread: 39
    main thread: 40
    main thread: 41
    main thread: 42
    main thread: 43
    main thread: 44
    main thread: 45
    main thread: 46
    main thread: 47
    main thread: 48
    main thread: 49
    main thread: 50
    main thread: 51
    main thread: 52
    main thread: 53
    main thread: 54
    main thread: 55
    main thread: 56
    main thread: 57
    main thread: 58
    main thread: 59
    main thread: 60
    main thread: 61
    main thread: 62
    main thread: 63
    main thread: 64
    main thread: 65
    main thread: 66
    main thread: 67
    main thread: 68
    main thread: 69
    main thread: 70
    main thread: 71
    main thread: 72
    main thread: 73
    main thread: 74
    main thread: 75
    main thread: 76
    main thread: 77
    main thread: 78
    main thread: 79
    main thread: 80
    main thread: 81
    main thread: 82
    main thread: 83
    main thread: 84
    main thread: 85
    main thread: 86
    main thread: 87
    main thread: 88
    main thread: 89
    main thread: 90
    main thread: 91
    main thread: 92
    main thread: 93
    main thread: 94
    main thread: 95
    main thread: 96
    main thread: 97
    main thread: 98
    main thread: 99
    myThread: 0
    myThread: 1
    myThread: 2
    myThread: 3
    myThread: 4
    myThread: 5
    myThread: 6
    myThread: 7
    myThread: 8
    ····

继承Thread类和实现Runnable接口两种方法比较

  • 实现Runnable接口(建议)

    • 代码相对复杂性:自定义Runnable类 + 创建类对象 + 创建Thread对象 + 启动
    • 由于java支持实现多接口,实现了Runnable接口,还可以实现其他接口
  • 继承Thread类支持多线程

    • 代码相对简单: 自定义继承Thread类 + 创建类实例 + 启动
    • java只允许单一继承,继承Thread类,就不能在继承其他类

java.lang.Thread类的讲解

  • 构造方法

    public Thread(); //分配新的Thread对象
    public Thread(String name); // 分配新的Thread对象,name为该线程的名字
    public Thread(Runnable target); // 包装一个实现Runnable接口的对象(target),分配新的Thread对象
    
    public Thread(ThreadGroup group,String name); // 分配新的Thread对象,group是所在的线程组,name是线程名字
    public Thread(ThreadGroup group,Runnable target); //包装一个实现Runnable接口的对象target,分配新的Thread对象,group是所在的线程组
    
    
  • 其他方法

    public void run();  如果线程是使用实现Runnable接口的对象构造的,那么该线程调用实现Runnable接口的对象的run方法;如果是继承Thread对象,则调用子类重写的run方法。
    
    public void start(); 线程进入就绪状态,java虚拟机伺机调用该线程的run方法
    
    public void stop(); 暴力停止线程,不推荐用
    
    public static void yield(); 让正在执行线程退出执行进入就绪状态,并执行其他线程
    
    public static void sleep(long millis); 让正在执行的线程休眠进入阻塞状态,在指定的毫秒数(millis)后进入就绪状态
    
    public final void join(); 将当前线程和该线程合并,即使当前线程停下来等待,直至所引用join()方法的另一个线程终止
    
    public final void wait(); 当前线程进入对象的wait pool
    
    public final void notify()/notifyAll();唤醒对象的wait pool中的一个或多个等待线程进入就绪状态
    
    public final boolean isAlive(); 判断进程是否还“活”着,即线程是否终止
    
    public final int getPriority(); 获得线程的优先级数值
    
    public final void setPriority(int newPriority); 设置线程的优先级
    
    
    
  • 线程状态图(借用)
    在这里插入图片描述

sleep()/join()/yield()方法详解

  • sleep()方法
package 多线程机制.sleep方法;



import java.util.Date;

class MyThread extends Thread{

    /**
     * 该run()方法是否可以抛出异常???
     * 不行,因为该run()方法是重写父类的run()方法,重写方法要么抛出和父类一样的异常,要么不抛出异常。
     * 但是该父类的run()方法并不抛出异常,故该异常只能在内部解决
     */
    @Override
    public void run() {
        while (true){
            System.out.println("=====" + new Date() + "=====");
            try {
                System.out.println("在睡眠");
                sleep(1000);
            }catch (InterruptedException e){
                return;
            }
        }
    }
}
public class test01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        System.out.println("----------------");
        try {
            Thread.sleep(10000);
        }catch (InterruptedException e){

        }
        System.out.println("+++++++++++++++++++++");

        /**
         * 中断线程命令
         */
        myThread.interrupt();
    }
}



结果;
    ----------------
    =====Tue Oct 15 21:17:30 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:31 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:32 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:33 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:34 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:35 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:36 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:37 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:38 CST 2019=====
    在睡眠
    =====Tue Oct 15 21:17:39 CST 2019=====
    在睡眠
    +++++++++++++++++++++
    
  • join()方法

执行join()方法,表示该线程结束后才能跑主线程接下来的代码块

在这里插入图片描述

package 多线程机制.join方法;

class TestJoin implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("我是线程");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                return;
            }
        }
    }
}
public class test01 {
    public static void main(String[] args) throws InterruptedException{
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        System.out.println("睡眠开始");
        Thread.sleep(1000);
        System.out.println("睡眠结束");

        
//        try{
//            System.out.println("执行join方法");
//            thread.join();
//            System.out.println("join方法执行完成");
//        }catch (InterruptedException e){
//
//        }

        for(int i=0;i<10;i++){
            System.out.println("我是主线程");
        }
    }
}


结果:

没加入注解部分:                                
    睡眠开始
    我是线程
    睡眠结束
    执行join方法
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    join方法执行完成
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程

加入注解部分:
    睡眠开始                                                                          
    我是线程                                    
    我是线程                                        
    睡眠结束
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是主线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程
    我是线程

  • yield()方法

使当前线程从执行状态(运行状态)变为可执行状态(就绪状态)。cup会从众多的可执行状态中选择,也就是说,当前刚刚的那个线程还有可能被再次选中,并不是说一定执行其他线程而该线程在下一次不会执行到。

package 多线程机制.yield方法;

class MyThread extends Thread{

    MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for(int i=0;i<=20;i++){
            System.out.println(getName() + " : " + i);
            if (i % 10 == 0){
                System.out.println(getName() + "要执行yield方法 " + i);
                yield();
                System.out.println(getName() + " yield方法执行完成");
            }
        }
    }
}
public class test01 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread("one");
        MyThread myThread2 = new MyThread("two");

        myThread1.start();
        myThread2.start();
    }
}


结果:
    one : 0
    one要执行yield方法 0
    two : 0
    two要执行yield方法 0
    two yield方法执行完成
    one yield方法执行完成
    two : 1
    one : 1
    two : 2
    one : 2
    two : 3
    one : 3
    two : 4
    one : 4
    two : 5
    two : 6
    two : 7
    two : 8
    two : 9
    one : 5
    two : 10
    one : 6
    two要执行yield方法 10
    two yield方法执行完成
    one : 7
    two : 11
    two : 12
    two : 13
    two : 14
    one : 8
    two : 15
    one : 9
    two : 16
    one : 10
    two : 17
    one要执行yield方法 10
    two : 18
    two : 19
    two : 20
    two要执行yield方法 20
    one yield方法执行完成
    one : 11
    one : 12
    one : 13
    one : 14
    one : 15
    one : 16
    one : 17
    one : 18
    one : 19
    one : 20
    one要执行yield方法 20
    two yield方法执行完成
    one yield方法执行完成

分析:
在这里插入图片描述

线程的优先级

  • java提供了一个线程调度器来监控程序中启动后进入就绪转态的所有线程
  • 并不是线程的优先级越高,线程就先执行。
    • 优先级反应的是线程占用资源多少,优先级越高,占用的资源越多,性能越好
  • 线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5
    - Thread.MIN_PRIORITY = 1
    - Thread.MAX_PRIORITY = 10
    - Thread.NORM_PRIORITY = 5
  • 使用下列方法获取和设置线程的优先级
    - int getPriority();
    - void SetPriority(int newPriority);

例子:

package 多线程机制.线程的优先级;

class T1 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println("T1:" + i);
        }
    }
}
class T2 implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println("T2:" + i);
        }
    }
}
public class test01 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        Thread thread1 = new Thread(t1);

        T2 t2 = new T2();
        Thread thread2 = new Thread(t2);

        // 线程的优先级越高,并不代表该线程就先执行,只能说该线程先执行的可能性大
        thread2.setPriority(Thread.NORM_PRIORITY + 4);

        thread1.start();
        thread2.start();
    }
}


结果:
    T2:0
    T1:0
    T1:1
    T1:2
    T1:3
    T2:1
    T2:2
    T1:4
    T2:3
    T2:4
    T2:5
    T2:6
    T2:7
    T2:8
    T2:9
    T1:5
    T1:6
    T1:7
    T1:8
    T1:9

结果二:
    T1:0
    T2:0
    T1:1
    T2:1
    T2:2
    T2:3
    T2:4
    T2:5
    T2:6
    T2:7
    T2:8
    T2:9
    T1:2
    T1:3
    T1:4
    T1:5
    T1:6
    T1:7
    T1:8
    T1:9

线程同步

  • 共享内存区域:被多个线程访问的变量和方法

  • 线程同步问题:共享区域被多个线程同时访问,至少有一个线程是写操作

    • 解决:
      • 协调多个线程对内存区域的访问
      • 确保线程按照正确的顺序进行访问
  • 在java中,引入对象互斥锁的概念,保证共享数据操作的完整性。每个对象都对应一个可称为“互斥锁”的标记,这个标记确保在任一时刻,只能有一个线程访问该对象。

  • 关键字来与synchronize来与对象的互斥锁联系。当某个对象synchronize修饰时,表明该对象任一时刻只能由一个线程访问。

  • synchronize可以放在方法声明中,表示整个方法为同步方法

synchronized public void add(String name){···}

案例:

package 多线程机制.线程同步;

class Bank{
    private static int num = 3000;
    public void add(String name){
        System.out.println(name + num + "!");
        try {
            Thread.sleep(20);
        }catch (InterruptedException e){

        }

        if (num > 2000){
            System.out.println(name + num + "!!");
            System.out.println(name + "取走了2000元");
            num -= 2000;
        }else
            System.out.println("余额不足");
    }
}
class Runnable1 implements Runnable{
    Bank bank = new Bank();
    @Override
    public void run() {
        bank.add(Thread.currentThread().getName());
    }
}
public class test01 {
    public static void main(String[] args) {
        Runnable1 runnable1 = new Runnable1();

        Thread thread1 = new Thread(runnable1,"小汤");
        Thread thread2 = new Thread(runnable1,"小王");

        thread1.start();
        thread2.start();
    }
}

结果一(符合实际):
    小汤3000!
    小王3000!
    小王3000!!
    小王取走了2000元
    余额不足
    
结果二:
    小汤3000!
    小王3000!
    小汤3000!!
    小王3000!!
    小汤取走了2000元
    小王取走了2000元

原因分析:

在这里插入图片描述

例子:

package 多线程机制.线程同步;

class Timer{
    private static int num = 0;
    public void add(String name){
//       注释一   
//        num++;
//        try {
//            Thread.sleep(1);
//        }catch (InterruptedException e){}
//        System.out.println("name " + "你是第" +  num + "个访问");

//       注释二
//       synchronized (this){
//            num++;
//            try {
//                Thread.sleep(1);
//            }catch (InterruptedException e){}
//            System.out.println("name " + "你是第" +  num + "个访问");
//        }
    }
}
public class test02 implements Runnable{

    Timer timer = new Timer();

    public static void main(String[] args) {
        test02 test02 = new test02();
        Thread thread1 = new Thread(test02);
        Thread thread2 = new Thread(test02);

        thread1.setName("thread1");
        thread2.setName("thread2");

        thread1.start();
        thread2.start();
    }
    @Override
    public void run() {
        timer.add(Thread.currentThread().getName());
    }
}

结果:
    将注释一去除
    name 你是第2个访问
    name 你是第2个访问
    
    将注释二去除
    name 你是第1个访问
    name 你是第2个访问

对synchronize(this)的一些理解

  • 当两个并发线程访问同一个对象时中的synchronize(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块
  • 当一个线程访问一个对象中的synchronize(this)同步代码块时,另一个线程仍然可以访问该对象的非synchronize(this)同步代码块。
  • 当一个线程访问对象的一个synchronize(this)同步代码块时,其他线程对该对象的其他synchronize(this)同步代码块访问受阻。

死锁

死锁是多线程编程中最常见的问题。若程序中有多个线程共同访问多个资源,相互之间存在竞争,就有可能产生死锁。如:当一个线程等待另一个线程持有的锁,而另一个线程也在等待第一个线程持有的锁,两个线程就进入阻塞状态。

package 多线程机制.死锁;

class DeadClock extends Thread{
    int flag;

    DeadClock(String name,int flag){
        super(name);
        this.flag = flag;
    }
    @Override
    public void run() {
        if (flag == 1){
            synchronized ("A"){
                try {
                    System.out.println(Thread.currentThread().getName() + "拿到A锁");
                    Thread.sleep(5000);
                }catch (InterruptedException e){};

                synchronized ("B"){
                    System.out.println(Thread.currentThread().getName() + "拿到B锁");
                }
                System.out.println(Thread.currentThread().getName() + "释放B锁");

            }
            System.out.println(Thread.currentThread().getName() + "释放A锁");


        }
        if (flag == 0){
            synchronized ("B"){
                try {
                    System.out.println(Thread.currentThread().getName() + "拿到B锁");
                    Thread.sleep(5000);
                }catch (InterruptedException e){};

                synchronized ("A"){
                    System.out.println(Thread.currentThread().getName() + "拿到A锁");
                }
                System.out.println(Thread.currentThread().getName() + "释放A锁");

            }
            System.out.println(Thread.currentThread().getName() + "释放B锁");

        }
    }
}
public class test01 {
    public static void main(String[] args) {
        DeadClock deadClock1 = new DeadClock("线程一",1);
        DeadClock deadClock2 = new DeadClock("线程二",0);

        deadClock1.start();
        deadClock2.start();
    }
}

结果:
    线程一拿到A锁
    线程二拿到B锁

在这里插入图片描述

synchronize的三种用法(对象锁和类锁和同步代码块)

  • 对象锁
    • 当使用synchronized修饰普通类方法时,当前加锁的级别就是实例对象,即对象锁。当多个线程并发访问该对象的同步方法、同步代码块时,会进行同步
  • 类锁
    • 当使用synchronized修饰静态方法时,那么当前加锁的级别是类,即类锁。当多个线程并发访问该类(所以实例对象)的同步方法以及同步代码块时,会进行同步
  • 同步代码块
    • 当使用synchronized修饰代码块时,那么当前加锁的级别就是synchronized(x)中配置的x对象实例,当线程并发访问该对象的同步方法、同步代码块以及当前的代码块时,会进行同步。

实例一:

package 多线程机制.线程同步;

public class test03 implements Runnable {

    public synchronized void m1(){
        System.out.println("在执行m1方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1执行完毕");
    }

    public  void m2(){
        System.out.println("在执行m2方法");
    }

    @Override
    public void run() {
        m1();
    }

    public static void main(String[] args) throws InterruptedException {
        test03 test03 = new test03();
        Thread thread = new Thread(test03);

        thread.start();
        Thread.sleep(1000);

        test03.m2();
    }
}

结果:
    在执行m1方法
    在执行m2方法
    m1执行完毕

实例二:

package 多线程机制.线程同步;

public class test03 implements Runnable {

    public synchronized void m1(){
        System.out.println("在执行m1方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1执行完毕");
    }

    public synchronized void m2(){
        System.out.println("在执行m2方法");
    }

    @Override
    public void run() {
        m1();
    }

    public static void main(String[] args) throws InterruptedException {
        test03 test03 = new test03();
        Thread thread = new Thread(test03);

        thread.start();
        Thread.sleep(1000);

        test03.m2();
    }
}


结果:
    在执行m1方法
    m1执行完毕
    在执行m2方法

在这里插入图片描述

线程等待wait()和线程唤醒notify()

  • object类中与锁操作相关成员方法

    • public final void wait() throws InterruptedException:让当前线程等待,释放当前线程所持有的共享资源的锁,直到其他线程调用notify()方法或notifyall()方法
    • public final void notify():唤醒等待此对象的共享资源的单个线程
    • public final void notifyall():唤醒等待此对象的共享资源的所有线程
  • wait()使当前线程阻塞,前提是必须获得锁。一般配合synchronized关键字使用。即,一般在synchronized同步代码块里使用wait()、notify()、notifyall()方法

  • 由于wait()、notify()、notifyall()在synchronized代码块中执行,这说明当前线程一定获取了锁。

    • 当线程执行wait()方法时,会释放当前的锁,然后让出CUP,进入等待状态
    • 只有当notify()、notifyall()被执行时,才会唤醒一个或多个正在处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或者遇到wait(),再次释放锁。
      • 也就是说,notiyf()、notifyall()的执行只是唤醒沉睡的线程,二不会立即释放锁,锁的释放要看代码的具体执行情况。
  • 在多线程中要测试某个条件的变化,使用while()而不是使用if()

    • 例子:
      • 有两个线程从list中删除数据,而只有一个线程向list中加入数据。初始化时,list为空,只有往list中添加数据后,才能删除list的数据。添加数据的线程向list添加完数据后,调用notifyall(),唤醒两个线程,但是它只添加了一个数据,而现在有两个数据,这该怎么办??
        • 如果if测试list中的数据,则会出现IndexOutOfBoundException,因为一个线程删除一个数据后,list中没有数据,第二个线程在去执行,则删除失败。
        • 但是用while()则不会,因为在执行删除之前,会判断是否成立。
  • notify()和notifyall()方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁。

  • 消费者问题

package 多线程机制.线程同步之消费者问题;


/**
 * 定义面包类,id为面包编号
 */
class Bread{
    int id;
    Bread(int id){
        this.id = id;
    }

    @Override
    public String toString() {
        return "bread: " + id;
    }
}

/**
 * 定义篮子类
 * num 表示还剩面包个数
 * breads 面包数组
 */
class Basket{
    private int num = 0;
    Bread[] breads = new Bread[6];

    /**
     * add方法为添加面包
     * 由于要共享breads,所以用synchronized锁定
     * @param bread
     */
    public synchronized void add(Bread bread){
        /**
         * while()判断篮子是否加满,
         * 如果加满,则提示生产者暂停生产,并让其wait()等待
         */
        while (num == breads.length){
            try {
                System.out.println("篮子已经装满了,请生产者暂停生产");
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        // 当消费者暂停时,唤醒消费者
        this.notify();

        breads[num] = bread;
        num++;
    }

    public synchronized Bread eat(){
        /**
         * 当篮子为空时,
         * 提示消费者暂停消费,进入wait()等待
         */
        while (num == 0){
            try {
                System.out.println("篮子为空,请消费者暂停消费");
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        /**
         * 如果篮子面包小于3,则唤醒生产者生产
         */
        if (num < 3)
            this.notify();
        /**
         * 由于前面加1,故要先减
         * 如,刚开始 breads[0]有面包, num = 1
         * 要返回时, 只能返回 breads[0] ,故num先减1
         */
        num--;
        return breads[num];
    }
}

class Producer extends Thread{
    // 篮子对象,两个线程共享的数据
    Basket basket = null;

    Producer(Basket basket){
        this.basket = basket;
    }

    @Override
    public void run() {
        for (int i=1;i<20;i++){
            Bread bread = new Bread(i);
            basket.add(bread);
            System.out.println("生产了:" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Consumer extends Thread{
    // 篮子对象,两个线程共享的数据
    Basket basket = null;
    Consumer(Basket basket){
        this.basket = basket;
    }

    @Override
    public void run() {
        for (int i=0;i<20;i++){
            Bread bread = basket.eat();
            System.out.println("消费了 " + bread);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class ProducerConsumer{

    public static void main(String[] args) {
        Basket basket = new Basket();
        Producer producer = new Producer(basket);
        Consumer consumer = new Consumer(basket);

        producer.start();
        consumer.start();
    }
}

结果:
    生产了:1
    消费了 bread: 1
    生产了:2
    生产了:3
    生产了:4
    生产了:5
    生产了:6
    生产了:7
    篮子已经装满了,请生产者暂停生产
    消费了 bread: 7
    消费了 bread: 6
    消费了 bread: 5
    消费了 bread: 4
    生产了:8
    消费了 bread: 3
    生产了:9
    生产了:10
    生产了:11
    生产了:12
    篮子已经装满了,请生产者暂停生产
    消费了 bread: 12
    消费了 bread: 11
    消费了 bread: 10
    消费了 bread: 9
    消费了 bread: 8
    生产了:13
    生产了:14
    生产了:15
    生产了:16
    生产了:17
    篮子已经装满了,请生产者暂停生产
    消费了 bread: 17
    消费了 bread: 16
    消费了 bread: 15
    消费了 bread: 14
    消费了 bread: 13
    生产了:18
    生产了:19
    消费了 bread: 19
    消费了 bread: 18
    消费了 bread: 2
    篮子为空,请消费者暂停消费 
  • wait与sleep之间的区别
    • wait时,别的线程可以访问锁定对象
      • 调用wait方法时,必须锁定该对象
      • wait执行后,放开锁
    • sleep时,别的线程不可以访问锁定对象
      • sleep的时候,一直拥有锁

结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值