多线程

多线程

一、线程的定义和创建

1.进程和线程

  • 程序(Program)

    程序是一段静态的代码,每个.java文件就是java中的一个程序,是应用程序执行的蓝本

  • 进程(Process)

    1.进程是指正在运行的程序,有自己的地址空间

2.进程的特点:

  • 动态性:正在运行的程序

  • 并发性:可同时打开world,idea等软件

  • 独立性:world,idea等软件之间是没有关系的

  • 并发和并行的区别:

    并发(concurrency):(一个CPU(采用时间片)同时执行多个任务。)在同一时刻只能有一条指令执行,多个进程被快速的轮换交替执行。从宏观来看是多个进程同时执行。但从微观上来看,是把时间分成了若干片段,使多个进程快速交替执行。

并行(parallel):(多个CPU同时执行多个任务)在同一时刻,有多条指令在多个处理器上同时执行,从宏观和微观上来看,都是同时执行的。

​			

  • 线程(Thread)

    1. 进程内部的一个执行单元

    2. 被称为轻量级进程

    3. 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称为多线程

    4. 线程的特点:

      • 轻量级进程
      • 独立调度的基本单位(一个cpu上同时运行了两个进程时,是进程里的线程在排队)
      • 共享进程资源(进程申请了资源后,线程可以直接用)
      • 可并发执行

      如下图:

      (1)公路中的隔离带将公路隔离成了两条马路(即两条进程)。每条马路上有多个车道(即进程中的多个线程),每个车道是可以同时走车的(即可并发执行)

      (2)迅雷相当于一个进程,里面的两个下载任务相当于线程。

  • 线程和进程的区别

    • 根本区别:进程是资源分配的单位,线程是调度和执行的单位(找CPU排队)
    • 开销:进程间的切换开销大,线程间的切换开销小
    • 分配内存:进程可以分配到资源,线程直接使用进程的资源
    • 包含关系:线程是进程的一部分

2.线程的定义和创建

2.1方法一:继承Thread类

  • Thread类是线程顶级类。继承Thread类可以快速定义线程
  • run() 是线程体,线程要完成的任务
  • start() 线程启动,线程进入就绪队列,等待获取CPU并执行
package com.zhang.thread.thread1;

/**
 * 自定义一个线程类,继承Thread类
 * 
 * run() 是线程体
 * start() 线程启动
 * setName() 设置线程名
 * Thread.currentThread().getName() 线程名
 * setPriority() 设置线程优先级
 * Thread.currentThread().getPriority() 线程优先级
 */
public class MyThread extends Thread {
    public void run() {
        // this.setName("乌龟线程");//设置线程名
        // this.setPriority(3);//设置线程的优先级
        while (true) {
           /* System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
                    + " 线程优先级:" + Thread.currentThread().getPriority());*/
            System.out.println("乌龟领先了。。。。 线程名:" + this.getName()
                    + " 线程优先级:" + this.getPriority());
        }
    }

}

package com.zhang.thread.thread1;

public class Test {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.setName("乌龟线程1");
        thread.start();//启动线程

        Thread thread2 = new MyThread();
        thread2.setName("乌龟线程2");
        thread2.start();//启动线程

        Thread.currentThread().setName("兔子线程");
        Thread.currentThread().setPriority(6);//最大优先级是10,优先级越高,执行的越多
        while (true) {
            System.out.println("兔子领先了。。。。  线程名:" + Thread.currentThread().getName() + " 线程优先级:" + Thread.currentThread().getPriority());
        }
    }
}

运行结果:

乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程1 线程优先级:5
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
乌龟领先了。。。。 线程名:乌龟线程2 线程优先级:5
兔子领先了。。。。  线程名:兔子线程 线程优先级:6
兔子领先了。。。。  线程名:兔子线程 线程优先级:6

总结:

  • 优点:编码简单
  • 缺点:单继承,继承了Thread类无法继承其他类

2.2方法二:实现了Runnable接口

package com.zhang.thread.runnable;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
                    + " 线程优先级:" + Thread.currentThread().getPriority());
        }
    }
}

package com.zhang.thread.runnable;

public class Test {
    public static void main(String[] args) {
        //方法一:自定义MyRunnable类
        //Runnable runnable = new MyRunnable();

        //方法二:匿名内部类
        /*Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
                            + " 线程优先级:" + Thread.currentThread().getPriority());
                }
            }
        };*/

        //方法三:lambda表达式
        Runnable runnable = () -> {
            while (true) {
                System.out.println("乌龟领先了。。。。 线程名:" + Thread.currentThread().getName()
                        + " 线程优先级:" + Thread.currentThread().getPriority());
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();//启动乌龟线程

        Thread thread2 = new Thread(runnable);
        thread2.start();//启动乌龟线程
        while (true) {
            System.out.println("兔子领先了。。。。  线程名:" + Thread.currentThread().getName() + " 线程优先级:" + Thread.currentThread().getPriority());
        }
    }
}

运行结果:

兔子领先了。。。。  线程名:main 线程优先级:5
兔子领先了。。。。  线程名:main 线程优先级:5
兔子领先了。。。。  线程名:main 线程优先级:5
兔子领先了。。。。  线程名:main 线程优先级:5
兔子领先了。。。。  线程名:main 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-1 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5
乌龟领先了。。。。 线程名:Thread-0 线程优先级:5

总结:

  • 优点:可以继承其他类 ;便于多个线程共享同一个任务(只创建了一个Runnable对象,多个线程共用一个资源;而继承Thread会创建多个对象,资源重复)
  • 缺点:编码复杂
  • 使用这种方式更多一点

2.3实现了Callable接口

  • 与Runnable相比,Callable的功能更强大,可以有返回值,可以抛异常

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }
    
  • 需要借助FutureTask获取返回结果

    public class FutureTask<V> implements RunnableFuture<V> {
    }
    
