Java多线程

1、线程,进程简介

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个

线程。(进程是资源分配的最小单位)

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是CPU调度的最小单位)

注意:线程开始并不一定立即执行,有CPU调度执行

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

多进程是指操作系统能同时运行多个任务(程序)。

多线程是指在同一程序中有多个顺序流在执行。

常用方法

sleep(): 强迫一个线程睡眠N毫秒。 
isAlive(): 判断一个线程是否存活。 
join(): 等待线程终止。 
activeCount(): 程序中活跃的线程数。 
enumerate(): 枚举程序中的线程。 
currentThread(): 得到当前线程。 
isDaemon(): 一个线程是否为守护线程。 
setDaemon(): 设置一个线程为守护线程。用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束 
setName(): 为线程设置一个名称。 
wait(): 强迫一个线程等待。 
notify(): 通知一个线程继续运行。 
setPriority(): 设置一个线程的优先级。

2、多线程的实现

1)、扩展Thread类

// 创建线程 方式1
public class ThreadDemo01 extends Thread{
    // 重写 run方法
    @Override
    public void run() {
        // run 方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码~~~~");
        }
    }
​
    public static void main(String[] args) {
        // main方法线程体
        // 创建一个线程对象
        ThreadDemo01 td = new ThreadDemo01();
        // 启动线程  start是同时进行, run是顺序进行
        td.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程");
        }
    }
}
​

不建议使用:避免 OOP 单继承局限性

2)、实现runnable接口

public class ThreadDemo02 implements Runnable{
    // 重写 run方法
    @Override
    public void run() {
        // run 方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码~~~~");
        }
    }
​
    public static void main(String[] args) {
        ThreadDemo02 td = new ThreadDemo02();
        // 创建一个Thread对象(代理)然后将实现类传入
        new Thread(td).start();
​
        for (int i = 0; i < 20; i++) {
            System.out.println("我在学习多线程");
        }
    }
}

建议使用:避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

3)、实现Callable接口(了解)

// 线程创建 方式3  Callable接口
public class ThreadDemo05 implements Callable<Boolean> {
​
    // 重写call方法
    @Override
    public Boolean call() throws Exception {
        return true;
    }
​
    // 创建主线程
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ThreadDemo05 td1 = new ThreadDemo05();
        ThreadDemo05 td2 = new ThreadDemo05();
        ThreadDemo05 td3 = new ThreadDemo05();
​
        // 创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(2);
        // 提交执行
        Future<Boolean> s1 = service.submit(td1);
        Future<Boolean> s2 = service.submit(td2);
        Future<Boolean> s3 = service.submit(td3);
        // 获取结果
        Boolean b1 = s1.get();
        Boolean b2 = s2.get();
        Boolean b3 = s3.get();
        System.out.println("b1" + b1);
        System.out.println("b2" + b2);
        System.out.println("b3" + b3);
​
        // 关闭服务
        service.shutdown();
    }
}
​

3、初识并发问题

多个线程操作同一个资源,线程不安全

// 初识并发问题
public class ThreadDemo03 implements Runnable{
​
    // 模拟抢票
    private int ticket = 10;
​
    @Override
    public void run() {
        while(true){
            if (ticket <= 0){
                break;
            }
​
            // 模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
​
            System.out.println(Thread.currentThread().getName() + "-->拿到第" + ticket-- + "张票");
        }
    }
​
    public static void main(String[] args) {
        ThreadDemo03 td = new ThreadDemo03();
        // 单个对象被多个线程使用
        new Thread(td,"小明").start();
        new Thread(td,"小红").start();
        new Thread(td,"黄牛党").start();
​
        // 小红-->拿到第10张票
        // 黄牛党-->拿到第8张票
        // 小明-->拿到第9张票
        // 黄牛党-->拿到第6张票
        // 小红-->拿到第5张票
        // 小明-->拿到第7张票
        // 小红-->拿到第4张票
        // 黄牛党-->拿到第3张票
        // 小明-->拿到第2张票
        // 小红-->拿到第1张票
        // 黄牛党-->拿到第1张票
        // 小明-->拿到第1张票
    }
}
​

