多线程入门学习

一 多线程的基本概念

1.1 什么是多线程

多线程技术是指在一个程序中同时运行多个独立的执行流,这些执行流被称为线程。

1.2 多线程的应用场景

异步处理任务
分布式计算
提升接口响应速度

1.3 进程与线程

程序是一段静态代码,进程是正在运行程序的抽象,在进程内部可能需要同时执行一些子任务,也就是线程;
在Java程序至少有JVM GC垃圾回收后台线程和main主线程这两个线程;
进程时系统进行资源分配的基本单位,线程是系统调用的基本单位;

1.4 并发与并行

并发是指在同一时刻多个指令在单个cpu上交替执行;
并行是指在同一时刻多个指令在多个cpu上同时执行;

二 线程任务的实现方式

2.1 继承Thread类

public class ThreadOne extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() 
                + "继承Thread类的任务... ...");
    }
}

public class ThreadExperiment {
    public static void main(String[] args) {
        ThreadOne thread1 = new ThreadOne();
        ThreadOne thread2 = new ThreadOne();
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
        // 线程1继承Thread类的任务... ...
        // 线程2继承Thread类的任务... ...
    }
}

2.2 实现Runnable接口

public class RunnableOne implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()
                + "实现Runnable接口的任务... ...");
    }
}
public class ThreadExperiment {
    public static void main(String[] args) {
        RunnableOne runnable = new RunnableOne();
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
        // 线程1实现Runnable接口的任务... ...
        // 线程2实现Runnable接口的任务... ...
    }
}

2.3 实现Callable接口

public class CallableOne implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()
                + "实现Callable接口的任务... ...");
        return "任务完成";
    }
}
public class ThreadExperiment {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableOne callable = new CallableOne();
        FutureTask<String> futureTask1 = new FutureTask<>(callable);
        FutureTask<String> futureTask2 = new FutureTask<>(callable);
        Thread thread1 = new Thread(futureTask1);
        Thread thread2 = new Thread(futureTask2);
        thread1.setName("线程1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
        String s1 = futureTask1.get();
        String s2 = futureTask1.get();
        System.out.println("线程1执行的结果是: " + s1);
        System.out.println("线程2执行的结果是: " + s2);
        // 线程2实现Callable接口的任务... ...
        // 线程1实现Callable接口的任务... ...
        // 线程1执行的结果是: 任务完成
        // 线程2执行的结果是: 任务完成
    }
}

Thread类的构造方法参数可以接收的任务类型是Runnable接口,不可以直接是Callable接口;
FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable和Future接口,所以可以将FutureTask对象作为Thread类的构造方法参数;
FutureTask类有个构造方法的参数是Callable接口,Callable接口通常是和FutureTask类配合使用;
FutureTask类实现了Runnable和Future接口,既可以作为线程任务,又可以作为异步执行的结果;
FutureTask类的get()方法是阻塞方法,直到线程任务执行完成,才会运行调用FutureTask任务的线程继续执行;

三 常用的API

3.1 构造方法

public Thread():创建线程对象的时候,线程名字默认是Thread-开头,Thread-0到Thread-n;
public Thread(String name):创建线程对象的时候,并给线程对象设置名字;
public Thread(Runnable target):创建线程对象的时候,指定一个线程任务;
public Thread(Runnable target, String name):创建线程对象的时候,执行一个线程任务,并设置线程名字;

public static void main(String[] args) {
    Thread thread1 = new Thread();
    Thread thread2 = new Thread();
    Thread thread3 = new Thread("normal");
    Thread thread4 = new Thread(() -> {
        System.out.println("执行线程任务");
    });
    Thread thread5 = new Thread(() -> {
        System.out.println("执行线程任务");
    }, "product");
    System.out.println("thread1线程的名字是: " + thread1.getName());
    System.out.println("thread2线程的名字是: " + thread2.getName());
    System.out.println("thread3线程的名字是: " + thread3.getName());
    System.out.println("thread4线程的名字是: " + thread4.getName());
    System.out.println("thread5线程的名字是: " + thread5.getName());
    // thread1线程的名字是: Thread-0
    // thread2线程的名字是: Thread-1
    // thread3线程的名字是: normal
    // thread4线程的名字是: Thread-2
    // thread5线程的名字是: product
}