    public interface RunnableFuture<V> extends Runnable, Future<V> {
    }
    
  • Future接口

    • FutureTask是Future接口接口的唯一实现类
    • FutureTask同时实现了 Runnable, Future两个接口。既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回结果
    • 可以对具体Runnable,Callable任务的执行结果进行取消,查询是否完成,获取结果
  package com.zhang.thread.callable;
  
  import java.util.Random;
  import java.util.concurrent.Callable;
  
  public class MyCallable implements Callable<Integer> {
  
      @Override
      public Integer call() throws Exception {
          System.out.println("==================1===========");
          Thread.sleep(5000);//休息5秒
          System.out.println("==================2===========");
          return new Random().nextInt(10);
  
      }
  }
  
package com.zhang.thread.callable;

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

/**
 * futureTask.isDone();是否执行完成
 * futureTask.get();//获取结果
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //方法一:自定义MyCallable类
        // Callable callable = new MyCallable();

        //方法二:匿名内部类
        /*Callable callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return new Random().nextInt(10);
            }
        };*/

        //方法三:lambda
        Callable callable = () -> new Random().nextInt(10);

        FutureTask<Integer> futureTask = new FutureTask(callable);
        Thread thread = new Thread(futureTask);
        thread.start();

        System.out.println(futureTask.isDone());//是否执行完成
        Integer integer = futureTask.get();//获取结果,得不到返回值就会在此阻塞,后面代码不会执行
        System.out.println("获取结果:::" + integer);
        System.out.println(futureTask.isDone());

    }
}

运行结果:

false
获取结果:::4
true

Process finished with exit code 0

二、线程控制

1.线程的生命周期

  • 新建:new()了线程对象后

  • 就绪:调用start()后处于就绪状态,等待系统分配CPU

    ​ 当系统选定等待的线程开始执行时,(就绪状态->执行状态,称为CPU调度)

  • 运行:执行run()中的代码

  • 阻塞:sleep()

  • 死亡:(1)线程完成了全部工作(2)抛出异常(3)强制终止(stop(),不推荐)

  • 运行状态的出口:死亡,就绪,阻塞

  • 就绪的入口:新建,运行,阻塞

  • 了解线程同步,线程通信后,会知道更多的线程状态

2.线程控制

用来对线程的生命周期进行干预

(1)join():阻塞其他线程,当自己的线程执行完后才能继续执行

package com.zhang.thread.ctrl1;

public class MyThread extends Thread {
    public void run() {
        int i = 1;
        while (i <= 10) {
            System.out.println("乌龟领先了。。。。 "+i);
            i++;
        }
    }

}

package com.zhang.thread.ctrl1;

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

        int i = 1;
        while (i <= 10) {
            System.out.println("兔子领先了。。。。  " + i);
            //实现,兔子先跑,跑到5后,乌龟再跑,等乌龟跑完兔子才能接着跑
            if (i == 5) {
                Thread thread = new MyThread();
                thread.start();//启动线程
                thread.join();//让别人的线程阻塞,当自己的线程跑完后,别的线程才能开始跑
            }
            i++;
        }
    }
}

执行结果:

兔子领先了。。。。  1
兔子领先了。。。。  2
兔子领先了。。。。  3
兔子领先了。。。。  4
兔子领先了。。。。  5
乌龟领先了。。。。 1
乌龟领先了。。。。 2
乌龟领先了。。。。 3
乌龟领先了。。。。 4
乌龟领先了。。。。 5
乌龟领先了。。。。 6
乌龟领先了。。。。 7
乌龟领先了。。。。 8
乌龟领先了。。。。 9
乌龟领先了。。。。 10
兔子领先了。。。。  6
兔子领先了。。。。  7
兔子领先了。。。。  8
兔子领先了。。。。  9
兔子领先了。。。。  10

Process finished with exit code 0

(2)sleep():让自己的线程停止运行一段时间,让出CPU

  • 实际中用来模拟线程切换,暴露线程安全问题
package com.zhang.thread.ctrl2;

public class MyThread extends Thread {
    public void run() {
        int i = 1;
        while (i <= 10) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("乌龟领先了。。。。 ");
            i++;
        }
    }

}

package com.zhang.thread.ctrl2;

import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();//启动线程
        int i = 1;
        while (i <= 10) {
            //Thread.sleep(100);
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println("兔子领先了。。。。  ");
            i++;
        }
    }
}

运行结果:

乌龟领先了。。。。 
兔子领先了。。。。  
兔子领先了。。。。  
乌龟领先了。。。。 
乌龟领先了。。。。 
兔子领先了。。。。  
乌龟领先了。。。。 
兔子领先了。。。。  
乌龟领先了。。。。 
兔子领先了。。。。  
乌龟领先了。。。。 
兔子领先了。。。。  
兔子领先了。。。。  
乌龟领先了。。。。 
兔子领先了。。。。  
乌龟领先了。。。。 
兔子领先了。。。。  
乌龟领先了。。。。 
乌龟领先了。。。。 
兔子领先了。。。。  

Process finished with exit code 0

(3)yield():让正在执行的线程暂停,进入就绪状态,如果没有其他等待就绪的线程,就会马上恢复执行

package com.zhang.thread.ctrl3;

public class MyThread extends Thread {
    public void run() {
        int i = 1;
        while (i <= 10) {
            Thread.yield();
            System.out.println("乌龟领先了。。。。 ");
            i++;
        }
    }
}

package com.zhang.thread.ctrl3;

public class Test {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.start();//启动线程
        int i = 1;
        while (i <= 10) {
            Thread.yield();
            System.out.println("兔子领先了。。。。  ");
            i++;
        }
    }
}

sleep()和yield()的区别

  • 执行了sleep会进入阻塞状态,而执行了yield会进入就绪状态
  • sleep需要指定休息时间,yield无需指定
  • sleep有异常,可以中断休息,而yield不涉及这些