出现三个人同时抢同一张票的情况(并发问题)

小红-->拿到第1张票、黄牛党-->拿到第1张票、小明-->拿到第1张票

龟兔赛跑案例

// 模拟龟兔赛跑
public class ThreadDemo04 implements Runnable{
    // 胜利者
    private static String winner;
​
    // 重写 Run 方法
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            // 模拟兔子睡觉
            if(Thread.currentThread().getName().equals("兔子") &&  i % 10 == 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
​
​
            // 判断是否完成比赛
            boolean flag = gameOver(i);
            if (flag){
                break;
            }
​
            System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
        }
    }
​
    // 判断谁获胜
    private boolean gameOver(int steps) {
        // 判断是否与获胜者
        if(winner != null){
            return true;
        }{
            if (steps >= 100){
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }
        }
        return false;
    }
​
    // 主线程
    public static void main(String[] args) {
        ThreadDemo04 td = new ThreadDemo04();
        new Thread(td, "兔子").start();
        new Thread(td, "乌龟").start();
    }
}
​

4、线程状态

1、新建状态(New):新创建了一个线程对象。 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。 4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: (一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁) (二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。 (三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁) 5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

1)、停止线程

// 线程停止 建议线程正常停止 -->  利用次数,不建议使用jdk 的stop和destroy方法,已经过时
public class ThreadTest01 implements Runnable{
    // 1、创建一个标志位
    private boolean flag = true;
​
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run......" + i++);
        }
    }
​
    // 创建一个方法,停止线程
    public void stop(){
        this.flag = false;
    }
​
    public static void main(String[] args) {
        ThreadTest01 threadTest01 = new ThreadTest01();
        new Thread(threadTest01).start();
​
        for (int i = 0; i < 1000; i++) {
            System.out.println("main......" + i);
            if (i == 900){
                // 调用stop方法, 让线程停止
                threadTest01.stop();
                System.out.println("线程停止");
            }
        }
    }
}

2)、线程休眠

模拟网络延时:放大问题的发生性

在上述 模拟购票ThreadDemo03类中,通过模拟网络延时,发现线程并发问题

每个对象都有一把锁,sleep对象不会释放锁

// 模拟倒计时.....
public class ThreadTest03 {
​
    public static void main(String[] args){
        // 获取当前系统时间
        Date startTime = new Date(System.currentTimeMillis());
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                // 更新当前时间
                startTime = new Date(System.currentTimeMillis());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
​
    public static void tenDown() throws InterruptedException {
        int i = 10;
        while (true){
            Thread.sleep(1000);
            System.out.println(i--);
            if (i <= 0){
                break;
            }
        }
    }
}
​

3)、线程礼让

礼让线程,让当前正在执行的线程暂停,但不阻塞

将线程从运行状态转换为就绪状态

让CPU重新调度,礼让不一定成功

// 测试礼让线程 yield
public class ThreadTest04 {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        new Thread(myYield,"A").start();
        new Thread(myYield,"B").start();
        // A线程开始执行
        // B线程开始执行
        // A线程停止执行
        // B线程停止执行
    }
}
​
//创建一个线程类
class MyYield implements Runnable{
​
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始执行");
        // 礼让线程
        Thread.yield();
        System.out.println(Thread.currentThread().getName() + "线程停止执行");
    }
}

4)、线程插队

Join合并线程,待此线程执行完成以后,再执行其他线程,其他线程阻塞

// 测试线程强制执行 join
public class ThreadTest05 implements Runnable{
​
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("VIP线程....." + i);
        }
    }
​
    public static void main(String[] args) throws InterruptedException {
        ThreadTest05 threadTest05 = new ThreadTest05();
        Thread thread = new Thread(threadTest05);
        
        for (int i = 0; i < 500; i++) {
            if (i == 200){
                thread.start();
                thread.join();
            }
            System.out.println("主线程....." + i);
        }
    }
}

5、线程的优先级

Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。 JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

优先级低:被调用的概率低,具体顺序看CPU调度

