多线程基础回顾

1. 线程的三种创建方式

程序(program):是为完成特定任务、用某种语言编写的一组指令的集合,是一段静态的代码。 (程序是静态的)

进程(process):是程序的一次执行过程。正在运行的一个程序,进程作为资源分配的单位,在内存中会为每个进程分配不同的内存区域。 (进程是动态的)是一个动的过程 ,进程的生命周期 : 有它自身的产生、存在和消亡的过程

线程(thread),进程可进一步细化为线程, 是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程,就是支持多线程的。

1.1 通过继承Thread类

public class TestThread extends Thread {
    public TestThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.getName() + i);
        }
    }
}

1.2 通过实现Runnable接口

public class TestThread implement Runnable {
    public TestThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(this.getName() + i);
        }
    }
}

1.3 实现Callable接口

public class TestRundomNum implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(10);//返回一个10以内的随机数
    }
}
class Test{
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建子线程
        TestRundomNum trn = new TestRundomNum();
        FutureTask<Integer> ft = new FutureTask<>(trn);
        Thread t = new Thread(ft);
        t.start();
        Integer integer = ft.get();

        System.out.println(integer);

    }
}

对比第一种和第二种创建线程的方式发现,无论第一种继承Thread类的方式还是第二种实现Runnable接口的方式,都需要有一个run方法,
但是这个run方法有不足:

(1)没有返回值
(2)不能抛出异常

基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现Callable接口:

实现Callable接口好处:(1)有返回值 (2)能抛出异常
缺点:线程创建比较麻烦

2. 线程的声明周期

3. 线程常用方法

  1. start() : 启动当前线程,表面上调用start方法,实际在调用线程里面的run方法
  2. run() : 线程类 继承 Thread类 或者 实现Runnable接口的时候,都要重新实现这个run方法,run方法里面是线程要执行的内容
  3. currentThread :Thread类中一个静态方法:获取当前正在执行的线程
  4. setName 设置线程名字
  5. getName 读取线程名字
  6. setPriority设置优先级,同优先级别的线程,采取的策略就是先到先服务,使用时间片策略,如果优先级别高,被CPU调度的概率就高,级别:1-10 默认的级别为5。
  7. join方法:当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程。
    注意:必须先start,再join才有效。
  8. sleep,线程睡眠
  9. setDeamon将线程设置为守护线程

4. 线程安全问题

买火车票程序:

//买火车票线程
public class BuyTicketThread extends Thread{
    public BuyTicketThread(String name){
        super(name);
    }
    static int ticketNum = 10;