(4)setDaemon():将线程设置为寄生线程(后台线程)

  • 只能在线程启动前设置为后台线程
  • 前台线程结束时,后台线程要跟着结束

中断后台线程方法一:

​ thread.setDaemon(true);//设为寄生线程(后台线程),主线程跑完后这个线程也停止

package com.zhang.thread.ctrl4;

public class Test {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        thread.setDaemon(true);//设为寄生线程(后台线程),主线程跑完后这个线程也停止
        thread.start();//启动线程
        int i = 1;
        while (i <= 100) {
            System.out.println("兔子领先了。。。。  " + i);
            i++;
        }
    }
}

package com.zhang.thread.ctrl4;

public class MyThread extends Thread {
    public void run() {
        while (true) {
            System.out.println("乌龟领先了。。。。 ");
        }
    }

}

运行结果:
乌龟领先了。。。。 
兔子领先了。。。。  92
兔子领先了。。。。  93
兔子领先了。。。。  94
兔子领先了。。。。  95
兔子领先了。。。。  96
兔子领先了。。。。  97
兔子领先了。。。。  98
兔子领先了。。。。  99
兔子领先了。。。。  100
乌龟领先了。。。。 
乌龟领先了。。。。 
乌龟领先了。。。。 
    
Process finished with exit code 0

(5)interrupt():并没有直接中断线程,而是需要被中断线程自己处理

中断后台线程方法二:

package com.zhang.thread.ctrl5;

public class MyThread extends Thread {
    public void run() {
        while (!this.isInterrupted()) {//isInterrupted是true,说明被打断了
            System.out.println("乌龟领先了。。。。 ");
        }
    }

}

package com.zhang.thread.ctrl5;

public class Test {
    public static void main(String[] args) {
        Thread thread = new MyThread();
        //thread.setDaemon(true);//设为寄生线程,后台线程,守护线程
        thread.start();//启动线程
        int i = 1;
        while (i <= 100) {
            System.out.println("兔子领先了。。。。  " + i);
            i++;
        }
        thread.interrupt();//中断乌龟线程,并没有真正的中断乌龟线程,只是修改了乌龟线程的一个相关符号位
    }
}

运行结果:
兔子领先了。。。。  99
兔子领先了。。。。  100
乌龟领先了。。。。 
乌龟领先了。。。。 

Process finished with exit code 0    

总结:

  • thread.interrupt();//中断乌龟线程,并没有真正的中断乌龟线程,只是修改了乌龟线程的一个相关符号位
  • this.isInterrupted()判断 和 thread.interrupt()结合实现
  • 补充:this.isInterrupted()被打断后,底层会自动还原为未打断状态,等待下一次被打断

(6) stop():结束线程,不推荐使用

三、线程的同步(synchronized)

3.1问题的提出

场景:多个用户同时操作一个银行账户,每次取400,取款前检查余额是否足够,不够则取款失败

分析:使用多线程解决;开发一个取款线程类,每个用户对应一个线程对象;多个线程共享一个银行账户,使用Runnable解决

思路:创建一个银行账户Account;创建取款线程AccountRunnable;创建测试类Test,让两个人同时取款

package com.zhang.thread.syn0;

/**
 * 银行账户
 */
public class Account {
    private double balance = 600.0;//余额

    public double getBalance() {
        return this.balance;
    }

    public void withDraw(double money) {//取款
        this.balance -= money;
    }

}

package com.zhang.thread.syn0;

/**
 * 取款
 */
public class AccountRunnable implements Runnable {
    Account account = new Account();

    @Override
    public void run() {

        if (account.getBalance() >= 400) {
            try {
                Thread.sleep(1);//模拟线程中的问题
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.withDraw(400.0);
            System.out.println("取款成功,现在的余额是" + account.getBalance());
        } else {
            System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
        }


    }
}

package com.zhang.thread.syn0;

public class Test {
    public static void main(String[] args) {
        Runnable runnable = new AccountRunnable();
        //创建两个线程
        Thread thread1 = new Thread(runnable, "小明");
        Thread thread2 = new Thread(runnable, "小明妻子");
        //启动线程(开始取款)
        thread1.start();
        thread2.start();
    }
}

运行结果:(从运行结果可以看出,两次都取款成功,是错误的

取款成功,现在的余额是-200.0
取款成功,现在的余额是-200.0

Process finished with exit code 0

总结:

  • 当多个线程访问同一个数据时,容易出现线程安全问题,需要让线程同步,保证线程安全
  • 线程同步:当多个线程访问同一个资源时,需要确保资源在某一时刻只被一个线程访问
  • 线程同步的实现方案:
    • (1)同步代码块锁:synchronized (obj) {}
    • (2)同步方法锁: public synchronized void withDraw() {}
    • (3)Lock锁:ReentrantLock ,ReentrantReadWriteLock
    • (4)volatile+CAS无锁化方案

解决方法:对取款代码加锁synchronized(){}

package com.zhang.thread.syn0;

/**
 * 取款
 */
public class AccountRunnable implements Runnable {
    Account account = new Account();

    @Override
    public void run() {
        synchronized (account) {
            if (account.getBalance() >= 400) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.withDraw(400.0);
                System.out.println("取款成功,现在的余额是" + account.getBalance());
            } else {
                System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
            }
        }


    }
}

重新运行:
取款成功,现在的余额是200.0
余额不足,取款失败,现在的余额是200.0

Process finished with exit code 0    

3.2同步代码块:synchronized (锁对象) {}

package com.zhang.thread.syn0;

/**
 * 取款
 */

/**
 * 总结1(认识锁对象):synchronized (锁对象) {}
 * (1)必须是引用数据类型,不能是基本数据类型
 * (2)在同步代码块中可以改变锁对象的值,不能改变其引用
 * (3)尽量不用String,和包装类Integer做锁对象,如果使用了,只要保证在代码块中没有任何操作也可以
 * (4)一般使用共享资源做锁对象即可
 * (5)也可以创建一个专门的锁对象,没有任何业务意义
 * (6)建议使用final修饰锁对象
 *
 * 总结2:同步代码块的执行过程:
 * (1)第一个线程来到同步代码块,发现锁对象(其实是:和锁对象关联的监视器对象中)的锁是open状态,将锁close,然后执行同步代码块中的代码
 * (2)第一个线程执行过程中,发生了线程切换(阻塞,就绪),让出CPU,并未开锁
 * (3)第二个线程获取CPU,来到同步代码块,发现锁是close状态,无法执行其中的代码,也进入阻塞状态
 * (4)第一个线程再次获取CPU,执行完代码,释放锁open
 * (5)第二个线程再次获取CPU,发现锁对象(其实是:和锁对象关联的监视器对象中)的锁是open状态,重复第一线程的处理过程
 *
 * 注意:同步代码块中能发生CPU切换吗?  可以,但后续执行的线程无法执行线程同步(锁还是close)
 *
 * 总结3:线程同步的优缺点:
 * 优点:线程安全
 * 缺点:效率低,可能出现死锁(A被锁,锁中调了B;B被锁,锁中调了A)
 *
 * 总结4:
 * 多个代码块使用了同一个锁对象时,锁住了一个代码块,所有使用该锁的代码块都被锁住,其他线程无法访问其中的任何一个
 *
 */
public class AccountRunnable implements Runnable {
    Account account = new Account();