// 线程优先级
public class ThreadTest06{
    public static void main(String[] args) {
        // 主线程默认优先级
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
​
        MyPriority myPriority = new MyPriority();
        Thread thread1 = new Thread(myPriority);
        Thread thread2 = new Thread(myPriority);
        Thread thread3 = new Thread(myPriority);
        Thread thread4 = new Thread(myPriority);
        Thread thread5 = new Thread(myPriority);
        Thread thread6 = new Thread(myPriority);
​
        // 先设置优先级,再启动
        thread1.setPriority(1);
        thread1.start();
​
        thread2.setPriority(4);
        thread2.start();
​
        thread3.setPriority(Thread.MAX_PRIORITY);
        thread3.start();
​
        //java.lang.IllegalArgumentException
        //thread4.setPriority(-1);
        //thread4.start();
​
        thread5.setPriority(9);
        thread5.start();
​
        //java.lang.IllegalArgumentException
        //thread6.setPriority(11);
        //thread6.start();
​
    }
}
​
class MyPriority implements Runnable{
​
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
    }
}
​

6、守护线程

线程分为用户线程和守护线程

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕

eg:后台日志操作日志,监控内存,垃圾回收GC......

A线程守护B线程

// 守护线程  A线程守护B线程
public class ThreadTest07 {
    public static void main(String[] args) {
        AThread aThread = new AThread();
        BThread bThread = new BThread();
​
        Thread thread = new Thread(aThread);
        // 设置成守护线程,JVM不用等待守护线程,执行完毕,直接退出
        thread.setDaemon(true); // 默认是false,正常线程都是用户线程.....
        thread.start();
​
        // 用户线程
        new Thread(bThread).start();
    }
}
​
//A线程
class AThread implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println("A线程执行中...A线程守护B线程");
        }
    }
}
​
​
//B线程
class BThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("B线程执行中...");
        }
        System.out.println("B线程执行完毕...");
    }
}

7、线程同步(并发)

1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。 2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法 3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。 6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。 7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。

三大不安全案例

买票

// 不安全的买票
public class SynDemo01 implements Runnable{
​
    // 模拟抢票
    private int ticket = 10;
​
    @Override
    public void run() {
        while(true){
            if (ticket <= 0){
                break;
            }
            // 模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到第" + ticket-- + "张票");
        }
    }
​
    public static void main(String[] args) {
        SynDemo01 synDemo01 = new SynDemo01();
        // 单个对象被多个线程使用
        new Thread(synDemo01,"小明").start();
        new Thread(synDemo01,"小红").start();
        new Thread(synDemo01,"黄牛党").start();
        /*
        小明-->拿到第10张票
        黄牛党-->拿到第10张票
        小红-->拿到第9张票
        小红-->拿到第8张票
        小明-->拿到第7张票
        黄牛党-->拿到第6张票
        小明-->拿到第5张票
        黄牛党-->拿到第4张票
        小红-->拿到第4张票
        小红-->拿到第3张票
        黄牛党-->拿到第2张票
        小明-->拿到第3张票
        小红-->拿到第1张票
        黄牛党-->拿到第-1张票
        小明-->拿到第0张票
        */
    }
}
​
​

取钱

// 不安全的取钱
public class SynDemo02 {
    public static void main(String[] args) {
        Account account = new Account(1000, "结婚基金");
        DrawMoney You = new DrawMoney(account, 300, "You");
        DrawMoney GirlFriend = new DrawMoney(account, 800, "GirlFriend");
        You.start();
        GirlFriend.start();
        System.out.println("结婚基金账户余额为:" + account.money);
​
    }
}
​
// 账户
class Account {
    int money;
    String name;
​
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
​
// 银行
class DrawMoney extends Thread{
    Account account;
    int drawMoney;
​
    public DrawMoney( Account account,int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }
​
    // 线程执行体
    @Override
    public void run() {
        // 判断是否有钱
        if (account.money >= drawMoney) {
            // 账户余额大于取钱金额
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 账户余额减去取钱金额
            account.money -= drawMoney;
            System.out.println(Thread.currentThread().getName() + "取钱成功,账户余额为:" + account.money);
        } else {
            // 账户余额小于取钱金额
            System.out.println(Thread.currentThread().getName()+"取钱失败,账户余额为:" + account.money);
        }
    }
}

不安全的集合

// 线程不安全的集合
public class SynDemo03 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        // 模拟延迟
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
​
        System.out.println(list.size()); // 9997 线程不安全
    }
}