3.2 线程名字方法

public final synchronized void setName(String name):设置线程的名字;
public final String getName():获取线程的名字;
public static native Thread currentThread():获取当前线程对象;

public static void main(String[] args) {
    Thread thread1 = new Thread();
    thread1.setName("product");
    String threadName = thread1.getName();
    System.out.println("线程名字是: " + threadName);
    System.out.println("当前线程名字是: " + Thread.currentThread().getName());
    // 线程名字是: product
    // 当前线程名字是: main
}

3.3 线程优先级方法

public final void setPriority(int newPriority):设置线程的优先级;
public final int getPriority():获取线程的优先级;
/**
  * The minimum priority that a thread can have.
  */
public final static int MIN_PRIORITY = 1;
/**
  * The default priority that is assigned to a thread.
  */
public final static int NORM_PRIORITY = 5;
/**
  * The maximum priority that a thread can have.
  */
public final static int MAX_PRIORITY = 10;
线程的优先级默认是5,最大可以设置为10,最小可以设置为1;
设置线程的优先级只是让线程有更多的机会获得时间片执行任务,并不是优先级高的线程一定先执行;

public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()
                + "开始执行任务...");
    }, "线程001");
    Thread thread2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName()
                + "开始执行任务...");
    }, "线程002");
    thread1.setPriority(1);
    System.out.println("线程001的优先级是: " + thread1.getPriority());
    System.out.println("线程002的优先级是: " + thread2.getPriority());
    thread1.start();
    thread2.start();
    // 线程001的优先级是: 1
    // 线程002的优先级是: 5
    // 线程001开始执行任务...
    // 线程002开始执行任务...
}

3.4 守护线程方法

public final boolean isDaemon():判断线程是不是守护线程;
public final void setDaemon(boolean on):true是守护线程,false是用户线程,默认是是用户线程;
Java线程分为两类,用户线程和守护线程:
守护线程是为用户线程服务的线程,当所有用户线程执行结束后,守护线程也会随之结束;
主线程默认是用户线程,在用户线程创建的线程默认是用户线程;
在守护线程创建的线程默认是守护线程;
线程类型可以通过setDaemon(true/false)方法修改,isDaemon()方法来判断线程的类型;

public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()
                    + "输出数据 " + i);
        }
    }, "线程001");
    Thread thread2 = new Thread(() -> {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()
                    + "输出数据 " + i);
        }
    }, "线程002");
    thread2.setDaemon(true);
    System.out.println("thread1是不是守护线程: " + thread1.isDaemon());
    System.out.println("thread2是不是守护线程: " + thread2.isDaemon());
    thread1.start();
    thread2.start();
}

线程1和线程2都是用户线程时,两个线程任务都会正常完成,当线程2设置为守护线程时,主线程和线程1的任务完成后,线程2也会立马结束,哪怕线程2的任务还没有完成

3.5 线程yield方法

public static native void yield():静态原生方法,当前线程让出所占用的cpu,从运行状态转为就绪状态,和其他线程重新抢夺cpu的使用权,使具有相同优先级的其他线程获得运行机会;
线程Thread的yield()方法,释放当前线程的cpu执行权,但是这个线程又可能再抢到cpu的执行权;

public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "任务准备...");
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "任务完成...");
    }, "线程001");
    Thread thread2 = new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + "任务准备...");
        System.out.println(Thread.currentThread().getName() + "任务完成...");
    }, "线程002");
    thread1.start();
    thread2.start();
}
//线程001任务准备...
//线程002任务准备...
//线程002任务完成...
//线程001任务完成...

3.6 线程join方法