    @Override
    public void run() {
        synchronized (account) {
            if (account.getBalance() >= 400) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.withDraw(400.0);
                System.out.println("取款成功,现在的余额是" + account.getBalance());
            } else {
                System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
            }
        }


    }
}

3.3同步方法:public synchronized void 方法名()

package com.zhang.thread.syn1;

/**
 * 取款
 */

/**
 * 总结:
 * 1.不要将run()定义为同步方法
 * 2.同步方法锁的对象是this,静态同步方法锁的监视器是类名.class
 * 3.同步代码块的效率高于同步方法锁
 *      同步方法锁住了整个方法;同步代码块只锁住了方法中的一部分代码
 *      同步方法的锁对象是this,一旦锁住一个方法,会锁住所有的同步方法;同步代码块只会锁住同一个锁对象的代码块
 */
public class AccountRunnable implements Runnable {
    Account account = new Account();

    @Override
    public void run() {

        this.withDraw();
    }

    public synchronized void withDraw() {
        if (account.getBalance() >= 400) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.withDraw(400.0);
            System.out.println("取款成功,现在的余额是" + account.getBalance());
        } else {
            System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
        }

    }
}

synchronized总结:

  1. 对于synchronized锁(同步方法,同步代码块),如果正常执行完毕,会释放锁。如果线程执行异常,JVM也会让线程自动释放锁
  2. synchronized的缺点:
    • 如果获取锁的线程由于IO等原因让线程阻塞了,此时一直没有释放锁,其他线程只能一直等着,影响效率
    • synchronized会将读写操作都上锁。无法实现多个读操作同时运行,一个写操作运行

3.4Lock锁

public interface Lock {
        /**
        *lock();最常用的锁,如果锁被其他线程获取,则等待;
        *要主动通过 unlock()释放锁,且要放到finally里
        */
        void lock();
       /**
        *tryLock();尝试获取锁,成功则返回true;失败(被其他线程获取),返回false;不会一直等待
        */
        boolean tryLock();
     	/**
        *tryLock(long time, TimeUnit unit);尝试在某段时间获取锁,成功则返回true;失败,返回false;		   *不再等待
        */
   		boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
   	   /**
        *lockInterruptibly();通过这个方法获取锁时,如果线程正在等待获取锁,可以通过					*Thread.interrupt()中断等待过程
        */
    	void lockInterruptibly() throws InterruptedException;
       /**
         * unlock()释放锁
         */
        void unlock();
}

代码示例:

package com.zhang.thread.syn2;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 取款
 */

public class AccountRunnable implements Runnable {
    Account account = new Account();
    Lock lock = new ReentrantLock();//定义锁

    @Override
    public void run() {

        lock.lock();//关锁
        try {
            if (account.getBalance() >= 400) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                account.withDraw(400.0);
                System.out.println("取款成功,现在的余额是" + account.getBalance());
            } else {
                System.out.println("余额不足,取款失败,现在的余额是" + account.getBalance());
            }
        } finally {
            lock.unlock();//解锁,必须放到finally里
        }

    }
}

ReentrantLock:可重入锁
  • ReentrantLock是唯一实现了Lock接口的非内部类
  • ReentrantLock可重入的意思是可被单个线程多次获取(发现被线程自己锁了后,还可继续获取)
  • 分为公平锁(需排队)和非公平锁,默认非公平锁,非公平锁效率高
  • ReentrantLock是通过队列来管理线程的

总结:

Lock锁的底层核心是AbstractQueuedSynchronizer(AQS抽象的队列式同步器)

3.5ReadWriteLock锁

  • ReadWriteLock接口有两个方法(readLock()读锁,writeLock()写锁),将读写分成两个锁,让多个线程可以进行读操作
public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();
}
  • ReentrantReadWriteLock是接口ReadWriteLock的实现类,该类中包括两个内部类ReadLock,WriteLock,这两个内部类实现了Lock接口

代码示例:

package com.zhang.thread.syn3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLock rwl = new ReentrantReadWriteLock();//非公平,可重入,分为读锁和写锁
        Lock readLock = rwl.readLock();
        Lock readLock1 = rwl.readLock();
        Lock writeLock = rwl.writeLock();
        Lock writeLock1 = rwl.writeLock();
        
        System.out.println(readLock == readLock1);//true 同一把锁
        System.out.println(writeLock == writeLock1);//true 同一把锁

        readLock.lock();//对读锁上锁
        readLock.unlock();//对读锁解锁
        
        writeLock.lock();//对写锁上锁
        writeLock.unlock();//对写锁解锁
        //结论:读读共享,读写互斥,写写互斥

    }
}