8、synchronized

synchronized锁的对象,监听的对象 就是变的量,就是执行增删改查操作的量

1、synchronized关键字的作用域有二种: 1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法; 2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

买票问题解决

// 不安全的买票
public class SynDemo01 implements Runnable{
​
    // 模拟抢票
    private int ticket = 10;
​
    @Override
    public void run() {
        while(true){
            // synchronized 修饰  默认锁本身
            synchronized (this){
                if (ticket <= 0){
                    break;
                }
                // 模拟延时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "-->拿到第" + ticket-- + "张票");
            }
        }
    }
​
    public static void main(String[] args) {
        SynDemo01 synDemo01 = new SynDemo01();
        // 单个对象被多个线程使用
        new Thread(synDemo01,"小明").start();
        new Thread(synDemo01,"小红").start();
        new Thread(synDemo01,"黄牛党").start();
    }
}

取钱问题解决

// 不安全的取钱
public class SynDemo02 {
    public static void main(String[] args) {
        Account account = new Account(1000, "结婚基金");
        DrawMoney You = new DrawMoney(account, 300, "You");
        DrawMoney GirlFriend = new DrawMoney(account, 800, "GirlFriend");
        You.start();
        GirlFriend.start();
        System.out.println("结婚基金账户余额为:" + account.money);
​
    }
}
​
// 账户
class  Account {
    int money;
    String name;
​
    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
​
// 银行
class DrawMoney extends Thread{
    Account account;
    int drawMoney;
​
    public DrawMoney( Account account,int drawMoney, String name) {
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;
    }
​
    // 线程执行体
    @Override
    public void run() {
        synchronized (account){
            // 判断是否有钱
            if (account.money >= drawMoney) {
                // 账户余额大于取钱金额
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 账户余额减去取钱金额
                account.money -= drawMoney;
                System.out.println(Thread.currentThread().getName() + "取钱成功,账户余额为:" + account.money);
            } else {
                // 账户余额小于取钱金额
                System.out.println(Thread.currentThread().getName()+"取钱失败,账户余额为:" + account.money);
            }
        }
    }
}

不安全的集合加锁

// 线程不安全的集合
public class SynDemo03 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> {
                // 添加 synchronized 保证线程安全
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }
        // 模拟延迟
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(list.size()); 
        //加锁前:<10000 线程不安全    加锁后:添加 synchronized 保证线程安全
    }
}
​

9、CopyOnWriteArrayList

测试JUC安全类型的集合 CopyOnWriteArrayList是线程安全的集合

// 测试测试JUC安全类型的集合
public class CopyOnWriteArrayList {
    public static void main(String[] args) {
        java.util.concurrent.CopyOnWriteArrayList<String> list = new java.util.concurrent.CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
​
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
​
        System.out.println(list.size()); // 10000
    }
}

10、死锁

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对方获得的资源保持不放

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

  4. 循环等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系

避免方法:避免其中1个或多个条件即可避免死锁产生

某一同步块同时拥有两个或两个以上的锁时,可能会发生死锁的现象 相互等待对方的资源

// 会发生死锁的情况
private void makeUp() throws InterruptedException {
   if (choice == 0){
       synchronized (lipstick){
           System.out.println(name + "获得口红锁");
           Thread.sleep(1000);
           synchronized (mirror){
               System.out.println(name + "获得镜子锁");
           }
       }
   }else{
       synchronized (mirror){
           System.out.println(name + "获得镜子锁");
           synchronized (lipstick){
               System.out.println(name + "获得口红锁");
           }
       }
   }
}

eg:化妆