public final void join():调用join()方法的线程在指定的线程对象里面插队执行;
public final synchronized void join(long millis):调用join(millis)方法的线程在指定的线程对象里面插队执行指定的时间;
public final synchronized void join(long millis, int nanos):调用join(millis, nanos)方法的线程在指定的线程对象里面插队执行指定的时间;

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread(() -> {
        try {
            // 模拟线程任务执行
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() 
                    + "线程任务开始执行### ###");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程001");
    Thread thread2 = new Thread(() -> {
        try {
            thread1.join();
            // 模拟线程任务执行
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() 
                    + "线程任务开始执行... ...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程002");
    thread1.start();
    thread2.start();
    thread2.join();
    // 模拟main线程任务执行
    Thread.sleep(2000);
    System.out.println(Thread.currentThread().getName() 
            + "线程任务开始执行*** ***");
    //线程001线程任务开始执行### ###
    //线程002线程任务开始执行... ...
    //main线程任务开始执行*** ***
}

调用join()方法的线程在所插入的线程之前执行,线程001任务执行完成,接着线程002任务执行完成,最后是main线程任务执行完成

public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread(() -> {
        try {
            // 模拟线程任务执行
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() 
                    + "线程任务开始执行### ###");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程001");
    Thread thread2 = new Thread(() -> {
        try {
            // 指定插队执行时间
            thread1.join(2000);
            // 模拟线程任务执行
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() 
                    + "线程任务开始执行... ...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "线程002");
    thread1.start();
    thread2.start();
    // 指定插队执行时间
    thread2.join(1000);
    // 模拟main线程任务执行
    Thread.sleep(2000);
    System.out.println(Thread.currentThread().getName() 
            + "线程任务开始执行*** ***");
    //main线程任务开始执行*** ***
    //线程001线程任务开始执行### ###
    //线程002线程任务开始执行... ...
}

线程插队执行指定插队时间,过了执行的时间,线程开始抢夺cpu执行任务

面试题:怎样保证线程的有序执行

3.7 线程sleep方法

public static native void sleep(long millis):暂停当前线程的执行,让线程休眠指定的时间,单位为毫秒;
public static void sleep(long millis, int nanos):暂停当前线程的执行,让线程休眠指定的时间,单位为毫秒加上纳秒;
线程执行了Thread.sleep()方法,线程就会由运行态进入阻塞态,当前线程暂停执行,当前线程当休眠时间到了之后,线程会由阻塞状态进入就绪态,等抢到cpu时间片会继续执行下面的代码

public static void main(String[] args) throws InterruptedException {
    System.out.println(Thread.currentThread().getName()
            + "线程开始执行任务...");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName()
            + "线程执行任务完成...");
}

sleep()和wait()方法的区别

四 线程的生命周期

线程的生命周期包括以下五个阶段:
新建状态(New):线程对象创建完成就处于新建状态,线程还没有开始运行;
就绪状态(Runnable):调用线程对象的start()方法后,线程进入就绪状态,此时线程拥有除了CPU时间片的所有资源,准备好执行线程任务,此时是有执行资格,没有执行权;
运行状态(Running):当线程获得CPU时间片时,线程进入运行状态,线程开始执行任务,此时是有执行资格,还有执行权;
阻塞状态(Blocked):当线程因为某些原因无法继续执行时,线程进入阻塞状态,此时是没有执行资格,也没有执行权;
销毁状态(Terminated):当线程完成了任务或者因为异常等原因退出时,线程进入终止状态,此时线程的生命周期结束,线程死亡,变成垃圾;
当一个线程进入就绪状态时,JAVA虚拟机会为其创建方法调用栈和程序计数器,线程的执行是由底层平台控制, 具有一定的随机性;
当一个线程进入运行状态时,一般的操作系统是采用抢占式的方式来让线程获得CPU,CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞、就绪之间切换;
当一个线程处于阻塞状态时,CPU会调度其他就绪状态的线程进入运行状态;
当一个线程进入销毁状态时,它的执行权会被收回,其他等待的线程可能会被唤醒并进入就绪状态,等待CPU的调度;

当一个线程由运行状态进入阻塞状态的原因:
    1) 等待I/O操作;
    2) 等待网络资源操作;
    3) 调用sleep()方法,需要等到sleep时间结束;
    4) 调用wait()方法,需要等待其他线程调用同一个锁对象的notify()唤醒方法;
    5) 其他线程的join()插队操作,需要等到其他线程执行完;
    6) 没有抢到锁对象;