结论:从ReadWriteLock中多次获取的readLock和writeLock是同一把读锁,同一把写锁

代码示例2:

package com.zhang.thread.syn3;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class TestReadWriteLock {
    ReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        lock.readLock().lock();
        System.out.println("==========读锁开始=========" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("==========读锁结束=========" + Thread.currentThread().getName());
        lock.readLock().unlock();
    }

    public void write() {
        lock.writeLock().lock();
        System.out.println("==========写锁开始=========" + Thread.currentThread().getName());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("==========写锁结束=========" + Thread.currentThread().getName());
        lock.writeLock().unlock();

    }

    public static void main(String[] args) {
        TestReadWriteLock rw = new TestReadWriteLock();
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    rw.read();
                }
            }).start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread(() -> rw.write()).start();
        }
    }
}

运行结果:

==========读锁开始=========Thread-2
==========读锁开始=========Thread-4
==========读锁开始=========Thread-3
==========读锁开始=========Thread-1
==========读锁开始=========Thread-0
==========读锁结束=========Thread-0
==========读锁结束=========Thread-2
==========读锁结束=========Thread-3
==========读锁结束=========Thread-1
==========读锁结束=========Thread-4
==========写锁开始=========Thread-5
==========写锁结束=========Thread-5
==========写锁开始=========Thread-6
==========写锁结束=========Thread-6
==========写锁开始=========Thread-7
==========写锁结束=========Thread-7
==========写锁开始=========Thread-8
==========写锁结束=========Thread-8
==========写锁开始=========Thread-9
==========写锁结束=========Thread-9

Process finished with exit code 0

3.6Lock锁和同步锁的区别

  • synchronized 在jdk1.6之前是重量级锁,1.6的时候引入了:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。状态会随竞争情况逐渐升级

3.7volatile:保证多个线程之间对一个变量值的可见性

  • volatile boolean flag=true;//volatile:保证多个线程之间对一个变量值的可见性,作为类的成员变量定义
    
    System.out.println();//底层被synchronized修饰,可以确保线程间的可见性
    
    public void println(boolean x) {
            synchronized (this) {
                print(x);
                newLine();
            }
        }
    
  • 线程安全三要素:

    • 可见性(多个线程之间对一个变量值的可见性)
    • 原子性(不可分割)
    • 有序性(禁止指令重排)
  • synchronized:可见性,原子性,有序性,可以实现线程安全

  • volatile:可见性,有序性

  • CAS:保证了原子性,和volatile相结合保证线程安全(Lock底层就是这种方式)

3.8CAS和ABA问题

CAS:Compare And Swap/Set,比较并交换,比较并修改。实现原子性要靠底层的操作,有个类:UnSafe

ABA问题:I=5,A线程想修改为6,修改前需判断I是否改变,即使再次读的I是5,也不代表值没变过,可能是B线程将5改为7,C线程又将7该为5

四、线程通信

线程通信建立在线程同步的基础上。

生产者和消费者问题:仅靠线程同步无法解决

  • 上面的方法都是Object中的
  • 都只能在同步方法或同步代码块中使用,否则会抛异常

3.1线程通信准备工作

(一)

商品类:

package com.zhang.thread.comm0;

public class Product {
    private String name;
    private String color;

    public Product(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Product() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

生产者:

package com.zhang.thread.comm0;

public class ProductRunnable implements Runnable {
    Product product = new Product();

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                product.setName("馒头");
                product.setColor("白色");
            } else {
                product.setName("玉米饼");
                product.setColor("黄色");
            }
            System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
            i++;
        }
    }
}

消费者:

package com.zhang.thread.comm0;

public class ConsumeRunnable implements Runnable {
    Product product = new Product();

    @Override
    public void run() {
        while (true) {
            System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
        }
    }
}

测试类:

package com.zhang.thread.comm0;

public class Test {
    public static void main(String[] args) {
        //创建生产者和消费者线程并启动
        Runnable runnable1 = new ProductRunnable();
        Thread thread1 = new Thread(runnable1);
        thread1.start();

        Runnable runnable2 = new ConsumeRunnable();
        Thread thread2 = new Thread(runnable2);
        thread2.start();
    }
}

运行结果发现问题1:(发现即使生产者生产了商品,消费者也购买null,因为生产者new了一个对象,消费者new了另外一个对象)

生产者生产商品:玉米饼 黄色
生产者生产商品:馒头 白色
生产者生产商品:玉米饼 黄色
生产者生产商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:null null
消费者购买商品:null null
消费者购买商品:null null
消费者购买商品:null null

(二)解决问题1:

消费者:

package com.zhang.thread.comm1;

public class ConsumeRunnable implements Runnable {
    Product product;

    public ConsumeRunnable() {
    }

    public ConsumeRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
        }
    }
}

生产者:

package com.zhang.thread.comm1;

public class ProductRunnable implements Runnable {
    Product product;

    public ProductRunnable() {
    }

    public ProductRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                product.setName("馒头");
                product.setColor("白色");
            } else {
                product.setName("玉米饼");
                product.setColor("黄色");
            }
            System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
            i++;
        }
    }
}

测试类:

package com.zhang.thread.comm1;

public class Test {
    public static void main(String[] args) {
        Product product = new Product();
        //创建生产者和消费者线程并启动
        Runnable runnable1 = new ProductRunnable(product);
        Thread thread1 = new Thread(runnable1);
        thread1.start();

        Runnable runnable2 = new ConsumeRunnable(product);
        Thread thread2 = new Thread(runnable2);
        thread2.start();
    }
}

运行结果正确。

(三)在生产过程中加 Thread.sleep()

package com.zhang.thread.comm1;

public class ProductRunnable implements Runnable {
    Product product;