// 死锁: 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此所需资源不释放,而导致一种互相等待的状态,
public class LockDemo01 {
    public static void main(String[] args) {
        MakeUp makeUp1 = new MakeUp(0,"小红");
        MakeUp makeUp2 = new MakeUp(1,"小花");
        makeUp1.start();
        makeUp2.start();
​
    }
}
​
// 口红
class Lipstick {
}
​
// 镜子
class Mirror {
}
​
// 化妆
class MakeUp extends Thread {
   // 需要的资源只有一个,用static来保证只有一份
   static Lipstick lipstick = new Lipstick();
   static Mirror mirror = new Mirror();
​
   int choice; // 选择
   String name; // 化妆的人
​
   MakeUp(int choice, String name){
       this.choice = choice;
       this.name = name;
   }
​
    @Override
    public void run() {
        // 化妆
        try {
            makeUp();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
​
    // 化妆方法
    private void makeUp() throws InterruptedException {
       if (choice == 0){
           synchronized (lipstick){
               System.out.println(name + "获得口红锁");
               Thread.sleep(1000);
               }
           synchronized (mirror){
               System.out.println(name + "获得镜子锁");
           }
       }else{
           synchronized (mirror){
               System.out.println(name + "获得镜子锁");
               }
           synchronized (lipstick){
               System.out.println(name + "获得口红锁");
           }
       }
    }
}

11、锁Lock

通过实现 ReentrantLock 对象,创建Lock实例化,调用 .lock() 加锁

// Lock
public class LockDemo02 {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        new Thread(testLock2,"小明").start();
        new Thread(testLock2,"媛媛").start();
        new Thread(testLock2,"小红").start();
       
       
    }
}
​
class TestLock2 implements Runnable {
​
    // 票
    private int ticket = 10;
​
    private ReentrantLock lock = new ReentrantLock();
​
​
    @Override
    public void run() {
        while (true) {
            try {
                // 加锁
                lock.lock();
                if (ticket > 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } else {
                    break;
                }
            } finally {
                // 解锁
                lock.unlock();
            }
        }
    }
}

不加锁会出现线程不安全

不加锁的结果
小红正在卖第10张票
媛媛正在卖第10张票
小明正在卖第10张票
小明正在卖第7张票
小红正在卖第7张票
媛媛正在卖第7张票
媛媛正在卖第4张票
小明正在卖第4张票
小红正在卖第4张票
小明正在卖第1张票
小红正在卖第1张票
媛媛正在卖第-1张票

加锁后运行结果

 /*
        加锁
        小明正在卖第10张票
        小明正在卖第9张票
        小红正在卖第8张票
        小红正在卖第7张票
        小红正在卖第6张票
        小红正在卖第5张票
        小红正在卖第4张票
        媛媛正在卖第3张票
        小明正在卖第2张票
        小明正在卖第1张票
 */

synchronized和Lock的对比

Lock是显示锁(手动开启,手动关闭,别忘记关锁)synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有方法锁和代码块锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供较多子类)

优先顺序:Lock > 同步代码块(已经进入方法体,分配了相应资源)> 同步方法(在方法体之外)

12、线程协作

  • wait() / wait(long timeout):让当前线程进入等待状态。

  • notify():唤醒当前对象上⼀个休眠的线程(随机)。

  • notifyAll():唤醒当前对象上的所有线程

1.1.1 wait() 使用

wait 执行流程:

使当前执行代码的线程进行等待. (把线程放到等待队列中) 释放当前的锁 满足一定条件时被唤醒, 重新尝试获取这个锁. wait 结束等待的条件:

其他线程调用该对象的 notify 方法. wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间). 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

1.1.2 notify 使用

notify 方法是唤醒等待的线程:

方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到") 在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。 在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完才会释放对象锁,有三个等待的线程,每次唤醒的也是随机一个线程。

1.1.3 notiyAll 使用

notify() 方法只是唤醒某⼀个等待线程. 使用 notifyAll() 方法可以⼀次唤醒所有的等待线程。

注意事项:

wait/notify/notifyAll 必须配合 synchronized 一起使用; wait 和 synchronized 必须使用同⼀把锁,不然会报错; 当调用了 notify/notifyAll 之后,程序并不会立即恢复执行,而是尝试获取锁,只有获取到锁之后才能继续执行; notifyAll 并不是唤醒所有 wait 的对象,而是唤醒当前对象所有 wait 的对象。

1)、生产者和消费者问题

2)、管程法

利用缓存区解决

