多线程的使用

概述

定义

  1. 进程与线程

    • 进程:是程序执行的一条路径。指⼀个内存中运⾏的应⽤程序,⼀个应⽤程序可以同时运⾏多个进程;进程也是程序的⼀次执⾏过程,是系统运⾏程序的基本单位;系统运⾏⼀个程序即是⼀个进程从创建、运⾏到消亡的过程。
    • 线程:⼀个进程中可以有多个线程多个线程并发执⾏可以提⾼程序的效率。线程是进程中的⼀个执⾏单元,负责当前进程中程序的执⾏,⼀个进程中⾄少有⼀个线程。⼀个进程中是可以有多个线程的,这个应⽤程序也可以称之为多线程程序
  2. 并发与并行

    • 并发:两个或多个事件在同⼀个时间段内发⽣
    • 并行:两个或多个事件在同⼀时刻发⽣(同时发⽣)
  3. 线程调度

    • 分时调度:所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间
    • 抢占式调度:优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性),Java使⽤的为抢占式调度
  4. 线程的组成

    • CPU时间片:OS为每一个线程分配执行时间

    • 运行数据:

      ​ 堆空间:存储线程需要使用的对象,多个线程可以共享堆中的对象

      ​ 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈

    • 逻辑代码

  5. java虚拟机运行流程

    • java命令会启动java虚拟机,启动JVM,等于启动了⼀个应⽤程序,也就是启动了⼀个进程。该进程会⾃动启动⼀个 “主线程” ,然后主线程去调⽤某个类的 main ⽅法
    • JVM启动后⾄少会创建垃圾回收线程和主线程

线程的创建

Thread

步骤

  • 继承Thread方法
  • 定义类继承Thread
  • 重写run()
  • 实现run()的业务
  • 创建线程对象
  • 调用start()方法开启新线程,内部自动执行run()

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread方式创建的线程");
    }
}
 public static void main(String[] args) {
     MyThread myThread = new MyThread();
     myThread.start();
 }

原理解析

  • 创建多线程肯定要跟系统平台的底层打交道, 我们程序猿根本就不知道如何去做, 所有,我们仅
    仅是提供运⾏代码,⾄于如何创建多线程全靠java来实现
  • 继承Thread的形式,每个Thread的⼦类对象只能创建⼀个线程

Runnable

步骤

  • 定义类实现Runnable接⼝

  • 实现run⽅法

  • 把新线程要做的事写在run⽅法中

  • 创建⾃定义的Runnable的⼦类对象

  • 创建Thread对象, 传⼊Runnable

  • 调⽤start()开启新线程, 内部会⾃动调⽤Runnable的run()⽅法

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iGfvlmmh-1645536458297)(F:\笔记文件\JavaSE\14、多线程\多线程.assets\image-20210927104114892.png)]

    public class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Runnable创建的线程");
        }
    }
    public static void main(String[] args) {
            MyThread myThread = new MyThread();
            myThread.start();
    
            Runnable runnable = new MyRunnable();
            Thread thread = new Thread(runnable);
            thread.start();
    }
    

    实现原理

    • Thread类中定义了⼀个Runnable类型的成员变量target⽤来接收Runnable的⼦类对象
    • 当调⽤Thread对象的start()⽅法的时候, ⽅法内⾸先会判断target是否为null. 如果不为null就调⽤Runnable⼦类对象的run⽅法
    • 多个Thread对象可以共享⼀个Runnable⼦类对象

匿名Thread与Runnable

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                System.out.println("匿名Thread");
            }
        };
        
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名Runnable");
            }
        });
        thread1.start();
        thread2.start();
    }
}

Thread与Runnable区别

  1. 执行原理
    • 继承Thread类,是直接调用子类的run()
    • 实现Runnable接口,实际上是通过接口多态的方式调用实现类的run(),调用的是Runnable接口的run(),真实执行的是实现类的run
  2. 复用性
    • 继承Thread类:单个线程只能使用一个,互相之间独立
    • 实现Runnable接口:是一个参数传入Thread类中,所以可以多个对象共同调用一个Runnnable对象
  3. 创建方式
    • 继承Thread类:直接创建线程对象
    • 实现Runnable接口:实现Runnable接口,必须通过thread才能创建线程对象,自己不能创建
  4. 适用场景
    • 继承Thread类:对于子类来说,只能继承Thread,如果一个类已经有父类了,就没有办法通过这个方式创建线程
    • 实现Runnable接口:实现Runnable接口几乎没有限制,接口是可以多实现的
  5. 适用的方法
    • 继承Thread类:直接使用Thread类中定义的方法
    • 实现Runnable接口:没有办法直接使用,只能通过Thread提供的静态方法间接使用Thread的方法

线程状态

基本

等待

阻塞

常用API

getName()

  • 通过getName()⽅法获取线程对象的名字
  • 此⽅法只适⽤于Thread的形式
  • Runnable的形式必须通过获取线程对象来获取名字:Thread.currentThread().getName()
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                System.out.println(this.getName());
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
     }

setName()

通过构造⽅法传⼊String类型的名字

 new Thread("线程1") {
     @Override
     public void run() {
        System.out.println(this.getName());
        }
  }.start();

通过setName⽅法可以设置线程对象的名字

Thread thread1 = new Thread("线程⼀"){
    public void run(){
        System.out.println(this.getName());
    }
};
thread1.setName("线程⼀");
thread1.start();