如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源:
    1) run()/call()方法执行完成,线程正常结束;
    2) 线程抛出一个未捕获的Exception或Error;

五 线程安全

多个线程对同一共享资源的访问很可能会出现共享资源完整性的问题,这就需要使用锁来同步对共享资源的访问,而且多个线程使用的锁对象是唯一的,这样就可以保证多线程程序的正确性和一致性;

5.1 synchronized关键字

synchronized关键字是Java语言最基本的锁机制,在方法或代码块前添加synchronized关键字,可以保证同一时间只有一个线程能够执行该方法或代码块;
synchronized关键字使用起来简单方便,但它的粒度较大,是对整个方法或代码块加锁进行访问控制,synchronized锁是不可被中断的;

5.1.1 同步代码块

使用经典案例多个窗口同时售票演示

public class SellTicketRunnable implements Runnable{
    // 实现Runnable接口的任务,不需要使用static修饰共享资源
    // 实现Runnable接口的任务只会创建一个,并且作为参数让线程对象执行
    private int tickets = 100;
    @Override
    public void run() {
        while (true) {
            synchronized (SellTicketRunnable.class) {
                try {
                    if (tickets > 0) {
                        System.out.println(Thread.currentThread().getName()
                                + "线程正在售卖剩余的第" + tickets-- + "张票");
                        Thread.sleep(100);
                    } else {
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
}
public class ThreadMethod {
    public static void main(String[] args) {
        SellTicketRunnable sellTicketRunnable = new SellTicketRunnable();
        Thread thread1 = new Thread(sellTicketRunnable);
        Thread thread2 = new Thread(sellTicketRunnable);
        Thread thread3 = new Thread(sellTicketRunnable);
        thread1.setName("线程001...");
        thread2.setName("线程002...");
        thread3.setName("线程003...");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

synchronized同步代码块的锁对象synchronized(Object.class)锁住共享数据的相关代码,锁对象写在循环里面,把共享数据给锁起来,是同步操作,同步代码块外面的代码不需要同步;

synchronized同步代码块等于给这段代码上了一把锁,当一个线程抢到锁执行这段代码的时候,别的线程无法抢占cpu资源而处于阻塞状态,当前线程执行完同步代码时锁才会打开,这样解决了线程的安全问题,在操作同步代码时只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低;

5.1.2 同步方法

public class SellTicketThread extends Thread {
    // 共享资源使用static修饰,表示这个类的所有对象都共享static静态资源
    private static int tickets = 100;

    @Override
    public void run() {
        while (tickets > 0) {
            sellTicketMM();
        }
    }

    // 静态方法处理静态资源
    private static synchronized void sellTicketMM() {
        try {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName()
                        + "线程正在售卖剩余的第" + tickets-- + "张票");
                Thread.sleep(10);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadMethod {
    public static void main(String[] args) {
        SellTicketThread thread1 = new SellTicketThread();
        SellTicketThread thread2 = new SellTicketThread();
        SellTicketThread thread3 = new SellTicketThread();
        thread1.setName("线程001...");
        thread2.setName("线程002...");
        thread3.setName("线程003...");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

同步方法锁住方法里面的所有代码,同步方法里面的代码都是同步的,在生产中不要随意使用同步方法,会使得整个程序的运行效率变得非常低;

小结:
当一个线程执行了加锁的代码时,别的线程无法抢占cpu资源执行这段代码,只有当前线程执行完代码,锁才会打开,即只能有一个线程参与,其他线程等待;
任何类对象都可以作为锁,通常一个类会内置一个独立的变量作为锁对象,要保证多个线程使用到的锁是同一个,即锁对象要保证唯一性;
当前类对象this可以作为锁对象,当前类的字节码文件可以作为锁对象,当前类也可以当作锁对象,即类也是Class类的对象实例,万物皆对象;
Thread类的子类的run方法添加同步代码块,锁对象使用static修饰,共享资源使用static修饰,表示这个类的所有对象都共享static静态资源;
Thread类的子类的静态同步方法操作共享static静态资源,静态同步方法的锁对象是当前类的字节码文件;
Runnable接口的实现类的run方法添加同步代码块,锁对象可以是this,即Runnable接口的实现类对象,共享资源不需要使用static修饰;
Runnable接口的实现类的非静态同步方法,锁对象也是this,即Runnable接口的实现类对象;

5.2 Lock接口

ReentrantLock是Java.util.concurrent包中一个可重入锁实现类,实现了Lock接口提供的获取锁和释放锁的方法;
与synchronized关键字相比,ReentrantLock类提供了更多的灵活性和功能,可以使用lock()方法获取锁,使用tryLock()方法尝试获取锁,使用unlock()方法释放锁,其中的lock()方法是不可中断的,tryLock()方法是可以中断的,避免线程长时间等待;

5.2.1 lock()方法

public class SellTicketLockRunnable implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (tickets > 0) {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()
                            + "线程正在售卖剩余的第" + tickets-- + "张票");
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
public class SellTicketLockThread extends Thread {
    private static int tickets = 100;
    private static Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            lock.lock();
            try {
                if (tickets > 0) {
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()
                            + "线程正在售卖剩余的第" + tickets-- + "张票");
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
public class ThreadMethod {
    public static void main(String[] args) {
        // 实现Runnable接口
        SellTicketLockRunnable sellTicketLock = new SellTicketLockRunnable();
        new Thread(sellTicketLock, "线程001").start();
        new Thread(sellTicketLock, "线程002").start();
        new Thread(sellTicketLock, "线程003").start();
        // 继承Thread类
        SellTicketLockThread thread1 = new SellTicketLockThread();
        SellTicketLockThread thread2 = new SellTicketLockThread();
        SellTicketLockThread thread3 = new SellTicketLockThread();
        thread1.setName("线程one");
        thread2.setName("线程two");
        thread3.setName("线程three");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

5.2.2 tryLock()方法

public boolean tryLock():方法返回值类型是Boolean,成功获取锁立即返回true,获取锁失败立即返回false;
public boolean tryLock(long timeout, TimeUnit unit):方法限定了尝试获取锁的时间,如果在尝试时间范围内获取锁成功则返回true,如果在尝试时间范围内没有获取到锁则返回false;

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Runnable r = () -> {
        if (lock.tryLock()) {
            try {
                System.out.println(Thread.currentThread().getName()
                        + "抢到了锁, 开始执行任务, 当前时间是" + LocalDateTime.now());
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println(Thread.currentThread().getName()
                    + "没有抢到, 执行其他任务, 当前时间是" + LocalDateTime.now());
        }
    };
    new Thread(r, "线程001").start();
    new Thread(r, "线程002").start();
    // 线程001抢到了锁, 开始执行任务, 当前时间是2024-04-04T13:15:42.023
    // 线程002没有抢到, 执行其他任务, 当前时间是2024-04-04T13:15:42.023
}

线程002使用tryLock()方法获取锁,在没有抢到锁的时候,会立即执行else代码块

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Runnable r = () -> {
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + "抢到了锁, 开始执行任务, 当前时间是" + LocalDateTime.now());
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName()
                        + "没有抢到, 执行其他任务, 当前时间是" + LocalDateTime.now());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    new Thread(r, "线程001").start();
    new Thread(r, "线程002").start();
    // 线程002抢到了锁, 开始执行任务, 当前时间是2024-04-04T13:17:44.356
    // 线程001没有抢到, 执行其他任务, 当前时间是2024-04-04T13:17:47.342
}

线程001使用tryLock(long timeout, TimeUnit unit)方法获取锁,在尝试获取锁的时间范围(3秒)内没有获取到,会去执行else代码块

public static void main(String[] args) {
    Lock lock = new ReentrantLock();
    Runnable r = () -> {
        try {
            if (lock.tryLock(3, TimeUnit.SECONDS)) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + "抢到了锁, 开始执行任务, 当前时间是" + LocalDateTime.now());
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println(Thread.currentThread().getName()
                        + "没有抢到, 执行其他任务, 当前时间是" + LocalDateTime.now());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };
    new Thread(r, "线程001").start();
    new Thread(r, "线程002").start();
    // 线程001抢到了锁, 开始执行任务, 当前时间是2024-04-04T13:19:39.238
    // 线程002抢到了锁, 开始执行任务, 当前时间是2024-04-04T13:19:40.240
}

线程002使用tryLock(long timeout, TimeUnit unit)方法获取锁,在尝试获取锁的时间范围内获取到了锁,去执行任务代码,并会释放锁;
tryLock()方法的使用场景是不想不确定的时间一直等着锁,通过自己判断锁释放与否,执行任务代码;

5.3 CAS乐观锁机制

CAS乐观锁机制是指对共享变量的安全更新机制,可以够保证原子性、可见性、有序性;AtomicInteger是基于CAS的乐观锁实现的,是一个原子性操作类,用来实现对整型数据的原子操作;

public class SellTicketCAS implements Runnable {
    private AtomicInteger atomicInteger = new AtomicInteger(100);

    @Override
    public void run() {
        while (true) {
            int val = atomicInteger.getAndDecrement();
            if (val > 0) {
                try {
                    System.out.println(Thread.currentThread().getName()
                            + "正在售卖剩余的第" + val + "张票");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                break;
            }
        }
    }
}
public class ThreadMethod {
    public static void main(String[] args) {
        SellTicketCAS sellTicketCAS = new SellTicketCAS();
        new Thread(sellTicketCAS, "线程001").start();
        new Thread(sellTicketCAS, "线程002").start();
        new Thread(sellTicketCAS, "线程003").start();
    }
}

5.4 避免死锁

死锁是指多个线程在抢夺资源时互相等待的状态,死锁产生的原因是:
    线程需要获取多个资源,才能执行线程任务;
    线程获取到资源时,资源仅供当前线程使用,执行完线程任务后才会释放资源,如果当前线程在请求资源时处于阻塞状态时,那么当前线程已获得的资源不释放;
    多个线程都持有对方所需要的资源,相互等待;
避免死锁的方法:
    线程一次性获得需要的所有资源;
    线程如果有资源获取不到,可以使用Lock接口的tryLock()方法,如果超多获取资源的时间,那就释放掉已占用的资源;

六 生产者消费者模式

生产者消费者模式又叫等待唤醒机制,是一种并发设计模式,旨在解决线程之间的协作和数据共享问题;
生产者消费者模式中,生产者和消费者通过一个共享的缓冲区进行通信,生产者生成数据并放入缓冲区,消费者从缓冲区中取出数据进行处理,这样有效地协调异步任务的生成和处理,提高系统的响应速度;
实现生产者消费者模式时,需要考虑多线程之间的协作,以及对临界区资源的处理;

6.1 wait()和notify()

共享资源的操作写在资源类里面,使用synchronized关键字控制线程操作资源,配合wait()、notify()和notifyAll()方法实现线程之间的协作

资源类:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResourceOne {
    private int bread = 5;
    private int maxCapacity = 10;
    private int minCapacity = 3;
    public synchronized void addBrand() {
        try {
            if (bread < maxCapacity) {
                bread++;
                System.out.println(Thread.currentThread().getName()
                        + "线程正在生产" + bread + "个面包");
                notifyAll();
            } else {
                System.out.println(Thread.currentThread().getName()
                        + "线程处于休眠状态");
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public synchronized void removeBrand() {
        try {
            if (bread > minCapacity) {
                System.out.println(Thread.currentThread().getName()
                        + "线程正在消费" + bread + "个面包");
                bread--;
                notifyAll();
            } else {
                System.out.println(Thread.currentThread().getName()
                        + "线程处于休眠状态");
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

生产者类:

public class ProductOne implements Runnable{

    private ResourceOne resourceOne;
    private Random random = new Random();

    public ProductOne(ResourceOne resourceOne) {
        this.resourceOne = resourceOne;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resourceOne.addBrand();
		}
	}
}

消费者类:

public class ConsumerOne implements Runnable{

    private ResourceOne resourceOne;
    private Random random = new Random();

    public ConsumerOne(ResourceOne resourceOne) {
        this.resourceOne = resourceOne;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resourceOne.removeBrand();
		}
	}
}

测试类:

public class ThreadWaitingConsumer {
    public static void main(String[] args) {
        ResourceOne resourceOne = new ResourceOne();
        ProductOne p1 = new ProductOne(resourceOne);
        ConsumerOne c1 = new ConsumerOne(resourceOne);
        new Thread(p1, "生产者001").start();
        new Thread(p1, "生产者002").start();
        new Thread(p1, "生产者003").start();
        new Thread(c1, "消费者001").start();
        new Thread(c1, "消费者002").start();
        new Thread(c1, "消费者003").start();
    }
}

6.2 wait()和notify()

共享资源的操作写在生产者和消费者类里面,使用synchronized关键字控制线程操作资源,配合wait()、notify()和notifyAll()方法实现线程之间的协作
资源类:

@Data
public class ResourceOne {
    private int bread = 5;
}

生产者类:

public class ProductOne implements Runnable{

    private ResourceOne resourceOne;
    private Random random = new Random();
    private int maxBreadNum = 10;

    public ProductOne(ResourceOne resourceOne) {
        this.resourceOne = resourceOne;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (resourceOne) {
                try {
                    if (resourceOne.getBread() < maxBreadNum) {
                        resourceOne.setBread(resourceOne.getBread() + 1);
                        System.out.println(Thread.currentThread().getName()
                                + "线程生产了" + resourceOne.getBread() + "个面包...");
                        Thread.sleep(random.nextInt(10) * 50);
                        resourceOne.notifyAll();
                    } else {
                        System.out.println(Thread.currentThread().getName()
                                + "线程处于休眠状态...");
                        resourceOne.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
		}
	}
}

消费者类:

public class ConsumerOne implements Runnable{

    private ResourceOne resourceOne;
    private Random random = new Random();
    private int minBreadNum = 3;

    public ConsumerOne(ResourceOne resourceOne) {
        this.resourceOne = resourceOne;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (resourceOne) {
                try {
                    if (resourceOne.getBread() > minBreadNum) {
                        System.out.println(Thread.currentThread().getName()
                                + "吃掉了第"+ resourceOne.getBread() +"个面包...");
                        resourceOne.setBread(resourceOne.getBread() - 1);
                        Thread.sleep(random.nextInt(10) * 50);
                        resourceOne.notifyAll();
                    } else {
                        System.out.println(Thread.currentThread().getName()
                                + "线程处于休眠状态...");
                        resourceOne.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
		}
	}
}

测试类:

public class ThreadWaitingConsumer {
    public static void main(String[] args) {
        ResourceOne resourceOne = new ResourceOne();
        ProductOne p1 = new ProductOne(resourceOne);
        ConsumerOne c1 = new ConsumerOne(resourceOne);
        new Thread(p1, "生产者001").start();
        new Thread(p1, "生产者002").start();
        new Thread(p1, "生产者003").start();
        new Thread(c1, "消费者001").start();
        new Thread(c1, "消费者002").start();
        new Thread(c1, "消费者003").start();
    }
}

6.3 Condition

共享资源的操作写在资源类里面,使用Condition接口控制线程操作资源,与Lock接口配合使用实现线程之间的协作;
Condition对象是由Lock对象创建出来的,Condition是依赖Lock对象的,Condition定义了等待/通知两种类型的方法:
void await():当前线程进入等待状态直到接到singal唤醒通知或中断,当前线程将进入运行状态:
  其他线程调用同一个Condition对象的signal()或signalAll()方法,当前线程被唤醒;
  其他线程调用interrupt()方法中断当前线程;
  如果当前等待线程从await()方法返回,那么表明当前线程已经获取了Condition对象所对应的锁;
void signal():唤醒一个等待在同一个Condition上的线程;
void signalAll():唤醒所有等待在同一个Condition上的线程;

资源类:

public class ResourceTwo {
    private int initVal = 5;
    private int maxCapacity = 10;
    private int minCapacity = 3;
    private Lock lock = new ReentrantLock();
    private Condition producerCondition = lock.newCondition();
    private Condition consumerCondition = lock.newCondition();
    public void addBrand() {
        try {
            lock.lock();
            if (initVal < maxCapacity) {
                System.out.println(Thread.currentThread().getName()
                        + "线程正在生产第" + ++initVal + "个面包");
                consumerCondition.signalAll();
            } else {
                System.out.println(Thread.currentThread().getName() 
                        + "线程处于休眠状态");
                producerCondition.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void removeBrand() {
        try {
            lock.lock();
            if (initVal > minCapacity) {
                System.out.println(Thread.currentThread().getName()
                        + "线程正在消费第" + initVal-- + "个面包");
                producerCondition.signalAll();
            } else {
                System.out.println(Thread.currentThread().getName() 
                        + "线程处于休眠状态");
                consumerCondition.await();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

生产者:

public class ProductTwo implements Runnable{

    private ResourceTwo resource;
    private Random random = new Random();

    public ProductTwo(ResourceTwo resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.addBrand();
        }
    }
}

消费者类:

public class ConsumerTwo implements Runnable{

    private ResourceTwo resource;
    private Random random = new Random();

    public ConsumerTwo(ResourceTwo resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.removeBrand();
        }
    }
}

测试类:

public class ThreadWaitingConsumer {
    public static void main(String[] args) {
        ResourceTwo resource = new ResourceTwo();
        ProductTwo p1 = new ProductTwo(resource);
        ConsumerTwo c1 = new ConsumerTwo(resource);
        new Thread(p1, "生产者001").start();
        new Thread(p1, "生产者002").start();
        new Thread(p1, "生产者003").start();
        new Thread(c1, "消费者001").start();
        new Thread(c1, "消费者002").start();
        new Thread(c1, "消费者003").start();
    }
}

6.4 阻塞队列

使用BlockingQueue阻塞队列实现等待唤醒机制,ArrayBlockingQueue是BlockingQueue接口的实现类,底层数据结构是一个基于数组的阻塞队列;
ArrayBlockingQueue是一个有界队列,在创建ArrayBlockingQueue对象的时候需要指定队列的大小;
ArrayBlockingQueue的常用方法:
        boolean add(E e):添加对象到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则报异常 Exception in thread "main" java.lang.IllegalStateException: Queue full;
        boolean offer(E e):添加对象到BlockingQueue里,即如果BlockingQueue可以容纳,则返回true,否则返回false;
        void put(E e):添加对象到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被中断进入等待状态,直到BlockingQueue里面有空间;
        E take():取走BlockingQueue里排在首位的对象,若BlockingQueue为空,则调用此方法的线程被中断进入等待状态,直到有新的对象被加入;
        int remainingCapacity():阻塞队列中剩余可用的大小,等于初始容量减去当前的size大小;

资源类:

public class ResourceThree {
    private BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<Integer>(10);
    private Random random = new Random();
    int i = 0;
    public void addBrand() {
        try {
            blockingQueue.put(++i);
            System.out.println(Thread.currentThread().getName() +
                    "生产的数据是: " + i);
            Thread.sleep((long) 100 * random.nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void removeBrand() {
        try {
            Integer take = blockingQueue.take();
            System.out.println(Thread.currentThread().getName() +
                    "消费的数据是: " + take);
            Thread.sleep((long) 100 * random.nextInt(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

生产者类:

public class ProductThree implements Runnable{

    private ResourceThree resource;
    private Random random = new Random();

    public ProductThree(ResourceThree resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.addBrand();
        }
    }
}

消费者类:

public class ConsumerThree implements Runnable{

    private ResourceThree resource;
    private Random random = new Random();

    public ConsumerThree(ResourceThree resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(random.nextInt(10) * 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            resource.removeBrand();
        }
    }
}

测试类:

public class ThreadWaitingConsumer {
    public static void main(String[] args) {
        ResourceThree resource = new ResourceThree();
        ProductThree p1 = new ProductThree(resource);
        ConsumerThree c1 = new ConsumerThree(resource);
        new Thread(p1, "生产者001").start();
        new Thread(p1, "生产者002").start();
        new Thread(p1, "生产者003").start();
        new Thread(c1, "消费者001").start();
        new Thread(c1, "消费者002").start();
        new Thread(c1, "消费者003").start();
    }
}

tips

线程池入门学习

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值