    public ProductRunnable() {
    }

    public ProductRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                product.setName("馒头");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setColor("白色");
            } else {
                product.setName("玉米饼");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                product.setColor("黄色");
            }
            System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
            i++;
        }
    }
}

运行结果发现问题2(错误,颜色错误,原因:没有线程同步,生产商品没有生产完,消费者开始消费):

运行结果发现问题3(供不应求,功过于求,原因:生产者和消费者没有进行线程通信):

消费者购买商品:玉米饼 白色
消费者购买商品:玉米饼 白色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
消费者购买商品:馒头 黄色
(四)同步代码块中实现线程通信

解决问题2:对生产者和消费者加同把锁synchronized

必须对生产者和消费者同时加锁且加同把锁,否则还会出现黄色的馒头和白色的玉米饼

生产者:

package com.zhang.thread.comm1;

public class ProductRunnable implements Runnable {
    Product product;

    public ProductRunnable() {
    }

    public ProductRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            synchronized (product) {
                if (i % 2 == 0) {
                    product.setName("馒头");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("白色");
                } else {
                    product.setName("玉米饼");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("黄色");
                }
                System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
                i++;
            }

        }
    }
}

消费者:

package com.zhang.thread.comm1;

public class ConsumeRunnable implements Runnable {
    Product product;

    public ConsumeRunnable() {
    }

    public ConsumeRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (product) {
                System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
            }
        }
    }
}

运行结果正常。

(五)解决问题3(供不应求,功过于求,原因:生产者和消费者没有进行线程通信):