通过获取对象的形式在Runnable运⾏代码中查看当前线程的名称

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
        	//获取线程的名称
        	System.out.println(Thread.currentThread().getName());
        }
    }).start();
}

通过获取线程对象的形式设置线程的名称

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            Thread.currentThread().setName("线程⼆");//设置线程的名称
            System.out.println(Thread.currentThread().getName());
        }
    }).start();
}

通过Thread的构造⽅法设置线程的名称

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
        	System.out.println(Thread.currentThread().getName());
        }
    //通过构造⽅法设置线程的名称
    },"线程⼆").start();
}

sleep()

将线程休眠若⼲毫秒

public static void main(String[] args) {
    new Thread(new Runnable() {
        @Override
        public void run() {
            long time1 = System.currentTimeMillis();
            System.out.println(time1);
            try {
            	Thread.sleep(10000);//设置线程等待10000毫秒
            } catch (InterruptedException e) {
            	e.printStackTrace();
            }
            long time2 = System.currentTimeMillis();
            System.out.println(time2);
    	}
    //通过构造⽅法设置线程的名称
    }).start();
}

setDaemon()

  • 围绕着其他⾮守护线程运⾏, 该线程不会单独运⾏,当其他⾮守护线程都执⾏结束后,⾃动退出
  • 调⽤Thread的setDaemon()⽅法设置⼀个线程为守护线程
public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
            System.out.println("线程⼀运⾏中");
            }
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("守护线程运⾏..........");
            }
        }
    });
    t2.setDaemon(true);
    t1.start();
    t2.start();
}

join()

当前线程暂停, 等待指定的线程执⾏结束后, 当前线程再继续

只有调⽤其他线程加⼊⽅法的线程才会停⽌运⾏,其他线程不受影响

  • join() 优先执⾏指定线程
  • join(毫秒) 优先执⾏指定线程若⼲毫秒
public static void main(String[] args) {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
            System.out.println("线程⼀运⾏中");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
            System.out.println("线程⼆运⾏中..........");
                try {
                    t1.join(); //让线程⼀先执⾏
                    t1.join(100);//让线程⼀先执⾏100毫秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    });
    //只有调⽤了加⼊线程的线程才会停⽌运⾏,其他线程不受影响
    Thread t3 = new Thread(new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 50; i++) {
                System.out.println("线程三运⾏中..........");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             }
        }
    });
    t1.start();
    t2.start();
    t3.start();
} 

yield()

让出当前线程的执⾏权

Thread t2 = new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
        	System.out.println("线程⼆运⾏中..........");
        	//让出当前线程的执⾏权
        	Thread.yield();
        }
    }
});

isAlive()

判断当前线程是否处于活动状态:已经启动且尚未停止

System.out.println("线程的活动状态是:"+Thread.currentThread().isAlive());

getId()

获取线程的唯一标示

System.out.println(myTreadTest1.getId());

setPriority()

  • 每个线程都有优先级 默认是5 , 范围是1-10 ,1表示优先级最低
  • 优先级⾼的线程在争夺cpu的执⾏权上有⼀定的优势,但不是绝对的
myTreadTest1.setPriority(10);

interrupt()

线程的停止

interript 也不是想象中的那样,只要调用了这个方法,线程就会停止,其实,调用了interrupt 只相当于给当前线程上了一个停止的标记,而此时,线程其实并没有真正的停止,而这其中很明显,缺少一些步骤。

public class A {
    public static void main(String[] args) {
        B b = new B();
        b.start();
        b.interrupt();
    }
}