    @Override
    public void run() {
        for(int i = 1; i <= 100; i++){
            if(ticketNum > 0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
        }
    }
}
public class Test {
    public static void main(String[] args) {
        BuyTicketThread t1 = new BuyTicketThread("窗口1");
        BuyTicketThread t2 = new BuyTicketThread("窗口2");
        BuyTicketThread t3 = new BuyTicketThread("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

存在问题,相同编号票或者编号为负数的票

4.1同步代码块

public class BuyTicketThread extends Thread{
    public BuyTicketThread(String name){
        super(name);
    }
    static int ticketNum = 10;

    @Override
    public void run() {
        for(int i = 1; i <= 100; i++){
            //同步代码块加锁
            synchronized(BuyTicketThread.class){
                if(ticketNum > 0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
            }
        }
    }
}

4.2同步方法

public class BuyTicketThread extends Thread{
    public BuyTicketThread(String name){
        super(name);
    }
    static int ticketNum = 10;

    @Override
    public void run() {
        for(int i = 1; i <= 100; i++){
            buyTicket();
        }
    }

    public static synchronized void buyTicket(){
        if(ticketNum > 0){
            System.out.println("我在"+Thread.currentThread().getName()+"买到了从武汉到上海的第" + ticketNum-- + "火车票");
        }
    }
}

4.3 Lock锁

JDK1.5后新增新一代的线程同步方式:Lock锁
与采用synchronized相比,lock可提供多种锁方案,更灵活synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。

public class BuyTicketThread implements Runnable{
    Lock lock = new ReentrantLock();//获得一把锁
    int ticketNum = 10;
    @Override
    public void run() {
        for(int i = 1; i <= 100; i++){
            lock.lock();
            try{
                if(ticketNum > 0){
                    Thread.sleep(200);
                    System.out.println("我在"+Thread.currentThread().getName() + "买到了第" + ticketNum-- + "张火车牌");
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();//释放锁
            }
        }
    }

}

Lock和synchronized的区别

    1.Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
    2.Lock只有代码块锁,synchronized有代码块锁和方法锁
    3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先使用顺序:

    Lock----同步代码块(已经进入了方法体,分配了相应资源)----同步方法(在方法体之外)

线程安全,效率低
线程不安全,效率高

可能造成死锁:
死锁

不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

5.线程通信

应用场景:生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

5.1同步代码块

//产品类
public class Product {
    private String brand;//品牌
    private String name;//名字

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
//生产者
public class ProducerThread extends Thread{
    private Product product;

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

    @Override
    public void run() {
        for(int i = 1; i <= 10; i++){
            synchronized (product){
                if(i%2 == 0){
                    //生成小米手机
                    product.setBrand("小米");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setName("手机");
                }else{
                    //生成华为平板
                    product.setBrand("华为");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    product.setName("平板");
                }
                //输出信息
                System.out.println("生产者生产了一个" + product.getBrand() + "的" + product.getName());
            }
        }
    }
}
//消费者线程
public class CustomerThread extends Thread{
    private Product product;

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

    @Override
    public void run() {
        //消费十个产品
        for(int i = 0; i <= 10; i++){
            synchronized(product){
                System.out.println("消费者消费了" + product.getBrand() + "的" + product.getName());
            }
        }
    }
}
//测试
Product p = new Product();
ProducerThread pt = new ProducerThread(p);
CustomerThread ct = new CustomerThread(p);
pt.start();
ct.start();

5.2同步方法

public class Product {
    private String brand;//品牌
    private String name;//名字
    //true表示有商品,false表示没有商品
    private boolean flag = false;//默认没有商品

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //生成产品
    public synchronized void setProduct(String brand,String name){
        if(flag){
           //有商品
            try {
                wait();//进行等待池等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.setBrand(brand);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了一个" + this.getBrand() + "的" + this.getName());
        flag = true;
        notify();//唤醒消费者线程消费
    }

    public synchronized void getProduct(){
        if(!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者消费了" + this.getBrand() + "的" + this.getName());
        flag = false;
        notify();//唤醒生产者线程生产
    }
}
//消费者线程
public class CustomerThread extends Thread{
    private Product product;

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

    @Override
    public void run() {
        //消费十个产品
        for(int i = 1; i <= 10; i++){

            product.getProduct();

        }
    }
}
//生产者
public class ProducerThread extends Thread{
    private Product product;

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

    @Override
    public void run() {
        for(int i = 1; i <= 10; i++){
            if(i % 2 == 0){
                //生成小米手机
                product.setProduct("小米","手机");
            }else{
                //生成华为平板
                product.setProduct("华为","平板");
            }
        }
    }
}

Product p = new Product();
ProducerThread pt = new ProducerThread(p);
CustomerThread ct = new CustomerThread(p);
pt.start();
ct.start();

5.3 Lock锁通信

public class Product {
    private String brand;
    private String name;
    //表示是否有商品,true表示生产者已经生产了商品,false表示消费者已经消费者商品
    private boolean flag = false;
    Lock lock = new ReentrantLock();//锁机制
    //生产者等待队列
    Condition produceCondition = lock.newCondition();
    //消费者等待队列
    Condition customerCondition = lock.newCondition();

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

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

    public void setProduct(String brand,String name){
        lock.lock();//获取锁
        try{
            if(flag){
                //生产者进入等待队列
                try {
                    produceCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //生产产品
            this.setBrand(brand);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setName(name);
            System.out.println("生产者生产了" + this.getBrand() + "的" + this.getName());
            flag = true;
            customerCondition.signal();//唤醒消费者队列中等待线程消费
        }finally {
            lock.unlock();//释放锁
        }
    }

    public void getProduct(){
        lock.lock();//上锁
        try{
            if(!flag){
                try {
                    customerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("消费者消费了" + this.getBrand() + "的" + this.getName());
            flag = false;
            produceCondition.signal();//唤醒生产者线程生产
        }finally {
            lock.unlock();//释放锁
        }
    }
}
public class CustomerThread extends Thread{
    private Product product;

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

    @Override
    public void run() {
        for(int i = 1; i <= 10; i++){
            product.getProduct();
        }
    }
}

public class ProduceThread extends Thread {
    private Product product;
    public  ProduceThread(Product product){
        this.product = product;
    }

    @Override
    public void run() {
        for(int i = 1; i <= 10; i++){
            if(i % 2 == 0){
                product.setProduct("小米","手机");
            }else{
                product.setProduct("华为","笔记本");
            }
        }
    }
}

Product p = new Product();
ProduceThread pd = new ProduceThread(p);
CustomerThread ct = new CustomerThread(p);

pd.start();
ct.start();

Condition是在Java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。

它的更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition

一个Condition包含一个等待队列。一个Lock可以产生多个Condition,所以可以有多个等待队列。

在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而Lock(同步器)拥有一个同步队列和多个等待队列。

Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

调用Condition的await()、signal()、signalAll()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用

  • Conditon中的await()对应Object的wait();

  • Condition中的signal()对应Object的notify();

  • Condition中的signalAll()对应Object的notifyAll()。

void await() throws InterruptedException

造成当前线程在接到信号或被中断之前一直处于等待状态。

与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,且在发生以下四种情况之一 以前,当前线程将一直处于休眠状态:

  • 其他某个线程调用此 Condition 的 signal() 方法,并且碰巧将当前线程选为被唤醒的线程;或者
  • 其他某个线程调用此 Condition 的 signalAll() 方法;或者
  • 其他某个线程中断当前线程,且支持中断线程的挂起;或者
  • 发生“虚假唤醒”

在所有情况下,在此方法可以返回当前线程之前,都必须重新获取与此条件有关的锁。在线程返回时,可以保证它保持此锁。

void signal()

唤醒一个等待线程。

如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。

void signalAll()

唤醒所有等待线程。

如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。

posted @ 2022-03-27 20:40  无涯子wyz  阅读(39)  评论(0)    收藏  举报