// 测试 生产者消费者模型 -> 利用缓存区方法 :管程法
// 四个对象 :生产者、消费者、产品、缓冲区
public class TestPC {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}
​
// 生产者
class Producer extends Thread {
    Buffer buffer;
    public Producer(Buffer buffer){
         this.buffer = buffer;
    }
​
    // 生产方法
    public void run() {
        for (int i = 0; i <= 50; i++) {
            // 放入产品
            buffer.put(new Product(i));
            System.out.println("生产者生产了产品->" + i);
        }
    }
​
}
// 消费者
class Consumer extends Thread {
    Buffer buffer;
    public Consumer(Buffer buffer){
         this.buffer = buffer;
    }
​
    // 消费方法
    public void run() {
        for (int i = 0; i <= 50; i++) {
            // 获取产品
            buffer.get();
            System.out.println("消费者消费了产品------->" + buffer.get().id);
        }
    }
}
// 产品
class Product {
    int id;
​
    public Product(int id) {
        this.id = id;
    }
}
// 缓存区
class Buffer {
    // 缓冲区大小
    Product[] products = new Product[10];
    // 容器计算器
    int count = 0;
​
    // 生产者放入产品
    public synchronized void put(Product product) {
        // 如果容器满了,就需要等待消费者消费
        if (count == products.length){
            // 通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 容器没有满,可以生产
        products[count] = product;
        count++;
        // 通知消费者消费
        this.notifyAll();
    }
​
    // 消费者取出产品
    public synchronized Product get() {
        // 判断容器是否为空
        if (count == 0) {
            // 容器为空,需要等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        // 容器不为空,可以消费
        count--;
        Product product = products[count];
        // 通知生产者生产
        this.notifyAll();
        return product;
    }
}
​
​

3)、信号灯法

利用标识

package Chapter07;
​
// 测试生产者消费者模型 方法二使用标识符  信号灯法
public class TestPC2 {
    public static void main(String[] args) {
        Program program = new Program();
        new Actor(program).start();
        new Customer(program).start();
    }
}
​
// 生产者-->演员
class Actor extends Thread{
    Program program;
    // 表演
    public Actor(Program program){
        this.program = program;
    }
    // 重写run
    public void run(){
        for(int i = 0; i <= 20; i++){
            if(i % 2 == 0){
                program.play("快乐大本营第" + i + "期");
            }else{
                program.play("抖音第" + i + "期");
            }
        }
    }
}
​
// 消费者-->观众
class Customer extends Thread{
    Program program;
    // 观看
    public Customer(Program program){
        this.program = program;
    }
    // 重写run
    public void run(){
        for(int i = 0; i <= 20; i++){
            program.watch();
        }
    }
}
​
// 产品 --> 节目
class Program{
    // 演员表演,观众观看   true
    // 观众观看,演员等待   false
​
    // 表演的节目
    String voice;
    // 标识符,判断是否有表演
    boolean flag = true;
​
    // 表演
    public synchronized void play(String voice){
        // 判断是否有表演
        if(!flag){
            try{
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:"+voice);
        // 通知观众观看
        this.notifyAll();
        this.voice = voice;
        this.flag = !this.flag;
    }
​
    // 观看
    public synchronized void watch(){
        // 判断是否有表演
        if(flag){
            try{
                this.wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("观看了:"+voice);
        // 通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
​
}

13、线程池

ExecutorService线程池接口

execute:执行任务/命令;没有返回值,一般用来执行Runnable

submit:执行任务,有返回值,一般用于执行Callable

shutdown:关闭连接池


// 测试 线程池
public class TestPool {
    public static void main(String[] args) {
        // 1. 创建线程池 参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 2. 执行
        service.execute(new MyTask());
        service.execute(new MyTask());
        service.execute(new MyTask());
        service.execute(new MyTask());
        // 3. 关闭线程池
        service.shutdown();
    }
}
​
class MyTask implements Runnable {
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}

参考文章链接:Java多线程学习(吐血超详细总结)_java 多线程 学习-CSDN博客

原文链接:Java多线程(五):线程之间的通讯_java lock.wait-CSDN博客

  • 29
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值