class B extends Thread {
    @Override
    public void run() {
        System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

需要加上一个判断

class B extends Thread {
    @Override
    public void run() {
        System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
        for (int i = 0; i < 10; i++) {
           if (this.isInterrupted()){
               System.out.println("线程停止");
               System.out.println(this.isInterrupted());
               break;
           }
        }
    }
}

this.isInterrupted()代表着获取线程的中断标志,如果在此之前你调用了interrupt 的话它就返回true,否则就是 false,所以就可以通过这种方式来达到停止线程的目的

如果你让线程进行休眠,就会抛出一个中断异常,因为你之前打上了中断标志,所以调用sleep 就会抛出一个中断异常,而且还会将中断标志设置成false

class B extends Thread {
    @Override
    public void run() {
        System.out.println("正在执行线程B。。。" + Thread.currentThread().getName());
        try {
            for (int i = 0; i < 10; i++) {
                if (this.isInterrupted()) {
                    System.out.println("线程停止");
                    System.out.println(this.isInterrupted());
                    throw new InterruptedException();
                }
                System.out.println(i);
            }
            System.out.println("线程继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

isInterrupted()

isInterrupted()返回线程是否中断

 System.out.println(this.isInterrupted());

安全问题

  • 多个线程操作同⼀个数据时, 因为线程执⾏的随机性, 就有可能出现线程安全问题
  • 使⽤同步可以解决这个问题, 把操作数据的代码进⾏同步,同⼀时间只能有⼀个线程只操作数据
    就可以保证数据的安全性

当我们使⽤多个线程访问同⼀资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题,要解决多线程并发访问⼀个资源的安全性问题,Java中提供了同步机制 (synchronized)来解决

同步代码块

synchronized 关键字可以⽤于⽅法中的某个区块中,表示只对这个区块的资源实⾏互斥访问。

格式

synchronized (同步锁){}

同步锁:对象同步锁只是一个概念,可以想象为在对象上标记一个锁

  • 锁对象 可以任意类型
  • 多个线程对象,要使用同一把锁

案例:售票

使用Thread类创建线程,使用synchronized 锁住票数,使用多个线程调用同一个线程

public class Trcket extends Thread {
    static int count = 1;
    final static Object object = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (object) {
                if (count < 101) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + "卖出了第" + count + "张票");
                    count++;
                } else {
                    break;
                }
            }
        }
    }

    public Trcket(String name) {
        super(name);
    }
}

public class TrcketDemo {
    public static void main(String[] args) {
        Trcket trcket1 = new Trcket("第一窗口");
        Trcket trcket2 = new Trcket("第二窗口");
        Trcket trcket3 = new Trcket("第三窗口");
        Trcket trcket4 = new Trcket("第四窗口");
        trcket1.start();
        trcket2.start();
        trcket3.start();
        trcket4.start();
    }
}

使用Runnable接口实现run(),创建线程,使用synchronized 锁住票数,使用多个线程调用同一个线程

public class Station implements Runnable {
    static int num = 1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (num < 101) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t" + num);
                    num++;
                } else {
                    break;
                }
            }
        }
    }
}
public class TrcketDemo {
    public static void main(String[] args) {
        Runnable runnable = new Station();
        Thread thread1 = new Thread(runnable,"第一窗口");
        Thread thread2 = new Thread(runnable,"第二窗口");
        Thread thread3 = new Thread(runnable,"第三窗口");
        Thread thread4 = new Thread(runnable,"第四窗口");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

同步方法

使⽤synchronized修饰的⽅法,就叫做同步⽅法,保证A线程执⾏该⽅法的时候,其他线程只能在⽅法外等着

格式

public synchronized void method(){ 可能会产⽣线程安全问题的代码}

同步锁是谁? 对于⾮static⽅法,同步锁就是this。 对于static⽅法,我们使⽤当前⽅法所在
类的字节码对象(类名.class)。

Thread使用同步方法实现

public class Ticket extends Thread {
    static int count = 1;
    private static boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            salaTicket();
        }
    }

    public static synchronized void salaTicket() {
        if (count < 101) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出了第" + count + "张票");
            count++;
        } else {
            flag = false;
        }
    }

    public Ticket(String name) {
        super(name);
    }
}
        Ticket trcket1 = new Ticket("第一窗口");
        Ticket trcket2 = new Ticket("第二窗口");
        Ticket trcket3 = new Ticket("第三窗口");
        Ticket trcket4 = new Ticket("第四窗口");
        trcket1.start();
        trcket2.start();
        trcket3.start();
        trcket4.start();

Runnable使用同步方法实现

public class Station implements Runnable {
    static int num = 1;
    private static boolean flag = true;

    @Override
    public void run() {
        while (flag) {
            salaTicket();
        }
    }

    public static synchronized void salaTicket() {
        if (num < 101) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            num++;
        } else {
            flag = false;
        }
    }
}

Runnable runnable = new Station();
Thread thread1 = new Thread(runnable,"第一窗口");
Thread thread2 = new Thread(runnable,"第二窗口");
Thread thread3 = new Thread(runnable,"第三窗口");
Thread thread4 = new Thread(runnable,"第四窗口");
thread1.start();
thread2.start();
thread4.start();

注意事项

使用同一把锁的代码才能实现同步

  • 没有获取到锁的线程即使得到了cpu的执⾏权,也不能运⾏
  • 尽量减少锁的范围,避免效率低下
  • 锁可以加在任意类的代码中或⽅法上

死锁

定义

  • 使⽤同步的多个线程同时持有对⽅运⾏时所需要的资源
  • 多线程同步时, 多个同步代码块嵌套,很容易就会出现死锁
  • 锁的嵌套越多,越容易造成死锁的情况

案例

线程⼀需要先获取左边的筷⼦然后获取右边的筷⼦才能吃饭,线程⼆需要先获取右边的筷⼦然后获取左边的筷⼦才能吃饭

当线程⼀持有左边的筷⼦,线程⼆持有右边的筷⼦时,相互等待对⽅释放锁,但⼜同时持有对⽅释放锁的条件,互不相让,就会造成⽆限等待

 private static String s1 = "筷⼦左";
    private static String s2 = "筷⼦右";

    public static void main(String[] args) {
        new Thread() {
            public void run() {
                while (true) {
                    synchronized (s1) {
                        System.out.println(getName() + "...拿到" + s1 + "等待" + s2);
                        synchronized (s2) {
                            System.out.println(getName() + "...拿到" + s2 + "开吃");
                        }
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                while (true) {
                    synchronized (s2) {
                        System.out.println(getName() + "...拿到" + s2 + "等待" + s1);
                        synchronized (s1) {
                            System.out.println(getName() + "...拿到" + s1 + "开吃");
                        }
                    }
                }
            }
        }.start();
    }

通信、唤醒机制

wait

  • 让当前线程处于等待状态,并释放锁。
  • 线程不再活动,不再参与调度,进⼊ wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执⾏⼀个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait set 中释放出来,重新进⼊到调度队列ready queue)中

notify

  • 唤醒某个等待中的线程
  • 选取所通知对象的 wait set 中的⼀个线程释放;

notifyAll

  • 唤醒所有等待中的线程
  • 释放所通知对象的 wait set 上的全部线程

注意

  • 哪怕只通知了⼀个等待的线程,被通知线程也不能⽴即恢复执⾏,因为它当初中断的地⽅是在同步块内,⽽此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能⾯临其它线程的竞争),成功后才能在当初调⽤ wait ⽅法之后的地⽅恢复执⾏。