商品:( private boolean flag = false;//仓库不满)

package com.zhang.thread.comm2;

public class Product {
    private String name;
    private String color;
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Product(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Product() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }
}

消费者:

package com.zhang.thread.comm2;

public class ConsumeRunnable implements Runnable {
    Product product;

    public ConsumeRunnable() {
    }

    public ConsumeRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (product) {
                //仓库是否已满,不满则等待
                while (!product.isFlag()) {
                    try {
                        product.wait();//wait会释放锁和CPU
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("消费者购买商品:" + product.getName() + " " + product.getColor());
                product.setFlag(false);//更新仓库为空
                product.notifyAll();//通知生产者生产
            }
        }
    }
}

生产者

package com.zhang.thread.comm2;

public class ProductRunnable implements Runnable {
    Product product;

    public ProductRunnable() {
    }

    public ProductRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            synchronized (product) {
                //仓库是否满,满则等待
                while (product.isFlag()) {
                    try {
                        product.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (i % 2 == 0) {
                    product.setName("馒头");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("白色");
                } else {
                    product.setName("玉米饼");
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setColor("黄色");
                }
                System.out.println("生产者生产商品:" + product.getName() + " " + product.getColor());
                i++;
                product.setFlag(true);//更新仓库已满
                product.notifyAll();//通知消费者仓库已满
            }

        }
    }
}

总结:
  1. 进行线程通信的多个线程要使用同一个锁对象,还必须调用锁对象的wait(),notify(),notifyAll()

  2. 线程通信的三个方法:

    • wait():在调用notify(),notifyAll()之前,一直等待
    • wait(time):在调用notify(),notifyAll()之前,或在指定时间之前,一直等待
    • notify():唤醒在锁对象上等待的单个线程,唤醒的线程是随机的
    • notifyAll():唤醒在锁对象上等待的所有线程,线程将进行竞争得到CPU
  3. 完整的线程生命周期

    • 阻塞有三种状态:
    • 普通阻塞:sleep(),join(),等待键盘输入
    • 同步阻塞:(锁池队列,锁池状态),没有获取到锁对象的线程的队列
    • 等待阻塞:(等待队列),调用wait()后释放锁,进入该队列

    锁池状态:拿不到对象锁的在锁池状态等待

    等待队列:调用wait()后在等待队列等待,直到调用了notify(),notifyAll(),wait时间到后,到锁池状态等待

    只有拿到了锁,才能进入运行状态

  4. sleep()和wait()的区别?

    • sleep()会让出CPU,进入普通阻塞状态,不释放锁;wait()会让出CPU进入等待阻塞队列,释放锁
    • wait()只能在同步方法和同步代码块中使用,sleep()可以在任何地方使用

3.2 同步方法中实现线程通信

商品:

package com.zhang.thread.comm3;

public class Product {
    private String name;
    private String color;
    private boolean flag = false;//不满

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Product(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Product() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    public synchronized void product(String name, String color) {
        //仓库是否满,满则等待
        while (this.isFlag()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        this.color = color;
        // System.out.println("生产者生产商品:" + this.getName() + " " + this.getColor());
        System.out.println("生产者生产商品:" + name + " " + color);
        this.setFlag(true);//更新仓库已满
        this.notifyAll();//通知消费者仓库已满
    }

    public synchronized void consume() {
        //仓库是否已满,不满则等待
        while (!this.isFlag()) {
            try {
                this.wait();//wait会释放锁和CPU
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者购买商品:" + name + " " + color);
        this.setFlag(false);//更新仓库为空
        this.notifyAll();//通知生产者生产
    }
}

生产者:

package com.zhang.thread.comm3;

public class ProductRunnable implements Runnable {
    Product product;

    public ProductRunnable() {
    }

    public ProductRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            if (i % 2 == 0) {
                product.product("馒头", "白色");
            } else {
                product.product("玉米饼", "黄色");

            }
            i++;


        }
    }
}

消费者:

package com.zhang.thread.comm3;

public class ConsumeRunnable implements Runnable {
    Product product;

    public ConsumeRunnable() {
    }

    public ConsumeRunnable(Product product) {
        this.product = product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }

    @Override
    public void run() {
        while (true) {
            product.consume();
        }
    }
}

测试类:

package com.zhang.thread.comm3;

public class Test {
    public static void main(String[] args) {
        Product product = new Product();
        //创建生产者和消费者线程并启动
        Runnable runnable1 = new ProductRunnable(product);
        Thread thread1 = new Thread(runnable1);
        thread1.start();

        Runnable runnable2 = new ConsumeRunnable(product);
        Thread thread2 = new Thread(runnable2);
        thread2.start();
    }
}

运行结果:

生产者生产商品:馒头 白色
消费者购买商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:玉米饼 黄色
生产者生产商品:馒头 白色
消费者购买商品:馒头 白色
生产者生产商品:玉米饼 黄色
消费者购买商品:玉米饼 黄色
Process finished with exit code -1

总结:
  1. 同步方法的同步监视器都是this,所以需要将consume()和product()放入同一个类Product中,确保是同一把锁
  2. 必须调用this的wait(),notify(),notifyAll(),this可省略

3.3Lock锁实现线程通信

通过synchronized通信时,生产者和消费者在一个等待队列,无法精确的唤醒生产者或消费者,而Lock锁可以实现

package com.zhang.thread.comm4;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Product {
    private String name;
    private String color;
    private boolean flag = false;//不满
    Lock lock = new ReentrantLock();//lock锁
    Condition productCondition = lock.newCondition();//生产者队列
    Condition consumeCondition = lock.newCondition();//消费者队列

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Product(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Product() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

    public void product(String name, String color) {
        lock.lock();
        try {
            //仓库是否满,满则等待
            while (this.isFlag()) {
                try {
                    productCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            this.color = color;
            // System.out.println("生产者生产商品:" + this.getName() + " " + this.getColor());
            System.out.println("生产者生产商品:" + name + " " + color);
            this.setFlag(true);//更新仓库已满
            consumeCondition.signalAll();//通知消费者仓库已满
        } finally {
            lock.unlock();
        }


    }

    public void consume() {
        lock.lock();
        try {
            //仓库是否已满,不满则等待
            while (!this.isFlag()) {
                try {
                    consumeCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者购买商品:" + name + " " + color);
            this.setFlag(false);//更新仓库为空
            productCondition.signalAll();//通知生产者生产
        } finally {
            lock.unlock();

        }

    }
}

3.4Condition

  1. Condition是在1.5中出现了,用来代替Object中的wait(),notify(),notifyAll()
  2. Condition中的await(),signalAll(),signal()比wait(),notify(),notifyAll()能更安全高效的实现线程间的通信
  3. 同一个锁可以创建多个Condition。一个Condition包含一个等待队列,一个Lock可以创建多个Condition,所以可以有多个等待队列
  4. 在Object监视器上可以有一个同步队列和一个等待队列,而Lock拥有一个同步队列和多个等待队列
  5. 调用的Condition中的await(),signalAll(),signal()必须在lock.lock()和lock.unlock()之间
  6. Condition中的await(),对应Object中的wait()
  7. Condition中的signal()对应Object中的notify()
  8. Condition中的signalAll()对应Object中的notifyAll()
 Condition productCondition = lock.newCondition();//生产者队列
 Condition consumeCondition = lock.newCondition();//消费者队列

五、线程池

5.1什么是线程池

为什么要用线程池:创建对象(需分配内存)和销毁对象是非常消耗时间的,在并发情况下的线程,对性能影响大。所以可以创建多个线程放到线程池中,使用时直接获取引用,不用时放回池子,实现重复利用。

线程池的好处:

  • (1)减少了创建线程的时间,提高了响应速度;

  • (2)重复利用线程池中的线程,降低了资源消耗;

  • (3)提高线程的可管理性,避免无限制的创建线程

线程池的应用场合:

  • 需要大量线程,且完成任务时间短
  • 对性能要求苛刻的
  • 接受突发性的大量请求

创建对象(需分配内存)和销毁对象是非常消耗时间的,在并发情况下的线程,对性能影响大。所以可以创建多个线程放到线程池中,使用时直接获取引用,不用时放回池子,实现重复利用。

jdk1.5提供了内置的线程池:

使用者提交任务后,要看核心线程池是否已满,没满则创建,满了则看等待的队列是否已满,队列没满将任务存在队列里排队,队列也满了看整个线程池是否已满,不满则创建线程执行任务,满了按照策略处理无法执行的任务

5.2使用线程池执行大量的Runnable命令

package com.zhang.thread.pool.pool1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestRunnablePool {
    public static void main(String[] args) {
        //创建线程
        //线程池只有一个线程,保证一定有一个
        ExecutorService pool = Executors.newSingleThreadExecutor();
        //线程池有固定数量的线程
        //ExecutorService pool = Executors.newFixedThreadPool(10);
        //线程池有可变数量的线程
        //ExecutorService pool=Executors.newCachedThreadPool();
        //用来间隔执行(半小时执行一次)或延迟执行(12:00执行)
        //ExecutorService pool = Executors.newScheduledThreadPool(10);

        //使用线程池执行大量的Runnable命令

        for (int i = 0; i < 100; i++) {
            final int n = i;
            pool.execute(() -> {
                System.out.println("线程开始----------" + n);
                System.out.println("线程结束----------" + n);
            });
        }


        //关闭线程
        pool.shutdown();
    }


}

5.3使用线程池执行大量的Callable命令(有返回值)

注意:必须先将任务全部添加到队列,然后再获取结果

package com.zhang.thread.pool.pool1;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;

public class TestRunnablePool2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建线程
        //线程池只有一个线程,保证一定有一个
        //ExecutorService pool = Executors.newSingleThreadExecutor();
        //线程池有固定数量的线程
        ExecutorService pool = Executors.newFixedThreadPool(10);
        //线程池有可变数量的线程
        //ExecutorService pool=Executors.newCachedThreadPool();
        //用来间隔执行(半小时执行一次)或延迟执行(12:00执行)
        //ExecutorService pool = Executors.newScheduledThreadPool(10);

        //使用线程池执行大量的Callable命令
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                Thread.sleep(2000);
                return new Random().nextInt(10);
            }
        };
        List<Future> list = new ArrayList<>();
        for (int i = 0; i < 200; i++) {
            Future<Integer> submit = pool.submit(callable);
            list.add(submit);//必须先将任务添加到队列里
        }
        for (int i = 0; i < 200; i++) {
            System.out.println(list.get(i).get());
        }


        //关闭线程
        pool.shutdown();
    }


}

5.4线程池的API

  • Executor:线程池顶级接口,只有一个方法

    public interface Executor {
        void execute(Runnable command);
    }
    
    
  • ExecutorService:真正的线程池接口

    public interface ExecutorService extends Executor {
        void execute(Runnable command);//执行任务,没有返回值,一般用来执行Runnable
        Future<?> submit(Runnable task);//执行任务,有返回值,一般用来执行Callable
        void shutdown();//关闭线程池
    }
    
  • AbstractExecutorService:基本实现了ExecutorService的所有方法

    public abstract class AbstractExecutorService implements ExecutorService {}
    
  • ThreadPoolExecutor:默认的线程池实现类

    public class ThreadPoolExecutor extends AbstractExecutorService {}
    
    • ThreadPoolExecutor的七个参数

      int corePoolSize//核心池的大小
      int maximumPoolSize//最大线程数
      long keepAliveTime//线程没有任务时存活的时间(非核心池线程)
      TimeUnit unit,//keepAliveTime存活单位
      BlockingQueue<Runnable> workQueue//存储等待执行任务的阻塞队列(可是顺序队列,链式队列等)
      ThreadFactory threadFactory,//线程工厂,Executors的静态内部类
      RejectedExecutionHandler handler//拒绝处理任务的四种策略
          //AbortPolicy:默认抛出异常
          //DiscardPolicy:什么也不做
          //DiscardOldestPolicy:将在线程池等待队列中的第一个抛弃,当前的放进去
          //CallerRunsPolicy:如果线程还在运行,直接运行这个线程
      
  • ScheduledThreadPoolExecutor:实现周期性任务调度的线程池

    public class ScheduledThreadPoolExecutor
            extends ThreadPoolExecutor
            implements ScheduledExecutorService {}
    
    public interface ScheduledExecutorService extends ExecutorService {
    }
    
  • Executors:线程池的工具类,线程池的工厂类

    //线程池只有一个线程,保证一定有一个
    Executors.newSingleThreadExecutor();
    //线程池有固定数量的线程
    Executors.newFixedThreadPool(10);
    //线程池有可变数量的线程
    Executors.newCachedThreadPool();
    //用来间隔执行(半小时执行一次)或延迟执行(12:00执行)
    Executors.newScheduledThreadPool(10);
    

5.5ForkJoin框架

java7提供的框架,把一个大任务分解为若干小任务,最终汇总每个小任务的结果,得到大任务的结果的框架

ForkJoinPool和ThreadPoolExecutor是兄弟关系,都继承了AbstractExecutorService

public class ForkJoinPool extends AbstractExecutorService {
}
  • ThreadPoolExecutor多个线程共用一个队列;而ForkJoinPool每个线程有自己的队列,当自己队列中的任务完成后,从其他线程的任务队列(尾部)偷任务执行,充分利用资源(工作窃取算法)

  • 工作窃取算法:

    优点:利用线程进行并行计算,减少了线程间的竞争

    缺点:双端队列只有一个任务时,线程间会竞争;会消耗更多资源,如会创建多个线程和多个双端队列

六、用多线程实现火车站售票

1.方法一:synchronized()同步块
package com.zhang.thread.home;

/**
 * 火车站四个窗口,共卖100张票
 * 分析:四个线程共享100张票,实现Runnable接口
 */
public class TestTicket {

    public static void main(String[] args) {
        Runnable myRunnable1 = new MyRunnable1();
        new Thread(myRunnable1, "窗口1").start();
        new Thread(myRunnable1, "窗口2").start();
        new Thread(myRunnable1, "窗口3").start();
        new Thread(myRunnable1, "窗口4").start();
    }

}

class MyRunnable1 implements Runnable {
    int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticketNum <= 0) {
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
                ticketNum--;
            }

        }
    }
}


2.方法二:synchronized 同步方法
package com.zhang.thread.home;

/**
 * 火车站四个窗口,共卖100张票
 * 分析:四个线程共享100张票,实现Runnable接口
 */
public class TestTicket2 {

    public static void main(String[] args) {
        Runnable myRunnable1 = new MyRunnable2();
        new Thread(myRunnable1, "窗口1").start();
        new Thread(myRunnable1, "窗口2").start();
        new Thread(myRunnable1, "窗口3").start();
        new Thread(myRunnable1, "窗口4").start();
    }

}

class MyRunnable2 implements Runnable {
    int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                break;
            }
            sellTicket();
        }
    }

    public synchronized void sellTicket() {
        if (ticketNum <= 0) {
            return;
        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
        ticketNum--;
    }

}

3.方法三:Lock锁
package com.zhang.thread.home;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 火车站四个窗口,共卖100张票
 * 分析:四个线程共享100张票,实现Runnable接口
 */
public class TestTicket3 {

    public static void main(String[] args) {
        Runnable myRunnable1 = new MyRunnable3();
        new Thread(myRunnable1, "窗口1").start();
        new Thread(myRunnable1, "窗口2").start();
        new Thread(myRunnable1, "窗口3").start();
        new Thread(myRunnable1, "窗口4").start();
    }

}

class MyRunnable3 implements Runnable {
    int ticketNum = 100;
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (ticketNum <= 0) {
                    break;
                }

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖出了第" + ticketNum + "张票");
                ticketNum--;
            } finally {
                lock.unlock();

            }

        }
    }


}

posted @ 2021-08-23 16:57  wlbsm  阅读(39)  评论(0编辑  收藏  举报