• 005多线程Thread


    TOC

    多线程Thread

    多进程概述

    进程

    • 1.定义:正在进行的程序(任务管理器中可以看到)(一个CPU同一时间点只运行一个进程,只运行其中的一个线程)。是系统进行资源分配和调用的独立单位。每一个进程都有他自己的内存空间和系统资源。
    • 2.多进程有什么意义呢?
      • 可以提高CPU的使用率

          单进程的计算机只能做一件事情,而我们现在的计算机都可以做多件事情。举例:一边玩游戏(游戏进程),一边听音乐(音乐进程)。
          也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务。

    • 3.问题:一边玩游戏,一边听音乐是同时进行的吗?
      • 单cpu:
        不是。因为单CPU在某一个时间点上只能做一件事情。
        而我们在玩游戏,或者听音乐的时候,是CPU在做着程序间的高效切换让我们觉得是同时进行的。
      • 多CPU:可能是。

    多线程

    线程是依赖于进程而存在。

    • 线程:在同一个进程内又可以执行多个任务,而这每一个任务我就可以看出是一个线程。
      (1)线程定义:是程序的执行单元,执行路径。是程序使用CPU的最基本单位
      (2) 单线程:如果程序只有一条执行路径。
      (3)多线程:如果程序有多条执行路径。

      (360是一个进程,同时运行360的不同功能是多线程);

      一个进程中至少有一个线程。
      (4)目的:开启多个线程是为了同时运行多部分代码。

    • 程序:每一个线程都有自己运行的内容,这个内容可以成为线程要执行的程序。
    • 多线程意义:为了提高应用程序的使用率

      程序的执行其实都是在抢CPU的资源,CPU的执行权。

      多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多(线程多),就会有更高的几率抢到CPU的执行权。

      我们是不敢保证哪一个线程能够在哪个时刻抢到,所以线程的执行有随机性。

    • 注意并行和并发:
      并行:是逻辑上同时发生,指在某一个时间段内同时运行多个程序。
      并发:是物理上同时发生,指在某一个时间点同时运行多个程序。
    • Java程序运行原理
      由java命令启动JVM,JVM启动就相当于启动了一个进程。接着由该进程创建了一个主线程去调用main方法。
    • jvm虚拟机的启动是单线程的还是多线程的?
      多线程的。
      原因是垃圾回收线程(finalize())也要先启动,否则很容易会出现内存溢出。
      垃圾回收线程+主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。
    • 多线程的好处:解决了多部分同时运行的问题

      如果多线程中的某一条线程发生错误,会显示异常,并停止这条线程,但其他线程不受影响.

    • 创建线程的目的
      是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行。
    • 多线程实现
      C/C++去调用系统功能创建进程,然后由Java去调用进程实现多线程

    1.多线程优点

    • 资源利用率更好
    • 程序设计在某些情况下更简单
    • 程序响应更快

    2.多线程缺点:

    • 设计更复杂
    • 上下文切换的开销:上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。
    • 增加资源消耗。

    3.多线程有几种实现方案,分别是哪几种?
    两种。
    1.继承Thread类
    2.实现Runnable接口

    扩展一种:实现Callable接口。这个得和线程池结合。(一般可以不答)

    线程调度

    线程调度概述

    假如我们的计算机只有一个CPU,那么CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。

    线程有两种调度模型

    • 分时调度模型:所有线程轮流使用CPU 的使用权,平均分配每个线程占用CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU 时间片相对多一些。

    Java使用的是抢占式调度模型

    线程优先级

    • 线程默认优先级是:5
    • 线程优先级的范围是:1-10
    • 线程优先级:最大为10,最小为1,默认为5
    方法 定义
    public final int getPriority() 获取线程对象的优先级
    public final void setPriority(int newPriority) 设置线程的优先级

    设置对象优先级

    1. public class ThreadPriorityDemo {
    2. public static void main(String[] args) {
    3. ThreadPriority tp1 = new ThreadPriority();
    4. ThreadPriority tp2 = new ThreadPriority();
    5. //设置线程名
    6. tp1.setName("东方不败");
    7. tp2.setName("岳不群");
    8. // 获取默认优先级
    9. System.out.println(tp1.getPriority());
    10. //设置线程优先级
    11. tp1.setPriority(10);
    12. tp2.setPriority(1);
    13. System.out.println(tp1.getPriority());
    14. System.out.println(tp2.getPriority());
    15. tp1.start();
    16. tp2.start();
    17. }
    18. }
    19. ///////////////
    20. 5-----默认是5
    21. 10---设置后
    22. 1
    23. 5
    24. 东方不败:0
    25. 东方不败:1

    注意:
    线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

    线程控制:其他方法

    sleep(long millis) 线程休眠,单位是毫秒(ms)。(时间到了继续运行)
    join() 线程加入,只有这个线程完毕,其他线程才可以继续
    yield() 线程礼让,暂停当前线程,执行其他线程
    setDaemon(boolean on) 后台线程将该线程标记为守护线程或用户线程必须在启动线程前调用(一旦只剩下守护线程,后台线程立即结束
    stop() 中断线程
    interrupt() 中断线程,把线程的状态终止,并抛出一个异常,不会影响后续代码运行
    toString() Thread.*currentThread*().toString();
    Thread[Thread-0,5,main]

    线程睡眠sleep

    1. public class SleepThread extends Thread{
    2. public void run(){
    3. for(int i = 0 ; i < 5 ;i++){
    4. try{
    5. Thread.sleep(2000);//此线程sleep时,会运行其他线程
    6. //只能用try catch,不能throws,因为父类没抛这个异常,子类也不能抛
    7. //Thread.sleep(1000);//可以直接写sleep(1000);而省略Thread
    8. }catch(Exception ex){}
    9. System.out.println(getName()+" "+i);
    10. }
    11. }
    12. }
    13. SleepThread s1 = new SleepThread();
    14. SleepThread s2 = new SleepThread();
    15. s1.setName("意");
    16. s2.setName("而");
    17. s1.start();
    18. s2.start();

    线程加入 join():

    等待该线程终止,只有这个线程完毕,其他线程才可以继续

    1. public class SleepThread extends Thread{
    2. public void run(){
    3. for(int i = 0 ; i < 50 ;i++){
    4. System.out.println(getName()+" "+i);
    5. }
    6. }
    7. }
    8. //__________________
    9. SleepThread s1 = new SleepThread();
    10. SleepThread s2 = new SleepThread();
    11. SleepThread s3 = new SleepThread();
    12. s1.setName("111");s2.setName("222");s3.setName("333");
    13. s1.start();
    14. s1.join();//将1线程加入
    15. s2.start();
    16. s3.start();

    线程礼让,暂停当前线程,执行其他线程

    注意:
    理论上是一个线程执行一次后,等待下一个线程执行,然后第一个线程再执行第二次,……实际上可能有"误差"

    1. public void run() {
    2. for (int x = 0; x < 100; x++) {
    3. System.out.println(getName() + ":" + x);
    4. Thread.yield();
    5. }
    6. }
    7. //____________________________________________________
    8. ThreadYield ty1 = new ThreadYield();
    9. ThreadYield ty2 = new ThreadYield();
    10. ty1.setName("林青霞");
    11. ty2.setName("刘意");
    12. ty1.start();
    13. ty2.start();

    后台线程

    方法 定义
    setDaemon(boolean on) 将该线程标记为守护线程或用户线程。
    当正在运行的线程只剩下守护线程时,Java 虚拟机退出。
    该方法必须在启动线程前调用。

    关羽张飞守护刘备,刘备完成走了,关羽张飞就也走了

    1. ThreadDaemon td1 = new ThreadDaemon();
    2. ThreadDaemon td2 = new ThreadDaemon();
    3. td1.setName("关羽");
    4. td2.setName("张飞");
    5. // 设置守护线程(注意:必须在start()方法之前设置,否则会有异常!!)
    6. td1.setDaemon(true);
    7. td2.setDaemon(true);
    8. td1.start();
    9. td2.start();
    10. Thread.currentThread().setName("刘备");//改一改main线程的名字
    11. for (intx = 0; x < 5; x++) {
    12. System.out.println(Thread.currentThread().getName() + ":" + x);
    13. }
    14. 但是不会立马结束,还会运行几次

    中断线程

    方法 定义
    stop(): 让线程停止,这一个线程停止
    interrupt() 停止程序,运行catch部分代码
    1. public class ThreadStopDemo {
    2. public static void main(String[] args) {
    3. ThreadStop ts = new ThreadStop();
    4. ts.start();
    5. try {
    6. Thread.sleep(3000);
    7. ts.stop();//3s后会停止程序
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }}}

    --3s后程序停止

    1. public static void main(String[] args) {
    2. ThreadStop ts = new ThreadStop();
    3. ts.start();
    4. try {
    5. Thread.sleep(3000);
    6. ts.interrupt();//结束线程,运行catch内容
    7. } catch (InterruptedException e) {
    8. e.printStackTrace();
    9. }
    10. }


    好处:不影响后续代码执行(继续运行catch之后的)

    线程的生命周期

    • 新建new:创建线程对象
    • 就绪:有执行资格,没有执行权
    • 运行runable:有执行资格,有执行权
    • 阻塞:由于一些操作(sleep(),wait())让线程处于了该状态。没有执行资格,没有执行权;而另一种操作(sleep()时间到,notify())却可以把它激活,激活后处于就绪状态
    • 死亡:线程对象变成垃圾,等待回收。

    CPU同一时刻只能处理一个线程,多线程是多个线程轮流运行,允许运行但在等待的线程处于阻塞状态。

    方法 定义
    sleep 需要指定睡眠时间,单位是毫秒(ms)。(时间到了继续运行);
    wait() 等待,自己无法醒来,用notify(),可以唤醒;
    • CPU的执行资格:可以被CPU处理,在处理队列中排队;
    • CPU的执行权:正在被CPU处理;

    实现多线程

    1.继承Thread类

    • 实现步骤
    1. 1.继承Thread类
    2. 2.重写run方法
    3. 3.直接创建Thread的子类对象创建线程。
    4. 4.调用start方法开启线程并调用线程的任务run方法执行。
    • Run方法中定义的是线程中要运行的任务代码

      不是类中的所有代码都需要被线程执行的,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。(代码若想被多线程执行,必须写(封装)在run里面

      一般来说,被线程执行的代码肯定是比较耗时的。

    • 方法
    方法 定义
    run() 定义类时需要覆写的方法,定义的是线程中要运行的代码块
    start() 开启线程,调用run方法
    getName() 获取线程的名称(Thread-编号(从0开始))run和start都是Thread-0
    super(name) 带参构造时,设置线程名称
    Thread.currentThread() 返回正在运行的线程对象;Thread thread = Thread.currentThread();
    • run()和start()区别
      run():仅仅是封装被线程执行的代码,直接调用是普通方法,是按顺序运行
      start():首先启动了线程,然后再由jvm去调用该线程的run()方法,是随机运行

    注意:
    同一个线程只能调用一次!!(如my.start();只能用一次,第二次会显示异常IllegalThreadStateException)

    • 简单示例
    1. //创建一个类,继承Thread,实现run方法
    2. public class MyThread extends Thread {
    3. public void run() {
    4. for (int x = 0; x < 10000; x++) {
    5. System.out.println(x);
    6. }
    7. }
    8. }
    9. //测试,调用线程直接new一个,start即可
    10. public class MyThreadDemo {
    11. public static void main(String[] args) {
    12. // 创建两个线程对象
    13. MyThread my1 = new MyThread();//开启一个新的线程
    14. MyThread my2 = new MyThread();
    15. my1.start();//执行线程
    16. my2.start();
    17. }
    18. }
    19. //-----
    20. 这是三个线程,my1,my2main

    线程名称

    名字 方法 获取的线程
    获取main线程名称
    获取当前线程名称
    Thread.currentThread().getName() main
    当前的线程(main无法使用) 方法1:Thread.currentThread().getName()
    方法2:getName()
    Thread-0
    • 无参构造
    1. public class MyThread extends Thread {
    2. public void run() {
    3. for (int x = 0; x < 100; x++) {
    4. System.out.println(getName() + ":" + x);
    5. }
    6. }}
    • 带参构造
    1. public class MyThread extends Thread {
    2. public MyThread() {}
    3. ////在这里可以设置线程名比如super("林");
    4. public MyThread(String name){
    5. super(name);
    6. }
    7. @Override
    8. public void run() {
    9. for (int x = 0; x < 100; x++) {
    10. System.out.println(getName() + ":" + x);
    11. }
    12. }
    13. }
    获取线程名称
    1. //在main方法中调用
    2. MyThread my1 = new MyThread();
    3. MyThread my2 = new MyThread();
    4. System.out.println("main线程:"+Thread.currentThread().getName());//获取主线程名称
    5. my1.start();
    6. my2.start();
    7. //*************************************************************************
    8. main线程:main
    9. Thread-1:0
    10. Thread-1:1
    设置线程名称:2种
    方法 定义
    setName(String name) 设置线程的名称(main线程无法改名)

    无参构造设置线程名

    1. MyThread my1 = new MyThread();
    2. MyThread my2 = new MyThread();
    3. my1.setName("林青霞");
    4. my2.setName("刘意");
    5. my1.start();
    6. my2.start();
    7. ////////
    8. 林青霞:0
    9. 刘意:0...

    带参构造

    1. MyThread my1 = new MyThread("林青霞");
    2. MyThread my2 = new MyThread("刘意");
    3. my1.start();
    4. my2.start();
    5. //****************
    6. 林青霞:13
    7. 刘意:0

    2.实现Runnable接口(常用)

    概述

    1.为什么要给Thread类的构造函数传递Runnable的子类对象?

    因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。

    2.为什么可以避免由于Java单继承带来的局限性

    比如说,某个类已经有父类了,而这个类想实现多线程,但是这个时候它已经不能直接继承Thread类了(接口可以多实现implements,但是继承extends只能单继承),它的父类也不想继承Thread因为不需要实现多线程。

    3.实现Runnable接口的好处:

    • 可以避免由王Java单继承带来的 限性。
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面问对象的设计思想。

    实现

    • 实现步骤
    1. 1,实现Runnable接口。
    2. 2,重写run方法,将线程的任务代码封装到run方法中。
    3. 3,创建本类的对象
    4. 4,创建Thread类的对象,并把3步骤的对象作为构造参数传递。
    • 编写实现类
    1. class Demo implements Runnable{ //通过接口的形式完成。
    2. public void run(){ //覆盖接口中的run方法
    3. for(int x=0; x<100; x++)
    4. System.out.println(Thread.currentThread().getName()+"....."+x);//获取名称,只能间接用
    5. }
    6. }
    • 不初始化线程名:
    1. class ThreadDemo{
    2. public static void main(String[] args) {
    3. Demo d = new Demo();
    4. //注意:MyRunnable对象只需要创建一个即可,多个Thread对象可以接收同一个MyRunnable对象
    5. Thread t1 = new Thread(d);//通过Thread类创建线程对象,并传递Runnable。
    6. Thread t2 = new Thread(d);
    7. t1.start();
    8. t2.start();
    9. }
    10. }
    11. //-----结果---------
    12. Thread-0...0
    13. Thread-0...1
    • 初始化线程名
    1. Demo my = new Demo();
    2. Thread t1 = new Thread(my, "林青霞"); //创建,初始化线程名
    3. Thread t2 = new Thread(my, "刘意");
    4. t1.start();
    5. t2.start();
    6. //****************************
    7. 林青霞...84
    8. 刘意...64

    线程安全问题

    • 导致安全问题的出现的原因
      • 多个线程在操作共享的数据。
      • 操作共享数据的线程代码有多条。

    当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算。就会导致线程安全问题的产生。(例如,卖票,多线程,可能把数据卖到-1,因为各线程互相独立)

    注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

    • 解决思路--同步代码块

      就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程时不可以参与运算的。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。(同一时间,只能有一个线程参与运算---同步机制)

    • 回顾以前线程安全的类
    1. StringBuffer sb = new StringBuffer();
    2. Vector<String> v = new Vector<String>();
    3. Hashtable<String, String> h = new Hashtable<String, String>();
    4. (在定义时就锁了,因此效率低)

    注意:Vector是线程安全的时候才去考虑使用的,但是即使要安全,也不用

    • 把一个线程不安全的集合类变成一个线程安全的集合类

    以前的:List list1 = new ArrayList();// 线程不安全

    1. public static <T> List<T> synchronizedList(List<T> list)
    2. List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全

    实现卖电影票案例(不安全)

    某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。

    方式1:继承Thread类

    1. public class SellTicket extends Thread {
    2. // 定义100张票
    3. // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
    4. private static int tickets = 100;
    5. public void run() {
    6. // 定义100张票
    7. // 每个线程进来都会走这里,这样的话,每个线程对象相当于买的是自己的那100张票,这不合理,所以应该定义到外面
    8. // 是为了模拟一直有票
    9. while (true) {
    10. if (tickets > 0) {
    11. System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
    12. }
    13. }}}

    调用

    1. public class SellTicketDemo {
    2. public static void main(String[] args) {
    3. // 创建三个线程对象
    4. SellTicket st1 = new SellTicket();
    5. SellTicket st2 = new SellTicket();
    6. SellTicket st3 = new SellTicket();
    7. // 给线程对象起名字
    8. st1.setName("窗口1"); st2.setName("窗口2"); st3.setName("窗口3");
    9. // 启动线程
    10. st1.start(); st2.start(); st3.start();
    11. }
    12. }

    ----

    说明的是,通过继承Thread类来实现题中的需求并不是很好(tickets要用static修饰,并不太好),其实用Runnable接口更好地进行数据分离

    方式2:实现Runnable接口

    1. public class SellTicket implements Runnable {
    2. // 定义100张票
    3. private int tickets = 100;
    4. public void run() {
    5. while (true) {
    6. if (tickets > 0) {
    7. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
    8. }
    9. }
    10. }
    11. }

    调用

    1. public class SellTicketDemo {
    2. public static void main(String[] args) {
    3. // 创建资源对象
    4. SellTicket st = new SellTicket();
    5. // 创建三个线程对象
    6. Thread t1 = new Thread(st, "窗口1");
    7. Thread t2 = new Thread(st, "窗口2");
    8. Thread t3 = new Thread(st, "窗口3");
    9. // 启动线程
    10. t1.start();t2.start(); t3.start();
    11. }
    12. }

    问题分析:同票和负数票

    这种代码会有安全隐患,比如一个窗口获取票后,过来一段时间才订,在这段时间其他窗口就运行了,此处以sleep模拟,让每个线程休息一段时间

    1. if (tickets > 0) {
    2. // 为了模拟更真实的场景,我们稍作休息
    3. try {
    4. Thread.sleep(100); // t1就稍作休息,t2就稍作休息
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }
    8. System.out.println(......);
    9. }}}}

    但是出问题了!!!

    • 问题1:出现了重复的票

      CPU的一次操作必须是原子性的(在读取tickets--的原来的数值和减1之后的中间挤进了两个线程而出现重复)

    • 问题2:出现了0和负票

       随机性和延迟导致的(三个线程同时挤进一个循环里,tickets--的减法操作有可能在同一个循环中被执行了多次而出现越界的情况,比如说tickets要大于0却越界到了-1)

    也就是说,线程1执行的同时线程2也可能在执行,而不是线程1执行的时候线程2不能执行。

    这就是线程的安全问题

    • 解决方法:(下方有实现)
      • 同步代码块
      • 同步方法(简洁)
      • lock锁:

    同步(synchronized)

    同步概述

    同一时间,只能有一个线程访问该代码;

    同步可以解决安全问题的根本原因就在那个对象synchronized上。该对象如同锁的功能。(对象锁,同步锁)

    格式

    1. synchronized(对象){
    2. 需要被同步的代码
    3. }
    • 同步的特点
      • 同步的前提:同步中必须有多个线程并使用同一个锁。未满足这两个条件,不能称其为同步。
      • 同步的好处: 解决了线程的安全问题.
      • 同步的弊端:当线程相当多时,因为每个线程都会去判断同步锁,这是很耗费资源的,无形中会降低程序的运行效率。

    同步方法

    • 方法1:同步代码块
    1. Object obj = new Object();//同步,必须作为成员变量, 不能放在run内,
    2. public void run(){
    3. synchronized(obj){…}
    4. }
    • 方法2:
    1. public void run(){
    2. synchronized(thisTicket.class){…}
    3. }
    • 方法3:同步方法
    1. public synchronized void add(int num){...}

    同步代码块

    • 同步代码块的锁对象是任意对象

    买票同步代码块

    票是按顺序减少的且没有了同票和负票

    1. //卖票,共有100张票,多个线程同时卖票,一张票只能卖一次
    2. public class SellTicket implements Runnable {
    3. // 定义100张票
    4. private int tickets = 100;
    5. //创建锁对象
    6. private Object obj = new Object();
    7. public void run() {
    8. while (true) {
    9. synchronized (obj) {//必须用在run外面定义的obj,而不能用new Object,那样不是同一把锁,还会出错
    10. if (tickets > 0) {
    11. try {
    12. Thread.sleep(100);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();}
    15. System.out.println(Thread.currentThread().getName()
    16. + "正在出售第" + (tickets--) + "张票");
    17. }
    18. }
    19. }
    20. }
    21. }

    调用

    1. public class SellTicketDemo {
    2. public static void main(String[] args) {
    3. // 创建资源对象
    4. SellTicket st = new SellTicket();
    5. // 创建三个线程对象
    6. Thread t1 = new Thread(st, "窗口1");
    7. Thread t2 = new Thread(st, "窗口2");
    8. Thread t3 = new Thread(st, "窗口3");
    9. // 启动线程
    10. t1.start();
    11. t2.start();
    12. t3.start();
    13. }
    14. }


    票是按顺序减少的且没有了同票和负票

    同步方法

    1. public synchronized void add(int num){ }
    • 同步方法用的锁this
    • 静态方法的锁对象是当前类名.class,可以用对象.getClass()方法获取也可以用 类名.class表示;
    • 同步方法的格式:把同步关键字加在方法上。
    • 同步函数和同步代码块的区别
      • 同步函数的锁是固定的this。
      • 同步代码块的锁是任意的对象。

      建议使用同步代码块。

    1. public static synchronized void show(){}(其中num要该为static

    买票同步方法

    方法1
    1. //定义出售的票源
    2. public class Tickets implements Runnable{
    3. private int ticket = 100;
    4. public void run(){
    5. while(true){
    6. payTicket();
    7. }
    8. }
    9. public synchronized void payTicket(){
    10. if( ticket > 0){
    11. try{
    12. Thread.sleep(10);
    13. }catch(Exception ex){}
    14. System.out.println(Thread.currentThread().getName()+" 出售第 "+ticket--);
    15. }
    16. }
    17. }
    方法2

    会出错,因为第一个synchronized的锁是d,而第二个同步方法的锁是this,会出错

    • 方法
      • 方法1.把第一个的锁换成:this
      • 方法2.在第二个方法锁的第一行加上同样的锁synchronized(d)
    1. public class Tickets implements Runnable{
    2. private int ticket = 100;
    3. private int x = 0;
    4. //run方法定义
    5. public void run() {
    6. while (true) {
    7. if(x%2==0){
    8. synchronized (d) {//this
    9. if (tickets > 0) {
    10. try {
    11. Thread.sleep(100);
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
    16. }
    17. }
    18. }else {
    19. sellTicket();
    20. }
    21. x++;
    22. }
    23. }
    24. //-------------测试-----------
    25. private static synchronized void sellTicket() {
    26. if (tickets > 0) {
    27. try {
    28. // Thread.sleep(100);//延时
    29. } catch (InterruptedException e) {
    30. e.printStackTrace();
    31. }
    32. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
    33. }
    34. }
    35. }
    方法3:静态方法锁

    在总票数和同步方法上添加static

    出错,静态方法锁不能用this,需要用的是当前类的class对象:SellTicket.class

    1. public class Tickets implements Runnable{
    2. private static int tickets = 100;// 用静态方法锁,ticket必须加静态
    3. private int x = 0;
    4. public void run() {
    5. while (true) {
    6. if (x % 2 == 0) {
    7. synchronized (this) {// SellTicket.class
    8. if (tickets > 0) {
    9. try {
    10. Thread.sleep(10);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
    15. }
    16. }
    17. } else {
    18. sellTicket();
    19. }
    20. x++;
    21. }
    22. }
    23. private static synchronized void sellTicket() {
    24. if (tickets > 0) {
    25. try {
    26. Thread.sleep(100);
    27. } catch (InterruptedException e) {
    28. e.printStackTrace();
    29. }
    30. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票 ");
    31. }
    32. }
    33. }

    银行存钱案例

    两个人到银行存钱,每人存3次,每次100‘

    1. class Bank {// 建立银行类
    2. private int sum;// 金库
    3. public synchronized void add(int num) {// 同步函数(第二种写法),存钱
    4. sum = sum + num;
    5. try {
    6. Thread.sleep(10);
    7. } catch (InterruptedException e) {} // 延时等待,使顺序
    8. System.out.println("sum=" + sum);// 显示金库余额
    9. }
    10. }
    11. class Cus implements Runnable {// 定义线程类
    12. private Bank b = new Bank();
    13. public void run() {// 覆盖run方法
    14. for (int x = 0; x < 3; x++) {// 存钱,存三次,每次100
    15. b.add(100);
    16. }
    17. }
    18. }// 循环有次数限制,不会出现死循环
    19. class BankDemo {
    20. public static void main(String[] args) {
    21. Cus c = new Cus();// 实例化线程类
    22. Thread t1 = new Thread(c);// Thread
    23. Thread t2 = new Thread(c);
    24. t1.start();// 开启线程
    25. t2.start();
    26. }
    27. }

    Lock锁(JDK5之后)

    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    方法 定义
    void lock() 获取锁
    void unlock() 释放锁

    ReentrantLock是Lock的实现类(序列化类)

    Lock锁卖票案例

    1. public class SellTicket implements Runnable {//实现接口
    2. private int tickets = 100;
    3. // 定义锁对象
    4. private Lock lock = new ReentrantLock();
    5. public void run() {
    6. while (true) {
    7. try {
    8. // 加锁
    9. lock.lock();
    10. if (tickets > 0) {
    11. try {
    12. Thread.sleep(100);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
    17. }
    18. } finally {
    19. // 释放锁
    20. lock.unlock();// 放在finally中,使释放锁必须进行
    21. }
    22. }
    23. }
    24. }

    线程死锁

    死锁问题及其代码

    • 同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
    • 死锁前提:多线程,同步嵌套
    • 线程进入同步取锁,进去了不出去
    • 死锁概述:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

    死锁案例

    方法1

    1.定义锁对象类,两个锁A和B

    1. public class MyLock {
    2. public static final Object lockA=new Object();
    3. public static final Object lockB=new Object();
    4. }

    2.线程任务类

    1. public class ThreadTask implements Runnable {
    2. int x = new Random().nextInt(2);// 0,1 获取[0,2)之间随机值
    3. // 指定线程要执行的任务代码
    4. public void run() {
    5. while (true) {
    6. if (x % 2 == 0) {
    7. // 情况一
    8. synchronized (MyLock.lockA) {
    9. System.out.println("if-LockA");
    10. synchronized (MyLock.lockB) {
    11. System.out.println("if-LockB");
    12. System.out.println("if大口吃肉");
    13. } }
    14. } else {
    15. // 情况二
    16. synchronized (MyLock.lockB) {
    17. System.out.println("else-LockB");
    18. synchronized (MyLock.lockA) {
    19. System.out.println("else-LockA");
    20. System.out.println("else大口吃肉");
    21. } } }
    22. x++;
    23. } }}

    3.测试类

    1. public class ThreadDemo {
    2. public staticvoid main(String[] args) {
    3. // 创建线程任务类对象
    4. ThreadTask task = new ThreadTask();
    5. // 创建两个线程
    6. Thread t1 = new Thread(task);
    7. Thread t2 = new Thread(task);
    8. // 启动线程
    9. t1.start();
    10. t2.start();
    11. }
    12. }
    方法2:

    定义两个锁:

    1. public class LockA {
    2. // 做一个私有构造器,保证锁是私有的
    3. private LockA() {}
    4. // 为了让其他类可以调用,这样调用,用final使其他类不能修改
    5. // 只能通过类名调用静态成员获得
    6. public static final LockA locka = new LockA();
    7. }
    8. ///////////////////
    9. public class LockB {
    10. private LockB() {}
    11. public static final LockB lockb = new LockB();
    12. }

    线程任务类

    1. public class DeadLock implements Runnable {// 写死循环
    2. private int i = 0;
    3. public void run() {
    4. while (true) {
    5. if (i % 2 == 0) {// 偶数
    6. // 先进入A同步,再进入B同步
    7. synchronized (LockA.locka) {
    8. System.out.println("if...locka");// 先进入A同步
    9. synchronized (LockB.lockb) {
    10. System.out.println("if...lockb");
    11. }
    12. }
    13. } else {
    14. // 先进入B同步,再进入A同步
    15. synchronized (LockB.lockb) {
    16. System.out.println("else...lockb");// 先进入B同步
    17. synchronized (LockA.locka) {
    18. System.out.println("else...locka");
    19. }
    20. }
    21. }
    22. i++;// 一次奇数一次偶数
    23. }
    24. }
    25. }

    测试类

    1. public class DeadLockDemo {
    2. public static void main(String[] args) {
    3. DeadLock dead = new DeadLock();
    4. Thread t0 = new Thread(dead);
    5. Thread t1 = new Thread(dead);
    6. t0.start();
    7. t1.start();
    8. }
    9. }
    10. //------结果---
    11. if...locka
    12. if...lockb
    13. else...lockb
    14. if...locka 形成死锁

    线程间通信

    线程间通信概述

    线程间通讯:多个线程在处理同一资源,但是任务却不同。(不同线程处理同一个资源)
    例程:

    1.必须使用用一个resource,输入和输出类不能new
    2.必须加同步,否则会出现(mike...女女)姓名和性别不对应的情况
      输入和输出都要加锁,
      必须是同一个锁(此处用resource作为锁可以,用this作为锁不可以(输入输出不是同一个锁))
    1. //资源,一对一,输入一个,输出一个
    2. class Resource {
    3. String name;
    4. String sex;
    5. }

    生产者

    1. // 输入,生产者(输入),对资源对象Resource中成员变量赋值
    2. class Input implements Runnable {//实现接口
    3. Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了,可以加private
    4. Input(Resource r) { this.r = r; }// 反参数传递,构造函数,可以加public
    5. public void run() {
    6. int x = 0;
    7. while (true) {
    8. synchronized (r) {// 解决线程问题,与输出锁相同, 同步必须在while里面
    9. if (x == 0) {// x两次切换赋值,使得好像输入很多
    10. r.name = "mike";
    11. r.sex = "nan";
    12. } else {
    13. r.name = "丽丽";
    14. r.sex = "女女";
    15. } }
    16. x = (x + 1) % 2;// x的0、1变化
    17. }}}

    消费者

    1. //输出,消费者(输出)
    2. class Output implements Runnable {
    3. Resource r;
    4. Output(Resource r) { this.r = r;}
    5. public void run() {
    6. while (true) {
    7. synchronized (r) {// 和输入要用相同的锁,同步必须在while里面
    8. System.out.println(r.name + "....." + r.sex);
    9. }
    10. }
    11. }
    12. }

    测试

    1. class aaa {
    2. public static void main(String[] args) {
    3. Resource r = new Resource();// 创建资源。
    4. Input in = new Input(r);// 创建任务
    5. Output out = new Output(r);
    6. Thread t1 = new Thread(in);// 创建线程,执行路径。
    7. Thread t2 = new Thread(out);
    8. t1.start();// 开启线程
    9. t2.start();
    10. }
    11. }
    注意:   对于生产消费问题,生产者和消费者都必须加锁,而且是同一把锁
    • 优化:使输入和输出一对一:(下方的等待/唤醒机制案例)

    等待/唤醒机制

    虽然数据安全了,但是呢,一次一大片不好看(获取同一个数据一次而输出多次,也就是set一次却get了多次),我就想依次的一次一个输出(也就是set一次get一次)。

    如何实现呢?

    通过Java提供的等待唤醒机制解决。

    在Object类中提供了方法:

    • 涉及方法:
    方法 定义
    wait() 让线程处于冻结状态,被wait的线程会被存储到线程池中。(释放CPU执行资格和执行权)this.wait();
    notify() 唤醒线程池中一个线程(任意)。notify();
    notifyAll() 唤醒线程池中的所有线程。notifyAll();

    这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。

    • 为什么wait、notify、notifyAll定义在了Object类中

    因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁。任意的对象调用的方式一定定义在Object类中。

    • wait 和 sleep 区别?
    wait sleep
    指定时间 都可以
    暂停时 释放执行权,释放锁。(别的线程可以进去)

    方法

    方法 定义
    if if(flag),只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。(等待后,获得执行权不会再次进行判断flag)(一对一使用
    while while(flag),解决了线程获取执行权后,是否要运行!(等待后,获得执行权可以再次进行判断flag)(多对多使用
    notify 只能唤醒一个线程,如果本方唤醒了本方法,没有意义。而且while判断标记+notify会导致死锁。(一对一使用
    notifyAll 解决了本方线程一定会唤醒对方线程的问题。(多对多使用

    生产消费:加入等待唤醒机制,加入判断**

    方法1(更好):

    资源类

    1. class Resource {
    2. private String name;// 安全优化,私有化
    3. private String sex;// 安全优化,私有化
    4. private boolean flag = false;// 一对一输入输出
    5. public synchronized void set(String name, String sex) {
    6. if (flag) // 若资源有内容,暂时不输入,等输出后再赋值
    7. try { this.wait();
    8. } catch (InterruptedException e) {
    9. }
    10. // wait需要异常处理
    11. this.name = name;
    12. this.sex = sex;
    13. flag = true;
    14. this.notify();// 赋值完成,唤醒输出
    15. }
    16. public synchronized void out() {
    17. if (!flag)// 若资源无内容,暂时不输出
    18. try { this.wait();
    19. } catch (InterruptedException e) {
    20. }
    21. System.out.println(name + "..." + sex);
    22. flag = false;// 输出完成,定义flag无内容
    23. this.notify();// 唤醒输入
    24. }
    25. }

    输入

    1. class Input implements Runnable {
    2. Resource r;// 反参数传递,因为:输入输出是同一个资源,不能用new了
    3. Input(Resource r) {
    4. this.r = r;
    5. }// 反参数传递,构造函数
    6. public void run() {
    7. int x = 0;
    8. while (true) {
    9. if (x == 0)// x两次切换赋值,使得好像输入很多
    10. r.set("mike", "nan");
    11. else
    12. r.set("丽丽", "女女女女女女");
    13. x = (x + 1) % 2;// x的0、1变化
    14. } }}

    输出

    1. class Output implements Runnable {
    2. Resource r;
    3. Output(Resource r) {this.r = r;}
    4. public void run() {
    5. while (true) { r.out();
    6. } }}

    方法2:

    资源

    1. public class Student {
    2. String name;
    3. int age;
    4. boolean flag; // 判断默认情况是没有数据,如果是true,说明有数据
    5. }

    生产者

    1. public class SetThread implements Runnable {
    2. // 生产者
    3. private Student s;
    4. private int x = 0;
    5. public SetThread(Student s) {
    6. this.s = s;
    7. }
    8. public void run() {
    9. while (true) {
    10. synchronized (s) {
    11. if (s.flag) { // 判断有没有
    12. try {// 若有
    13. s.wait(); // 等待(必须是锁的wait),并释放锁,释放CPU执行权;
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();}
    16. }
    17. if (x % 2 == 0) {// 若没有,或没有了之后释放锁
    18. s.name = "林青霞";
    19. s.age = 27;
    20. } else {
    21. s.name = "刘意";
    22. s.age = 30;
    23. }
    24. x++; // x=1
    25. s.flag = true;// 生产完,修改标记,使进入消费
    26. s.notify(); // 唤醒t2,抢CPU的执行权。
    27. } // t1有,或者t2有
    28. }
    29. }
    30. }

    消费

    1. public class GetThread implements Runnable {// 消费
    2. private Student s;
    3. public GetThread(Student s) {
    4. this.s = s;
    5. }
    6. public void run() {
    7. while (true) {
    8. synchronized (s) {
    9. if (!s.flag) {// 若此时没有
    10. try {
    11. s.wait(); // 等待。并立即释放锁。将来醒过来的时候,是从这里醒过来
    12. } catch (InterruptedException e) {
    13. e.printStackTrace();
    14. }
    15. }
    16. System.out.println(s.name + "---" + s.age);// 当有内容的时候,
    17. // 林青霞---27
    18. // 刘意---30
    19. s.flag = false; // 修改标记防止再次进入消费
    20. s.notify();// 唤醒线程唤醒t1
    21. }
    22. }
    23. }
    24. }

    测试

    1. public class StudentDemo {
    2. public static void main(String[] args) {
    3. // 创建资源
    4. Student s = new Student();
    5. // 设置和获取的类
    6. SetThread st = new SetThread(s);
    7. GetThread gt = new GetThread(s);
    8. // 线程类
    9. Thread t1 = new Thread(st);
    10. Thread t2 = new Thread(gt);
    11. // 启动线程
    12. t1.start();
    13. t2.start();
    14. }
    15. }
    16. //-----结果----
    17. 林青霞---27
    18. 刘意---30
    19. 林青霞---27
    20. 刘意---30
    21. 林青霞---27

    优化生产消费问题

    多生产者,多消费者的问题。烤鸭生产一只消费一只

    资源

    1. class Resource {
    2. private String name;// 商品名称
    3. private int count = 1;// 烤鸭编号
    4. private boolean flag = false;// 标记
    5. public synchronized void set(String name) {// 生产设置烤鸭名字
    6. while (flag)// while进行判断,如果正在烤
    7. try {this.wait();}
    8. catch (InterruptedException e) {} // 等待t1 t0
    9. this.name = name + count;// 烤鸭+编号----若已经消费了
    10. count++;// 下一个编号2 3 4
    11. System.out.println(Thread.currentThread().getName() + "..生产者.." + this.name);// 生产烤鸭1
    12. flag = true;
    13. notifyAll();// 多线程,只唤醒一个可能出现死锁,需要唤醒所有线程
    14. }
    15. public synchronized void out() {// 消费t3
    16. while (!flag)// 如果正在卖
    17. try {this.wait();}
    18. catch (InterruptedException e) {} // 等待t2 t3
    19. System.out.println(Thread.currentThread().getName() + "...消费者........" + this.name);// 消费烤鸭1
    20. flag = false;
    21. notifyAll();
    22. }
    23. }

    生产者

    1. class Producer implements Runnable {// 生产者
    2. private Resource r;
    3. Producer(Resource r) {this.r = r;}// 构造
    4. public void run() {
    5. while (true) {r.set("烤鸭");} // 生产烤鸭
    6. }
    7. }

    消费者

    1. class Consumer implements Runnable {// 消费者
    2. private Resource r;
    3. Consumer(Resource r) {this.r = r;}
    4. public void run() {
    5. while (true) { r.out();} // 消费烤鸭
    6. }
    7. }

    测试

    1. class ProducerCustomerDemo {
    2. public static void main(String[] args) {
    3. Resource r = new Resource();// 实例化资源
    4. Producer pro = new Producer(r);// 生产
    5. Consumer con = new Consumer(r);// 消费
    6. Thread t0 = new Thread(pro);// 线程t0负责生产
    7. Thread t1 = new Thread(pro);
    8. Thread t2 = new Thread(con);
    9. Thread t3 = new Thread(con);
    10. t0.start();
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }
    16. //----结果----
    17. Thread-1..生产者..烤鸭136416
    18. Thread-2...消费者........烤鸭136416
    19. Thread-0..生产者..烤鸭136417
    20. Thread-3...消费者........烤鸭136417
    21. Thread-1..生产者..烤鸭136418
    22. Thread-2...消费者........烤鸭136418
    23. Thread-0..生产者..烤鸭136419
    24. Thread-3...消费者........烤鸭136419
    25. Thread-1..生产者..烤鸭136420
    26. Thread-2...消费者........烤鸭136420
    27. Thread-0..生产者..烤鸭136421

    Condition等待/唤醒机制

    jdk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

    • Synchronized()同步的锁是隐式的
    • Lock接口: 出现替代了同步代码块Synchronized或者同步函数。将同步的隐式锁操作变成显示锁操作。同时更为灵活。可以一个锁上加上多组监视器。
    1. Lock lock = new ReentrantLock();
    • Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象。可以任意锁进行组合。

    可以有多个监视器

    接口 方法 定义
    Lock接口 Lock lock = new ReentrantLock();
    lock() 获取锁。lock.lock();
    unlock() 释放锁,通常需要定义finally代码块中。lock.unlock();
    Condition接口 Condition con = lock.newCondition();
    await() 代替wait()功能,con.await();
    signal() 代替notify()功能,con.signal();
    signalAll() 代替notifyAll()功能,con.signalAll();

    优化生产消费问题

    资源

    1. //jdk1.5以后将同步和锁封装成了对象。
    2. import java.util.concurrent.locks.*;
    3. class Resource {
    4. private String name;
    5. private int count = 1;
    6. private boolean flag = false;
    7. Lock lock = new ReentrantLock();// 创建一个锁对象。
    8. // 通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
    9. Condition producer_con = lock.newCondition();// 监视生产锁
    10. Condition customer_con = lock.newCondition();// 监视消费锁
    11. public void set(String name) {// 去掉synchronized同步
    12. lock.lock();// 获取锁---同步
    13. try {// 异常处理
    14. while (flag)// 用while,循环检测
    15. try {
    16. producer_con.await();
    17. } catch (InterruptedException e) {
    18. } // 生产等待
    19. this.name = name + count;
    20. count++;
    21. System.out.println(Thread.currentThread().getName() + "...生产者5.0..." + this.name);// 生产烤鸭
    22. flag = true;
    23. customer_con.signal();// 生产完,释放另一组锁的一个,运行消费
    24. } finally {
    25. lock.unlock();
    26. } // 释放锁
    27. }
    28. public void out() {
    29. lock.lock();// 获取锁---同步
    30. try {
    31. while (!flag)// 用while,循环检测
    32. try {
    33. customer_con.await();
    34. } catch (InterruptedException e) {
    35. } // 消费等待
    36. System.out.println(Thread.currentThread().getName() + "...消费者.5.0......." + this.name);// 消费烤鸭
    37. flag = false;
    38. producer_con.signal();// 消费后,释放另一组锁的一个,运行生产
    39. } finally {
    40. lock.unlock();
    41. } // 释放锁
    42. }
    43. }

    线程组ThreadGroup

    概述

    • 线程组: 把多个线程组合到一起。

      它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

    • 默认情况下,所有的线程都属于主线程组。

      我们也可以给线程设置分组

    方法 定义
    public final ThreadGroup getThreadGroup() 获取所属的线程组
    public final String getName() 获取名字
    Thread(线程组名, 类名, 自定义线程) 创建线程组
    Void destroy() 销毁此线程组及其所有子组
    Void interrupt() 中断此线程组中的所有线程
    Void setDaemon() 变为后台线程:守护线程:只有守护线程会结束程序
    Void setMaxPriority() 设置最高优先级
    1. public class MyRunnable implements Runnable {
    2. public void run() {
    3. for (int x = 0; x < 100; x++) {
    4. System.out.println(Thread.currentThread().getName() + ":" + x);
    5. }
    6. }
    7. }

    测试

    获取线程组,名字

    1. MyRunnable my = new MyRunnable();
    2. Thread t1 = new Thread(my, "林青霞");
    3. Thread t2 = new Thread(my, "刘意");
    4. // 获取所在线程组的名字
    5. // 线程类里面的方法:public final ThreadGroup getThreadGroup()
    6. ThreadGroup tg1 = t1.getThreadGroup();
    7. //获取线程组
    8. ThreadGroup tg2 = t2.getThreadGroup();
    9. // 线程组名的方法:public final String getName()
    10. String name1 = tg1.getName();
    11. //获取线程组名字
    12. String name2 = tg2.getName();
    13. System.out.println(name1);
    14. System.out.println(name2);
    15. //获取主线程线程组名字
    16. System.out.println(Thread.currentThread().getThreadGroup().getName());

    默认情况下,所有的线程都属于主线程组main

    获取线程组名字,自己建立的线程属于main线程组

    修改线程组

    创建一个线程组

    创建其他线程的时候,把其他线程的组指定为我们自己新建线程组

    1. //创建一个新的线程组
    2. ThreadGroup tg = new ThreadGroup("这是一个新的组");
    3. MyRunnable my = new MyRunnable();
    4. //Thread(ThreadGroup group, Runnable target, String name)
    5. Thread t1 = new Thread(tg, my, "林青霞");
    6. Thread t2 = new Thread(tg, my, "刘意");
    7. System.out.println(t1.getThreadGroup().getName());
    8. System.out.println(t2.getThreadGroup().getName());
    9. //通过组名称设置后台线程,表示该组的线程都是后台线程---守护线程:若只剩下守护线程,程序结束
    10. //设置守护线程
    11. tg.setDaemon(true);

    ----创建成功

    线程池

    概述

    (1****)原因:创建和销毁线程成本是比较高的,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

    (2)线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

    在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池

        线程池主要用来**解决线程生命周期开销问题和资源不足问题**。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使**应用程序响应更快**。另外,通过适当的调整线程中的线程数目可以**防止****出现资源不足**的情况。
    

    (3)JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

    方法 定义
    public static ExecutorService newCachedThreadPool() 创建一个具有缓存功能的线程池
    public static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用的,具有固定线程数nThreads的线程池;
    ExecutorService pool = Executors.newFixedThreadPool(2);
    public static ExecutorService newSingleThreadExecutor() 创建一个只有单线程的线程池,相当于上个方法的参数是1
    Future submit(Runnable task) pool.submit(new MyRunnable());
    Future submit(Callable task) 调用
    Void shutdown() 结束线程池

    这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

    它提供了如下方法

    1. import java.util.concurrent.ExecutorService;
    2. public class MyRunnable implements Runnable {
    3. public void run() {
    4. for (int x = 0; x < 100; x++) {
    5. System.out.println(Thread.currentThread().getName() + ":" + x);
    6. }
    7. }
    8. }
    9. //_____________________________________________________________________
    10. public class ExecutorsDemo {
    11. public static void main(String[] args) {
    12. // 创建一个线程池对象,控制要创建几个线程对象。
    13. ExecutorService pool = Executors.newFixedThreadPool(2);
    14. // 可以执行Runnable对象或者Callable对象代表的线程
    15. pool.submit(new MyRunnable());//调用线程池中线程,提交一次调用一个线程,用完返回
    16. pool.submit(new MyRunnable());
    17. pool.shutdown();//结束线程池,没有他,程序一直不关闭
    18. }
    19. }

    Callable接口:创建线程3

    run方法没有返回值,不能抛出异常,而这个接口的方法call()可以。
    
    • (1)callable---是接口,有返回值,而且依赖于线程池才能使用,一般很少用
    • (2)Callable:是带泛型的接口。

      这里指定的泛型其实是call()方法的返回值类型。

    • (3)好处:
      • 可以有返回值
      • 可以抛出异常(Runable不能抛出异常)
    • (4)弊端:代码比较复杂,所以一般不用
    • (5)获取返回值:
    1. Future<String> f = es.submit(new ThreadPoolCallable());
    2. String s = f.get();//get是泛型

    案例

    1. public class MyCallable implements Callable {//可以直接设置call的类型 Callable<String>
    2. public String call() throws Exception {
    3. for (int x = 0; x < 100; x++) {
    4. System.out.println(Thread.currentThread().getName() + ":" + x);
    5. }
    6. return null;
    7. }
    8. }

    测试

    1. public class CallableDemo {
    2. public static void main(String[] args) {
    3. //创建线程池对象
    4. ExecutorService pool = Executors.newFixedThreadPool(2);
    5. //可以执行Runnable对象或者Callable对象代表的线程
    6. pool.submit(new MyCallable());
    7. pool.submit(new MyCallable());
    8. //结束线程池
    9. pool.shutdown();
    10. }
    11. }

    1.求和案例

    使用多线程技术,求和 , 两个线程,1个线程计算1+100,另一个线程计算1+200的和

    1. public class MyCallable implements Callable<Integer> {
    2. private int number;
    3. public MyCallable(int number) {
    4. this.number = number;
    5. }
    6. public Integer call() throws Exception {//求1-number的和
    7. int sum = 0;
    8. for (int x = 1; x <= number; x++) {
    9. sum += x;
    10. }
    11. return sum;
    12. }
    13. }
    14. /////////////////////////
    15. public class CallableDemo {
    16. public static void main(String[] args) throws InterruptedException, ExecutionException {
    17. // 创建线程池对象
    18. ExecutorService pool = Executors.newFixedThreadPool(2);
    19. // 可以执行Runnable对象或者Callable对象代表的线程
    20. Future<Integer> f1 = pool.submit(new MyCallable(100));
    21. Future<Integer> f2 = pool.submit(new MyCallable(200));
    22. // V get()
    23. Integer i1 = f1.get();//获得结果---需要抛出异常
    24. Integer i2 = f2.get();
    25. System.out.println(i1);//显示结果
    26. System.out.println(i2);
    27. // 结束
    28. pool.shutdown();
    29. }
    30. }

    匿名内部类方式使用多线程

    (1)匿名内部类的格式:

    1. new 类名或者接口名() {
    2. 重写方法;
    3. };

    (2)本质:是该类或者接口的子类对象。

    1. public class ThreadDemo {
    2. public static void main(String[] args) {
    3. // 1.继承Thread类来实现多线程-----只用一次
    4. new Thread() {
    5. public void run() {
    6. for (int x = 0; x < 100; x++) {
    7. System.out.println(Thread.currentThread().getName() + ":"+ x);
    8. }
    9. }.start();
    10. }
    11. }
    12. }

    1. // 2.实现Runnable接口来实现多线程
    2. new Thread(new Runnable() {
    3. public void run() {
    4. for (int x = 0; x < 100; x++) {
    5. System.out.println(Thread.currentThread().getName() + ":" + x);
    6. }
    7. }
    8. }) {}.start();
    9. }

    加上这个,就是两个线程互相抢了

    1. // 更有难度的
    2. new Thread(new Runnable() {
    3. public void run() {
    4. for (int x = 0; x < 100; x++) {
    5. System.out.println("hello" + ":" + x);
    6. }
    7. }
    8. }) {}.start();

    hello也加入了抢,共三个

    1. new Thread(new Runnable() {
    2. public void run() {
    3. for (int x = 0; x < 100; x++) {
    4. System.out.println("world" + ":" + x);
    5. }
    6. }
    7. }.start();

    全部运行的话,不会运行hello,只会运行world,还是3个

    方法3:

    1. Runnable r = new Runnable(){
    2. public void run(){
    3. System.out.println("###");
    4. }
    5. };
    6. new Thread(r).start();

    定时器

    • 概述

      定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能

    • 定时器:可以让我们在指定的时间做某件事情,还可以重复的做某件事情。

      在开发中一般不会用Timer,因为太弱,开发中一般用框架:

      Quartz是一个完全由java编写的开源调度框架。

    Timer---在util包中---定时器类

    方法 定义
    public Timer() 创建一个新的计时器
    public void schedule(TimerTask(任务),Date (时间)) 安排在指定的时间执行的任务(1次)
    public void schedule(TimerTask(任务),long(延迟)) 安排在指定延迟后执行的任务(1次)
    public void schedule(TimerTask (任务),long (时间),long period) 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行
    public void schedule(TimerTask (任务),Date (时间),long (延迟)) 安排指定的任务在指定的时间开始进行重复的固定延迟执行

    TimerTask--在Object包中:由Timer安排为一次执行或重复执行的任务

    方法 定义
    public abstract void run() 此计时器要执行的操作
    public boolean cancel() 终止此计时器
    Int purge() 从此计时器的任务队列中移除所有已取消的任务
    Long sheduleExecutionTime() 返回此任务最近实际执行是已安排执行时间

    循环一次

    1. // 做一个任务
    2. class MyTask extends TimerTask {
    3. private Timer t;//创建一个新的计时器
    4. public MyTask(){}//构造
    5. public MyTask(Timer t){ this.t = t;}//构造方法
    6. public void run() {
    7. System.out.println("beng,爆炸了");
    8. t.cancel();//终止要在任务执行完结束
    9. }
    10. }
    11. public class TimerDemo {
    12. public static void main(String[] args) {
    13. // 创建定时器对象
    14. Timer t = new Timer();
    15. //3s后运行,并结束任务
    16. t.schedule(new MyTask(t), 3000);
    17. }
    18. }

    循环调用

    循环爆炸:3秒后执行爆炸第一次,每隔2秒再继续炸---不用加结束了,不然会只运行一次

    1. // 做一个任务
    2. class MyTask2 extends TimerTask {
    3. public void run() {
    4. System.out.println("beng,爆炸了");
    5. }
    6. }
    7. public class TimerDemo2 {
    8. public static void main(String[] args) {
    9. // 创建定时器对象
    10. Timer t = new Timer();
    11. // 3秒后执行爆炸任务第一次,每隔2秒再继续炸
    12. t.schedule(new MyTask2(), 3000, 2000);
    13. }
    14. }

    案例:在指定的时间删除的指定目录

    1. //需求:在指定的时间删除我们的指定目录(我使用项目路径下的demo)(只删除1次)
    2. class DeleteFolder extends TimerTask {// 定义删除类
    3. public void run() {
    4. File srcFolder = new File("demo");
    5. deleteFolder(srcFolder);// 调用删除方法
    6. t.cancel();// 终止要在任务执行完结束
    7. }
    8. // 递归删除目录
    9. public void deleteFolder(File srcFolder) {
    10. File[] fileArray = srcFolder.listFiles();// 文件数组
    11. if (fileArray != null) {// 若有内容
    12. for (File file : fileArray) {// 遍历
    13. if (file.isDirectory()) {// 若是文件夹
    14. deleteFolder(file);// 遍历
    15. } else {// 若是文件—删除,并输出名字
    16. System.out.println(file.getName() + ":" + file.delete());
    17. }
    18. } // 若没有内容,删除文件夹
    19. System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
    20. }
    21. }
    22. }
    23. public class TimerTest {
    24. public static void main(String[] args) throws ParseException {
    25. Timer t = new Timer();
    26. String s = "2014-11-27 15:45:00";
    27. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    28. Date d = sdf.parse(s);// 定义删除时间
    29. t.schedule(new DeleteFolder(), d);// 在指定时间删除指定内容
    30. }
    31. }

    多线程的单例

    1. //多线程下的单例
    2. //饿汉式--单例模式,简单,使用
    3. class Single {// 公式
    4. private static final Single s = new Single();
    5. private Single() {}
    6. public static Single getInstance() { return s;
    7. }}
    8. // 懒汉式--延迟加载单例模式,麻烦,但是考试多(技术含量高)
    9. // 加入同步为了解决多线程安全问题。
    10. // 加入双重判断是为了解决效率问题。
    11. class Single {
    12. private static Single s = null;
    13. private Single() {}
    14. public static Single getInstance() {// 同步使同一时间只进入一个线程
    15. if (s == null) {// 加入双重判断是为了解决效率问题。
    16. synchronized (Single.class) {// 加入同步为了解决多线程安全问题。
    17. // 不可以用this.getClass(),因为是非静态的
    18. if (s == null) s = new Single();
    19. }}
    20. return s;
    21. }}
    22. class SingleDemo {
    23. public static void main(String[] args) {
    24. Single s1 = Single.getInstance();// 单例调用
    25. Single s2 = Single.getInstance();// 用s1,s2调用单例其他方法
    26. }}

    停止线程

    • 停止线程
      • stop方法。已经过时不再使用。
      • run方法结束。
    • 怎么控制线程的任务结束呢?
      • 方法1:定义循环结束标记

      任务中都会有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记(条件:while之类)来完成。(若有wait(处于冻结状态),sleep(时间有时很长)无法结束)

    • 如果线程处于了冻结状态,无法读取标记。如何结束呢
      • 使用interrupt(中断)方法。

      可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 但是强制动作会发生了InterruptedException,记得要处理。

    1. class StopThread implements Runnable {
    2. private boolean flag = true;
    3. public synchronized void run() {
    4. while (flag) {
    5. try {
    6. System.out.println("线程");
    7. wait();
    8. } // 有wait,处于冻结状态,无法正常结束
    9. catch (InterruptedException e) {// 中断异常处理
    10. System.out.println(Thread.currentThread().getName() + ".." + e);
    11. flag = false;// 中断后,结束
    12. }
    13. System.out.println(Thread.currentThread().getName() + "......++++");
    14. }
    15. }
    16. public void setFlag() {
    17. flag = false;
    18. }
    19. }
    20. class StopThreadDemo {
    21. public static void main(String[] args) {
    22. StopThread st = new StopThread();
    23. Thread t1 = new Thread(st);
    24. Thread t2 = new Thread(st);
    25. t1.start();// run中,直接进入wait状态,
    26. t2.setDaemon(true); // t2设置为守护线程(后台线程)在启动线程前调用
    27. t2.start();
    28. int num = 1;
    29. for (;;)// 无限循环
    30. {
    31. if (++num == 5) {// 若num==20,线程结束
    32. System.out.println("interrupt前");
    33. t1.interrupt();// t1中断,运行run中的中断异常处理
    34. System.out.println("interrupt后");
    35. break;
    36. }
    37. System.out.println("main...." + num);
    38. }
    39. System.out.println("over");// main结束
    40. }
    41. }
    42. //-----结果---
    43. 线程//线程1输出,之后1进入wait
    44. main....2//main输出
    45. main....3
    46. main....4
    47. interrupt//主函数,顺序
    48. interrupt//主函数,顺序(interrupt在线程中,并行)
    49. over//主函数
    50. 线程//线程2输出,之后2进入wait
    51. Thread-1.....java.lang.InterruptedException//interrupt1,运行run的catch
    52. Thread-1......++++//线程1,顺序输出

    习题

    1.如果错误 错误发生在哪一行?

    1. class Test implements Runnable{
    2. public void run(Thread t){}
    3. }
    4. ----------------------------------------------------
    5. 错误在第一行,应该被abstract修饰:class abstract Test implements Runnable{
    6. 原因:这个类实现了接口,但是抽象方法run没有进行覆盖,那么类要定义为抽象类

    2.输出的是什么?

    1. class ThreadTest {
    2. public static void main(String[] args) {
    3. new Thread(new Runnable() {
    4. public void run() {
    5. System.out.println("runnable run");
    6. }
    7. }) {
    8. public void run() {
    9. System.out.println("subThread run");
    10. }// run进行了覆写
    11. }.start();
    12. }}
    13. -------------------------------------------------
    14. 输出:subThread run
    15. 若是后面{}里没有内容,输出:runnable run
    16. 正常写法:
    17. new Thread(){
    18. public void run(){
    19. System.out.println("run覆写");
    20. }
    21. }.start();





  • 相关阅读:
    林正英电影之我见
    雕虫小技,颇感羞愧。
    简单地求最大公约数
    大声喊:我现在不喜欢编程!
    简单递归题,核反应堆中有α和β两种粒子...
    递归简单题2
    Java接口和抽象类的理解
    如何入门计算机高级程序语言,进化菜鸟程序员
    memcached安装报错 error while loading shared libraries: libevent2.0.so.5: cannot open shared object file: No such file or directory解决
    linux mount 过程
  • 原文地址:https://www.cnblogs.com/ziyue7575/p/12193933.html
Copyright © 2020-2023  润新知