总结如下

  • 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
  • 否则,从 wait set 出来,⼜进⼊ entry set,线程就从 WAITING 状态⼜变成 BLOCKED状态
public class WaitDemo {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (this) {
                        //唤醒
                        this.notify();
                        System.out.println(Thread.currentThread().getName() + "发生信号结束");
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        Thread thread = new Thread(runnable,"1");
        Thread thread1 = new Thread(runnable,"2");
        thread.start();
        thread1.start();
    }
}

执行流程:如果thread先拿到锁,执行唤醒,但是这个没有线程被唤醒,继续执行,发出信号,thread沉睡,交出锁。thread1拿到锁,执行唤醒thread,但是thread没有锁,只能等,thread1继续执行,发出信号,沉睡,交出锁。

wait、notify细节

  1. wait⽅法与notify⽅法必须要由同⼀个锁对象调⽤。因为:对应的锁对象可以通过notify唤醒
    使⽤同⼀个锁对象调⽤的wait⽅法后的线程。
  2. wait⽅法与notify⽅法是属于Object类的⽅法的。因为:锁对象可以是任意对象,⽽任意对象
    的所属类都是继承了Object类的。
  3. wait⽅法与notify⽅法必须要在同步代码块或者是同步函数中使⽤。因为:必须要通过锁对象
    调⽤这2个⽅法。

两个线程之间的通信

    public static void main(String[] args) {
        new Thread() {
            private String str;
            public void run() {
                synchronized (Class.class) {
                    while (true) {
                        Class.class.notify();//唤醒等待中的线程 如果有的话
                        System.out.println("线程⼀........");
                        try {
                            Class.class.wait();//当前线程陷⼊⽆限期的等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                synchronized (Class.class) {
                    while (true) {
                        Class.class.notify(); //唤醒等待中的线程
                        System.out.println("线程⼆.....................");
                        try {
                            Class.class.wait(); // 让当前线程陷⼊等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
    }

多个线程间的通信

    public static void main(String[] args) {
        new Thread() {
            public void run() {
                synchronized (Class.class) {
                    while (true) {
                        Class.class.notifyAll();//唤醒等待中的线程 如果有的话
                        System.out.println("线程⼀........");
                        try {
                            Class.class.wait();//当前线程陷⼊⽆限期的等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                synchronized (Class.class) {
                    while (true) {
                        Class.class.notifyAll(); //唤醒所有等待中的线程
                        System.out.println("线程⼆.....................");
                        try {
                            Class.class.wait(); // 让当前线程陷⼊等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                synchronized (Class.class) {
                    while (true) {
                        Class.class.notifyAll(); //唤醒所有等待中的线程
                        System.out.println("线程333333333...........");
                        try {
                            Class.class.wait(); // 让当前线程陷⼊等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
    }

注意事项

  • 线程间的所有通信⾏为都必须在同步代码块中执⾏,这些⾏为都是锁调⽤的。
  • 当⼀个线程陷⼊等待, 线程会释放掉锁, 并且⽆法动弹, 即使被唤醒了, 也仅仅表示有了获取锁的机会, 只有当真正获取到锁的时候才能继续运⾏
  • wait⽅法还有重载的⽅法,可以传⼊毫秒值,表示多少毫秒之后当前线程⾃动唤醒
  • ⼀个锁只能唤醒被⾃⼰锁定的线程,⽆法在当前同步代码块内操作别的锁

案例

需求: ⼩明同学上了⼤学, ⽗亲每次给1000块, ⼩明每次花100元, 当钱花完了, 就打电话给⽗亲,通知他去银⾏存钱, 编程模拟

public class WaitDemo {
    private static int money = 1000;

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    //同步锁,锁住money
                    synchronized (WaitDemo.class) {
                        //当money大于0,执行消费
                        if (money >= 0) {
                            System.out.println("消费" + money);
                            money = money - 100;
                            //当money=0,唤醒充值线程
                        } else {
                            //唤醒充值线程
                            WaitDemo.class.notify();
                            try {
                                //消费线程等待,释放锁
                                WaitDemo.class.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (WaitDemo.class) {
                        //当money=0,开始充值
                        if (money <= 0) {
                            money = 1000;
                            System.out.println("充值" + money);
                            //充值完成,唤醒消费线程
                            WaitDemo.class.notify();
                            try {
                                //充值线程等待,释放锁
                                WaitDemo.class.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }).start();

    }
}

案例:写两个线程,其中一个线程打印1-52,另一个线程打印A-Z,打印顺序应该是12A34B56C…5152Z

    private static int i = 1;//切换线程,需要锁住
    static int num = 1;//数字
    static int n = 65;//字母

    private static void three() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (Task927.class) {
                        //当数字大于52,退出系统
                        if (num > 52) {
                            System.exit(0);
                            //当i取余不等于0,进入数字线程
                        } else if (i % 2 != 0) {
                            //输出两个数字
                            System.out.print(num + "\t");
                            num++;
                            System.out.print(num + "\t");
                            num++;
                            i++;
                            //唤醒字母线程
                            Task927.class.notify();
                            try {
                                //数字线程进入等待,释放锁
                                Task927.class.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else if (n > 52) {
                            return;
                        }
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (Task927.class) {
                        //当i取余等于0,进入数字线程
                        if (i % 2 == 0) {
                            System.out.print((char) n + "\t");
                            n++;
                            i++;
                            //唤醒数字线程,字母线程进入等待,释放锁
                            Task927.class.notify();
                            Task927.class.wait();
                        }
                    }
                }
            }
        }).start();
    }

生产者和消费者问题

概述

包⼦铺线程⽣产包⼦,吃货线程消费包⼦。当包⼦没有时(包⼦状态为false),吃货线程等待,包⼦铺线程⽣产包⼦(即包⼦状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包⼦了,那么包⼦铺线程进⼊等待状态。接下来,吃货线程能否进⼀步执⾏则取决于锁的获取情况。如果吃货获取到锁,那么就执⾏吃包⼦动作,包⼦吃完(包⼦状态为false),并通知包⼦铺线程(解除包⼦铺的等待状态),吃货线程进⼊等待。包⼦铺线程能否进⼀步执⾏则取决于锁的获取情况。

处理

public class A {
    public static void main(String[] args) {
        //等待唤醒案例
        BaoZi bz = new BaoZi();
        ChiHuo ch = new ChiHuo("吃货", bz);
        BaoZiPu bzp = new BaoZiPu("包⼦铺", bz);
        ch.start();
        bzp.start();
    }
}

class BaoZi {
    String pier;
    String xianer;
    boolean flag = false;//包⼦资源 是否存在 包⼦资源状态
}

class ChiHuo extends Thread {
    private BaoZi bz;

    public ChiHuo(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (bz) {
                if (bz.flag == false) {//没包⼦
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货正在吃" + bz.pier + bz.xianer + "包⼦");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

class BaoZiPu extends Thread {
    private BaoZi bz;

    public BaoZiPu(String name, BaoZi bz) {
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包⼦
        while (true) {
            //同步
            synchronized (bz) {
                if (bz.flag == true) {//包⼦资源 存在
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 没有包⼦ 造包⼦
                System.out.println("包⼦铺开始做包⼦");
                if (count % 2 == 0) {
                    // 冰⽪ 五仁
                    bz.pier = "冰⽪";
                    bz.xianer = "五仁";
                } else {
                    // 薄⽪ ⽜⾁⼤葱
                    bz.pier = "薄⽪";
                    bz.xianer = "⽜⾁⼤葱";
                }
                count++;
                bz.flag = true;
                System.out.println("包⼦造好了:" + bz.pier + bz.xianer);
                System.out.println("吃货来吃吧");
                //唤醒等待线程 (吃货)
                bz.notify();
            }
        }
    }
}

执行原理:创建包子类,创建吃货类和包子铺类,将包子类作为对象,锁住包子类的属性。根据flag不断切换线程

案例

需求:李四、王五向张三转账,每次转500,转五次(不考虑余额不足问题)

import lombok.Data;

public class Money {
    public static void main(String[] args) {
        ZhangSan z = new ZhangSan(0);
        LiShi liShi = new LiShi(8000, z);

        WangWu wangWu = new WangWu(6000, z);
        liShi.start();
        wangWu.start();
    }
}

@Data
class ZhangSan {
    private int money;
    boolean aBoolean = false;

    public ZhangSan(int money) {
        this.money = money;
    }
}

@Data
class LiShi extends Thread {
    int num = 0;
    private int money;
    private ZhangSan zhangSan;


    public LiShi(int money, ZhangSan zhangSan) {
        this.money = money;
        this.zhangSan = zhangSan;
    }

    @Override
    public void run() {
        while (num < 5) {
            synchronized (zhangSan) {
                if (zhangSan.aBoolean == false) {
                    zhangSan.setMoney(zhangSan.getMoney() + 500);
                    money = money - 500;
                    System.out.println("李四线程:" + Thread.currentThread().getName() + "\t张三金额:"
                            + zhangSan.getMoney() + "\t李四剩余金额" + money);
                    num++;
                    zhangSan.aBoolean = true;
                }
            }
        }
    }
}

class WangWu extends Thread {
    private int money;
    private ZhangSan zhangSan;
    int num = 0;

    public WangWu(int money, ZhangSan zhangSan) {
        this.money = money;
        this.zhangSan = zhangSan;
    }

    @Override
    public void run() {
        while (num < 6) {
            synchronized (zhangSan) {
                if (num == 5) {
                    System.out.println("张三最后金额:" + zhangSan.getMoney());
                    System.exit(0);
                } else if (zhangSan.aBoolean == true) {
                    zhangSan.setMoney(zhangSan.getMoney() + 500);
                    money = money - 500;
                    System.out.println("王五线程:" + Thread.currentThread().getName() + "\t张三金额:"
                            + zhangSan.getMoney() + "\t王五剩余金额" + money);
                    num++;
                    zhangSan.aBoolean = false;
                }
            }
        }
    }
}

ThreadLocal

定义

  • 保存线程的独立变量,是存储线程变量的容器,里面的数据可以在线程中任意位置取出
  • ThreadLocal的变量是线程分离的,别的线程无法使用,保证变量的安全性
  • 每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value

常用API

  • get():返回当前线程的此线程局部变量的副本中的值
  • remove():删除此线程局部变量的值
  • set(Value):将当前线程的局部变量的副本设置为指定的值
  • withInitial( supplier <? extends S> supplier):创建线程的局部变量
   static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            local.set(1);
            local.get();
            local.remove();
            return 2;
        }
    };

案例

public class ThreadLocalDemo {
    public static void main(String[] args) {

        Lock1 lock1 = new Lock1(6);
        Lock1 lock2 = new Lock1(6);
        Lock1 lock3 = new Lock1(6);
        Thread thread1 = new Thread(lock1, " 线程一:");
        Thread thread2 = new Thread(lock2, " 线程二:");
        Thread thread3 = new Thread(lock3, " 线程三:");

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

class Lock1 extends Thread {
    private int num;

    public Lock1(int num) {
        this.num = num;
    }

    static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            local.set(1);
            local.get();
            local.remove();
            return 0;
        }
    };

    private Integer getNextValue() {
        local.set(local.get() + 1);
        return local.get();
    }

    @Override
    public void run() {
        for (int i = 0; i < num; i++) {
            System.out.println(Thread.currentThread().getName() + "\t" + getNextValue());
        }
    }
}

方案

public class Example {
    static ThreadLocal<Integer> local = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                local.set(1);
                System.out.println(local.get());
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                local.set(2);
                System.out.println(local.get());
            }
        }).start();
    }

    public static int method() {
        return local.get();
    }
}

Lock接口

定义

和Synchronized相比,结构上更加灵活,提供了更多实⽤性的⽅法,功能更加强⼤, 性能更加优越

常用方法

  • void lock() //获取锁,如果锁被占⽤,则等待
  • boolean tryLock(); // 尝试获取锁 ,成功则返回true,失败则返回false,不阻塞
  • void unlock()// 释放锁
public class TicketDemo {
    public static void main(String[] args) {
        Runnable runnable = new TicketDemoService();
        new Thread(runnable).start();
        new Thread(runnable).start();
    }
}

class TicketDemoService implements Runnable {
    private int ticket = 100;
    Lock lock = new ReentrantLock();//创建锁

    @Override
    public void run() {
        while (ticket > 0) {
            lock.lock();//加锁
            System.out.println(Thread.currentThread().getName() + "\t" + ticket--);
            lock.unlock();//解锁
        }
    }
}

互斥锁

定义
  • 使⽤ReentrantLock类代替synchronized关键字, 提供了锁定和解锁的⽅法,更多的操作所得⽅法
常用方法
  • lock() :锁定当前线程
  • unlock(): 解锁
  • newCondition() :获取可以操作线程等待和唤醒的Condition对象
  • await() :让当前线程陷⼊等待
  • signal() :唤醒某个被锁定的线程
public class ReetrantLockDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();//创建锁
        Condition condition = lock.newCondition();//创建可以操作线程等待唤醒的对象

        new Thread() {
            @Override
            public void run() {
                lock.lock();//加锁
                for (int i = 0; i < 10; i++) {

                    condition.signal();//唤醒其他线程
                    System.out.println("线程1");
                    try {
                        condition.await();//本线程沉睡
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.unlock();//解锁
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                lock.lock();
                for (int i = 0; i < 10; i++) {
                    condition.signal();
                    System.out.println("线程2");
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.unlock();
            }
        }.start();
    }
}

注意事项

互斥锁提供了更多操作线程的方法,但是必须使用同一把锁

读写锁

定义
  • ⼀种⽀持⼀写多读的同步锁,读写分离,课分别分配读锁、写锁
  • ⽀持多次分配读写锁,使多个读写操作可以并发执⾏
互斥规则
  • 写写、读写:互斥、阻塞
  • 读读:不互斥
  • 在读的操作远⾼于写的操作的环境中,可以在保障线程安全的情况下,提⾼运⾏的效率
import lombok.Data;
import lombok.Value;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;

/**
 * @Author:night_du
 * @Date:2021/9/29 8:11
 */
public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyClass myClass = new MyClass();
        //设置value的线程
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myClass.setValue(10);
            }
        };
        //获取value的线程
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                myClass.getValue();
            }
        };
        //线程池:创建可以提供管理终端和方法的对象
        ExecutorService service = Executors.newFixedThreadPool(20);
        long millis = System.currentTimeMillis();   //获取时间戳
        for (int i = 0; i < 2; i++) {   //运行两次设置value的线程
            service.submit(runnable);
        }
        for (int i = 0; i < 18; i++) {
            service.submit(runnable1);  //运行18次获取value的线程
        }
        service.shutdown(); //关闭线程
        while (!service.isTerminated()) {   //如果线程没有关闭,空转等待
            System.out.println(System.currentTimeMillis() - millis+"\t"+ myClass.getValue());    //获取时间戳的差额
        }
    }
}

@Data
class MyClass {
    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();//创建读写锁
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();//获取读的锁
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();//获取写的锁
    private int value;//读取或写入的值

    /**
     * @return 获取value
     */
    public int getValue() {
        readLock.lock();//加读的锁
        try {
            return value;
        } finally {
            readLock.unlock();//解读的锁
        }
    }

    /**
     * @param value  设置value值
     */
    public void setValue(int value) {
        writeLock.lock();//加写的锁
        try {
            this.value = value;//设置值
        } finally {
            writeLock.unlock();//解写的锁
        }
    }
}

线程组

定义

  • ThreadGroup来表示线程组,它可以对⼀批线程进⾏分类管理,Java允许程序直接对线程组进⾏控制
  • 提供了⼀些操作整体的⽅法, ⽐如设置组中线程的权限,销毁所有所有线程等等

案例

public class ThreadGroupDemo {
    public static void main(String[] args) {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(i);
                    if (i >= 6) {
                        System.out.println("------------");
                        Thread.currentThread().getThreadGroup().interrupt();//添加中断标记
                        break;
                    }
                }
            }
        };
        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("***");
                    //检测到当前线程存在中断标记,终止代码
                    if (Thread.interrupted()) {
                        break;
                    }
                }
            }
        };

        //创建新的线程组
        ThreadGroup threadGroup = new ThreadGroup("new ThreadGroup");
        //将线程添加到线程组:线程组、线程、线程名称
        Thread thread3 = new Thread(threadGroup, runnable1, "3333");
        Thread thread4 = new Thread(threadGroup, runnable2, "4444");

        System.out.println(thread3.getName() + "\t" + thread3.getThreadGroup().getName()
                + "\t" + thread3.getThreadGroup().getMaxPriority());
        System.out.println(thread4.getName() + "\t" + thread4.getThreadGroup().getName()
                + "\t" + thread4.getThreadGroup().getMaxPriority());

        thread3.start();
        thread4.start();
    }
}

线程池

定义

⼀个容纳多个线程的容器,其中的线程可以反复使⽤,省去了频繁创建线程对象的操作,⽆需反复创建线程⽽消耗过多资源

好处

  1. 降低资源消耗。减少了创建和销毁线程的次数,可以反复利用,执行多个任务
  2. 提高响应速度
  3. 方便管理。根据系统性能调配线程数量,防止服务器崩溃

创建

线程池的顶级接口是 java.util.concurrent.Executor,但是它不是线程池,是执行线程的工具,真正的接口是 java.util.concurrent.ExecutorService

Executors
  • public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最⼤数量)

  • public Future<?> submit(Runnable task) :获取线程池中的某⼀个线程对象,并执⾏

    Future接⼝:⽤来记录线程任务执⾏完毕后产⽣的结果。线程池创建与使⽤

Executors创建的四种线程池

  1. newCachedThreadPool(); //可缓冲的线程池 – 更加灵活的回收空置线程,没有可以回收的创建新的线程
  2. newFixedThreadPool(count); // 定长线程池,可以控制线程最大数量的,超出则等待
  3. newScheduledThreadPool(count); //定长的线程池,支持定时任务或者周期任务
  4. newSingleThreadExecutor(); //单线程的线程池,只有一个线程
newFixedThreadPool

定长线程池,可以控制线程最大数量的,超出则等待

步骤

  1. 创建线程池对象: ExecutorService service = Executors.newFixedThreadPool(10);
  2. 创建线程Runnable: Runnable runnable = new Runnable() {}
  3. 提交线程: Future<?> submit = service.submit(runnable);
  4. 关闭线程: service.shutdown();
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //创建有界线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //创建线程
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池任务");
            }
        };
        //提交线程到线程池
        Future<?> submit = service.submit(runnable);
        System.out.println(submit);
        //关闭线程池
        service.shutdown();
    }
}
ScheduledExecutorService

带有定时任务的线程池

步骤

  1. 创建线程池对象:ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
  2. 创建线程:Runnable runnable1 = new Runnable() {}
  3. 提交线程:executorService.schedule(runnable1, 3, TimeUnit.SECONDS);
  4. 关闭线程:executorService.shutdown();
import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //带有定时任务的线程池	5=核心线程数
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
        //创建runnable对象
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟3秒运行");
            }
        };
        //重复提交任务
        for (int i = 0; i < 10; i++) {
            executorService.schedule(runnable1, 3, TimeUnit.SECONDS);
        }
        //关闭
        executorService.shutdown();
    }
}
Callable接口

概念

Callable和Runnable接口类似,但是具有泛型返回值,可以声明异常

特点

  • 类似与Runnable接口
  • 有返回值
  • 可以抛出异常
  • 调用Call()计算数据
  • 支持泛型

实现原理

import java.util.concurrent.*;

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        //创建Callable对象,可以理解成带有计算的Runnable线程,因为带返回值
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName() + "-------");
                return 0;
            }
        };
        //创建异步计算对象,也充当Callable和Thread之间的中间件
        FutureTask<Integer> task = new FutureTask<>(callable);
        //创建Thread线程
        Thread thread = new Thread(task);

        thread.start();
        System.out.println(task.get());
        //创建管理线程池的工具,核心线程数=5
        ExecutorService service = Executors.newFixedThreadPool(5);
        //提交线程
        Future<Integer> submit = service.submit(callable);
        System.out.println(submit.get());
        //延迟20毫秒提交
        System.out.println(submit.get(20, TimeUnit.SECONDS));
    }
}

注意事项

  • FutureTask.get()方法可能会阻塞,因为数据在call后会return,在此之前,get阻塞
  • 一般get()放在最后,并通过异步操作执行调用

FutureTask是什么?

​ 就是实现异步调⽤,传⼊callable任务,他会单开⼀个线程去执⾏callable⾥⾯的call⽅法; 在主线程中需要执⾏⽐较耗时的操作时,但⼜不想阻塞主线程时,可以把这些作业交给Future对象在后台完成, 当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执⾏状态。 ⼀般FutureTask多⽤于耗时的计算,主线程可以在完成⾃⼰的任务后,再去获取结果。 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get ⽅法。⼀旦计算完成, 就不能再重新开始或取消计算。get⽅法⽽获取结果只有在计算完成时获取,否则会⼀直阻塞直到任务转⼊完成状态, 然后会返回结果或者抛出异常。

package com.qf.javase.day.day23;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @Author:night_du
 * @Date:2021/9/29 19:02
 */
public class FutureTest {
    public static void main(String[] args) throws
            ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread); // 适配器类
        new Thread(futureTask, "A").start();
        new Thread(futureTask, "B").start(); //结果会被缓存
        Object o = futureTask.get(); // 会阻塞,⼀般通过异步操作
        System.out.println(o);
    }
}

class MyThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("call()");
        return "123";
    }
}

当使⽤两个线程调⽤的时候,发现只输出⼀次,因为结果被缓存了,提⾼了效率

​ Future Task可用于异步获取执行结果或取消执行任务的场景。通过传入Runnable或者Callable的任务给FutureTask,直接调用其run方法或者放入线程池执行,之后可以在外部通过Future Task的get方法异步获取执行结果,因此,FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。另外,Future Task还可以确保即使调用了多次run方法,它都只会执行一次Runnable或者Callable任务,或者通过cancel取消Future Task的执行等。

Future接口

​ Future接⼝提供⽅法来检测任务是否被执⾏完,等待任务执⾏完获得结果,也可以设置任务执⾏的超时时间。这个设置超时的⽅法就是实现Java程序执⾏超时的关键。

常用方法

Future接⼝是⼀个泛型接⼝,严格的格式应该是Future,其中V代表了Future执⾏的任务返回值的类型。

  • boolean cancel (boolean mayInterruptIfRunning) 取消任务的执⾏。参数指定是否⽴即中断任务执⾏,或者等等任务结束
  • boolean isCancelled () 任务是否已经取消,任务正常完成前将其取消,则返回 true
  • boolean isDone () 任务是否已经完成。需要注意的是如果任务正常终⽌、异常或取消,都将返回true
  • V get () throws InterruptedException, ExecutionException 等待任务执⾏结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执⾏异常,如果任务被取消,还会抛出CancellationException
  • V get (long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,TimeoutException 同上⾯的get功能⼀样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计算超时,将抛出
    TimeoutException

使用原理

Futrue有两个子类:FatureTask和SwingWroker<T,V>。FutureTask类实现了Runnable接口,所以可以直接交给Executor执行。

Queue

import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;

public class QuereDemo {
    public static void main(String[] args) {
        //创建Queue对象
        Queue<Integer> queue = new ArrayBlockingQueue<>(3);
        //如果数据为空,不报异常,返回null
        Integer poll = queue.poll();//删除
        System.out.println(poll);
        Integer peek = queue.peek();//查询
        System.out.println(peek);
        queue.offer(145);//添加,即使超出最长容量,也不会报异常
        queue.offer(17);
        queue.offer(21);
        queue.offer(681);
        for (Integer integer : queue) {//遍历
            System.out.println(integer);
        }

        /*//如果数据为空,报异常:java.util.NoSuchElementException
        Integer remove = queue.remove();//删除
        Integer element = queue.element();//查询
        queue.add(11);
        queue.add(21);
        queue.add(35);
        java.lang.IllegalStateException: Queue full
        queue.add(4);
        for (Integer integer : queue) {
            System.out.println(integer);
        }*/
    }
}

ArrayBlockingQueue

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class ArrayBlockingQueueDemo<S> {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(30);
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        queue.put("放入一个苹果");
                        System.out.println("进货一个苹果");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread customer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        String take = queue.take();
                        System.out.println("消费了一个苹果---" + take);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        producer.start();
        customer.start();
    }
}

Runtime

定义

使用系统命令

使用

public static void main(String[] args) throws IOException {
    Runtime runtime = Runtime.getRuntime();
    runtime.exec("shutdown -s -t 300");//300秒后关机
    runtime.exec("shutdown -a"); //取消关机
}

Timer

定义

定时器

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
    private static Timer timer;


    public static void main(String[] args) {

        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println(1);
                //timer.cancel(); //取消定时任务,终结定时器对象
            }
        };
        Timer timer = new Timer();//创建定时器对象
        //timer.schedule(task, 2000);
        timer.schedule(task, 5000, 1000);//5秒后执行,每一秒后执行一次
    }
}
ckingQueue<String>(30);
        Thread producer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        queue.put("放入一个苹果");
                        System.out.println("进货一个苹果");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread customer = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                        String take = queue.take();
                        System.out.println("消费了一个苹果---" + take);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        producer.start();
        customer.start();
    }
}

Runtime

定义

使用系统命令

使用

public static void main(String[] args) throws IOException {
    Runtime runtime = Runtime.getRuntime();
    runtime.exec("shutdown -s -t 300");//300秒后关机
    runtime.exec("shutdown -a"); //取消关机
}

Timer

定义

定时器

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo {
    private static Timer timer;


    public static void main(String[] args) {

        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                System.out.println(1);
                //timer.cancel(); //取消定时任务,终结定时器对象
            }
        };
        Timer timer = new Timer();//创建定时器对象
        //timer.schedule(task, 2000);
        timer.schedule(task, 5000, 1000);//5秒后执行,每一秒后执行一次
    }
}
  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值