JavaSE-11笔记【多线程2(+2024新)】

文章目录

  • 6.线程安全
    • 6.1 线程安全问题
    • 6.2 线程同步机制
    • 6.3 关于线程同步的面试题
      • 6.3.1 版本1
      • 6.3.2 版本2
      • 6.3.3 版本3
      • 6.3.4 版本4
  • 7.死锁
    • 7.1 多线程卖票问题
  • 8.线程通信
    • 8.1 wait()和sleep的区别?
    • 8.2 两个线程交替输出
    • 8.3 三个线程交替输出
    • 8.4 线程通信-生产者和消费者模式
  • 9.线程生命周期的回顾与补充
  • 10.单例模式
    • 10.1 饿汉式单例模式
    • 10.2 懒汉式单例模式
    • 10.3 懒汉式单例模式可能会造成线程安全问题
  • 11.可重入锁ReentrantLock
  • 12.实现线程的第三种方式:实现Callable接口
  • 13.实现线程的第四种方式:使用线程池

接上一篇 JavaSE-10笔记【多线程1】

6.线程安全

6.1 线程安全问题

  1. 什么情况下需要考虑线程安全问题?
    多线程并发的环境下,有共享的数据,且涉及到共享数据的修改操作。
    一般情况下:
    ①局部变量若为基本数据类型,则不存在线程安全问题【在栈中,栈不是共享的】;而若为引用数据类型则另说了。
    ②实例变量可能存在线程安全问题,实例变量在堆中,堆是多线程共享的。
    ③静态变量也可能存在安全问题,静态变量在堆中,堆是多线程共享的。

多个线程并发对同一个银行账户进行取款操作时,会有安全问题:t1线程在取了一笔钱后,由于网络卡顿,没有及时更新余额,此时t2线程又将未更新的数据读取到,取了一笔钱,导致两个线程都取出了一笔钱,而余额只少了一笔。
如何解决:
将t1线程和t2线程排队执行,不要并发,要排队。这种排队机制被称作“线程同步机制”。【对于t1线程和t2线程,t2线程在执行的时候必须等待t1线程执行到某个位置之后,t2线程才能执行。t1和t2之间发生了等待,则认为是同步。
如果不排队,则被称为“线程异步机制”,t1和t2线程各自执行各自的,并发执行,互相不需要等待。
异步:效率高,但不安全。
同步:安全,但效率低。

示例代码:

package threadtest.thread14;

public class ThreadTest14 {
    public static void main(String[] args) {
        //创建账户对象
        Account account = new Account(54234657, 10000);

        //t1线程和t2线程共享一个账户
        Thread t1 = new Thread(new MyRunnable(account));
        t1.setName("t1");
        Thread t2 = new Thread(new MyRunnable(account));
        t2.setName("t2");

        //启动线程
        t1.start();
        t2.start();



    }
}

class MyRunnable implements Runnable{
    private Account account;

    public MyRunnable(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.withDraw(1000);
    }
}
class Account{
    private int actNo; //账户编号
    private double balance; //账户余额

    public Account(int actNo, double balance) {
        this.actNo = actNo;
        this.balance = balance;
    }

    public int getActNo() {
        return actNo;
    }

    public void setActNo(int actNo) {
        this.actNo = actNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 取款
     * @param money 取款额度
     */
    public void withDraw(double money){
        //为了演示出多线程并发带来的安全问题,这里将取款分为两步
        //1. 获取余额
        double before  = this.getBalance();
        System.out.println(Thread.currentThread().getName() + "线程正在取款" + money + ",当前账户" + this.getActNo() + "的账户余额为" + before);
        try {
            Thread.sleep(1000); //为了演示线程安全问题,这里让当前线程睡眠1秒
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //2. 取款后修改余额
        this.setBalance(before - money);
        System.out.println(Thread.currentThread().getName() + "线程取款成功,当前账户" + this.getActNo() + "的账户余额为" + this.getBalance());
    }
}

运行结果(取款2次,余额有误):
在这里插入图片描述

6.2 线程同步机制

使用线程同步机制(本质是线程排队执行),来避免多线程并发的线程安全问题:

语法格式:

synchronized(必须是需要排队的这几个线程共享的对象){
	//需要同步的代码
}

必须是需要排队的这几个线程共享的对象必须选对了,如果选错了可能会无故增加同步线程的数量,导致效率降低。
原理:

synchronized(obj){
	//需要同步的代码
}

假设obj是t1和t2两个线程共享的,t1和t2执行这段代码的时候,一定有先后顺序的,一定是有一个先抢到了CPU时间片。

  • 假设t1先抢到了CPU时间片,t1线程找共享对象obj的对象锁,找到之后,占有这把锁,只要能够占有obj对象的对象锁,就有权利进入同步代码块执行代码。
  • 当t1线程执行完同步代码块之后,会释放之前占有的对象锁(归还锁)。
  • 同样,t2线程抢到CPU时间片之后执行到这段代码也会去找对象obj的对象锁,但由于t1线程占有这把锁,t2线程只能在同步代码块之外等待,直到t1归还锁才能执行同步代码块的代码。

注意:不要无故扩大同步代码块的范围,其范围越小,效率越高。

示例代码:

package threadtest.thread14;

public class ThreadTest14 {
    public static void main(String[] args) {
        //创建账户对象
        Account account = new Account(54234657, 10000);

        //t1线程和t2线程共享一个账户
        Thread t1 = new Thread(new MyRunnable(account));
        t1.setName("t1");
        Thread t2 = new Thread(new MyRunnable(account));
        t2.setName("t2");

        //启动线程
        t1.start();
        t2.start();



    }
}

class MyRunnable implements Runnable{
    private Account account;

    public MyRunnable(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.withDraw(1000);
    }
}
class Account{
    private int actNo; //账户编号
    private double balance; //账户余额

    public Account(int actNo, double balance) {
        this.actNo = actNo;
        this.balance = balance;
    }

    public int getActNo() {
        return actNo;
    }

    public void setActNo(int actNo) {
        this.actNo = actNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 取款
     * @param money 取款额度
     */
    public void withDraw(double money) {
        //为了演示出多线程并发带来的安全问题,这里将取款分为两步
        synchronized (this) { //由于这里两个线程共享的是同一个account对象,所以这里可以直接填this,其他情况要再另外分析,并不一定都是填this!
            //1. 获取余额
            double before = this.getBalance();
            System.out.println(Thread.currentThread().getName() + "线程正在取款" + money + ",当前账户" + this.getActNo() + "的账户余额为" + before);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            //2. 取款后修改余额
            this.setBalance(before - money);
            System.out.println(Thread.currentThread().getName() + "线程取款成功,当前账户" + this.getActNo() + "的账户余额为" + this.getBalance());
        }
    }
}

也可以在实例方法上添加synchronized关键字:

  1. 在实例方法上添加了synchronized关键字之后,整个方法体就是一个同步代码块。
  2. 在实例方法上添加了synchronized关键字之后,共享对象的对象锁一定是当前对象this的对象锁。

这种方式相对于上面的局部同步代码块的方式要差一些,利用局部同步代码块的优点:

  • 共享对象可以随便调整;
  • 同步代码块的范围可以随便调整。

示例代码:

package threadtest.thread14;

public class ThreadTest14 {
    public static void main(String[] args) {
        //创建账户对象
        Account account = new Account(54234657, 10000);

        //t1线程和t2线程共享一个账户
        Thread t1 = new Thread(new MyRunnable(account));
        t1.setName("t1");
        Thread t2 = new Thread(new MyRunnable(account));
        t2.setName("t2");

        //启动线程
        t1.start();
        t2.start();



    }
}

class MyRunnable implements Runnable{
    private Account account;

    public MyRunnable(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        account.withDraw(1000);
    }
}
class Account{
    private int actNo; //账户编号
    private double balance; //账户余额

    public Account(int actNo, double balance) {
        this.actNo = actNo;
        this.balance = balance;
    }

    public int getActNo() {
        return actNo;
    }

    public void setActNo(int actNo) {
        this.actNo = actNo;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 取款
     * @param money 取款额度
     */
    public synchronized void withDraw(double money) { //直接在方法上加上synchronized关键字
        //为了演示出多线程并发带来的安全问题,这里将取款分为两步
        //1. 获取余额
        double before = this.getBalance();
        System.out.println(Thread.currentThread().getName() + "线程正在取款" + money + ",当前账户" + this.getActNo() + "的账户余额为" + before);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        //2. 取款后修改余额
        this.setBalance(before - money);
        System.out.println(Thread.currentThread().getName() + "线程取款成功,当前账户" + this.getActNo() + "的账户余额为" + this.getBalance());

    }
}

运行结果同上。

6.3 关于线程同步的面试题

分析以下程序,m2方法在执行的时候,需要等待m1方法的结束吗?

6.3.1 版本1

package threadtest.thread15;

public class ThreadTest15 {
    public static void main(String[] args) {
        MyClass mc = new MyClass();
        Thread t1 = new Thread(new MyRunnable(mc));
        Thread t2 = new Thread(new MyRunnable(mc));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();
    }
}
class MyRunnable implements Runnable{
    private MyClass mc;

    public MyRunnable(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            mc.m1();
        }
        if("t2".equals(Thread.currentThread().getName())){
            mc.m2();
        }
    }
}
class MyClass{

    public synchronized void m1(){ //同步方法
        System.out.println("m1 begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("m1 over");
    }

    public void m2(){ //非同步方法
        System.out.println("m2 begin");
        System.out.println("m2 over");
    }
}

运行结果:
在这里插入图片描述
不需要等待。
线程t1执行的是同步方法m1(),需要获取对象锁;
线程t2执行的是非同步方法m2(),并不需要获取对象锁。所以线程t2不需要等待线程t1。

6.3.2 版本2

package threadtest.thread15;

public class ThreadTest15 {
    public static void main(String[] args) {
        MyClass mc = new MyClass();
        Thread t1 = new Thread(new MyRunnable(mc));
        Thread t2 = new Thread(new MyRunnable(mc));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();
    }
}
class MyRunnable implements Runnable{
    private MyClass mc;

    public MyRunnable(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            mc.m1();
        }
        if("t2".equals(Thread.currentThread().getName())){
            mc.m2();
        }
    }
}
class MyClass{

    public synchronized void m1(){ //同步方法
        System.out.println("m1 begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("m1 over");
    }

    public synchronized void m2(){ //同步方法
        System.out.println("m2 begin");
        System.out.println("m2 over");
    }
}

运行结果:
在这里插入图片描述

需要等待。
由于线程t1执行的是同步方法m1(),需要获取对象锁;
线程t2执行的也是同步方法m2(),也需要获取对象锁。而线程t1和线程t2共享同一个对象,所以线程t2需要等待线程t1。

6.3.3 版本3

package threadtest.thread15;

public class ThreadTest15 {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();

        Thread t1 = new Thread(new MyRunnable(mc1));
        Thread t2 = new Thread(new MyRunnable(mc2));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();
    }
}
class MyRunnable implements Runnable{
    private MyClass mc;

    public MyRunnable(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            mc.m1();
        }
        if("t2".equals(Thread.currentThread().getName())){
            mc.m2();
        }
    }
}
class MyClass{

    public synchronized void m1(){ //同步方法
        System.out.println("m1 begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("m1 over");
    }

    public synchronized void m2(){ //同步方法
        System.out.println("m2 begin");
        System.out.println("m2 over");
    }
}

运行结果:
在这里插入图片描述
不需要等待。
虽然m1和m2方法都是同步方法,但是线程t1和线程t2并没有共享对象,其分别调用不同对象,各自占用各自对象的对象锁即可,不存在需要同一对象锁的问题。

6.3.4 版本4

package threadtest.thread15;

public class ThreadTest15 {
    public static void main(String[] args) {
        MyClass mc1 = new MyClass();
        MyClass mc2 = new MyClass();

        Thread t1 = new Thread(new MyRunnable(mc1));
        Thread t2 = new Thread(new MyRunnable(mc2));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t2.start();
    }
}
class MyRunnable implements Runnable{
    private MyClass mc;

    public MyRunnable(MyClass mc){
        this.mc = mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            mc.m1();
        }
        if("t2".equals(Thread.currentThread().getName())){
            mc.m2();
        }
    }
}
class MyClass{

    public static synchronized void m1(){ //静态同步方法
        System.out.println("m1 begin");
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("m1 over");
    }

    public static synchronized void m2(){ //静态同步方法
        System.out.println("m2 begin");
        System.out.println("m2 over");
    }
}

运行结果:
在这里插入图片描述

需要等待。
因为此时m1和m2方法都是静态方法,且又都加上了synchronized关键字,此时线程同步时会找类锁。类锁是对于一个类来说只有一把锁,不管创建了多少个对象,类锁都只有一把。

总结:
在静态方法上添加synchronized关键字,实际上是为了保证静态变量的安全;
在实例方法上添加synchronized关键字,实际上是为了保证实例变量的安全。

7.死锁

当有多个线程同时共享多个对象时可能会发生死锁问题:

示例代码:

package threadtest.thread16;

public class ThreadTest16 {
    public static void main(String[] args) {
        Object o1 = new Object();
        Object o2 = new Object();

        Thread t1 = new Thread(new MyRunnable(o1,o2));
        Thread t2 = new Thread(new MyRunnable(o1,o2));
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
}

class MyRunnable implements Runnable{
    private Object o1;

    private Object o2;


    public MyRunnable(Object o1, Object o2) {
        this.o1 = o1;
        this.o2 = o2;
    }

    @Override
    public void run() {
        if(Thread.currentThread().getName().equals("t1")){
            synchronized (o1){
                try {
                    Thread.sleep(1000); //为了产生死锁,这里设置睡眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o2){

                }
            }
        }
        if(Thread.currentThread().getName().equals("t2")){
            synchronized (o2){
                try {
                    Thread.sleep(1000); //为了产生死锁,这里设置睡眠
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (o1){

                }
            }
        }
    }
}

以上代码线程t1先占用o1对象的对象锁,线程t2先占用o2对象的对象锁,而后线程t1又需要寻找o2对象的对象锁,线程t2又需要寻找o1对象的对象锁,由于所需的对象锁分别被对方占用,所以只能陷入无休止的互相等待中。造成死锁。

运行结果(永远无法结束):
在这里插入图片描述
死锁容易发生在synchronized嵌套中,所以对synchronized要慎重使用。

7.1 多线程卖票问题

3个线程同时卖票(不排队):

package threadtest.thread17;

public class ThreadTest17 {
    public static void main(String[] args) {
        //创建一个对象,让多个线程共享一个对象
        Runnable r = new MyRunnable();

        //创建3个线程,模拟3个售票窗口
        Thread t1 = new Thread(r);
        t1.setName("1");

        Thread t2 = new Thread(r);
        t2.setName("2");

        Thread t3 = new Thread(r);
        t3.setName("3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunnable implements Runnable{
    private int ticketNum = 100;
    @Override
    public void run() {
        while (true) {
            if(ticketNum <= 0){
                System.out.println("票已售完!");
                break; //停止售票
            }
            //票还有
            try {
                //出票等待时间
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("窗口" + Thread.currentThread().getName() + "成功售出一张票,当前剩余票数:" + (--ticketNum));
      
        }
    }  
}

运行结果(出现数据问题,剩余票数出现负数):
在这里插入图片描述

排队卖票,即使用线程同步机制:

package threadtest.thread17;

public class ThreadTest17 {
    public static void main(String[] args) {
        //创建一个对象,让多个线程共享一个对象
        Runnable r = new MyRunnable();
        
        //创建3个线程,模拟3个售票窗口
        Thread t1 = new Thread(r);
        t1.setName("1");

        Thread t2 = new Thread(r);
        t2.setName("2");

        Thread t3 = new Thread(r);
        t3.setName("3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class MyRunnable implements Runnable{
    private int ticketNum = 100;
    @Override
    public void run() {
        synchronized (this) { //共享对象就是当前对象
            while (true) {
                if(ticketNum <= 0){
                    System.out.println("票已售完!");
                    break; //停止售票
                }
                //票还有
                try {
                    //出票等待时间
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("窗口" + Thread.currentThread().getName() + "成功售出一张票,当前剩余票数:" + (--ticketNum));
            }
        }
    }
}

运行结果:
在这里插入图片描述

8.线程通信

线程通信的三个方法:wait()、notify()、notifyAll()

  • wait(): 线程执行该方法后,进入等待状态,并且释放对象锁。
  • notify(): 唤醒优先级最高的那个等待状态的线程。【优先级相同的,随机选一个】。被唤醒的线程从当初wait()的位置继续执行。
  • notifyAll(): 唤醒所有wait()的线程。

例如:对于多线程共享的对象obj,调用了obj.wait()之后,在obj对象上活跃的所有线程都进入无期限等待,直到调用了该共享对象的notify()方法进行了唤醒。唤醒后,会接着上一次调用wait()方法的位置继续向下执行。

需要注意的:

  • 这三个方法都是Object类的方法。
  • 以上三个方法在使用时,必须在同步代码块中或同步方法中。
  • 调用这三个方法的对象必须是共享的锁对象。

8.1 wait()和sleep的区别?

相同点: 都会阻塞。
不同点:

  • wait是Object类的实例方法。sleep是Thread的静态方法。
  • wait只能用在同步代码块或同步方法中。sleep随意。
  • wait方法执行会释放对象锁。sleep不会。
  • wait结束时机是notify唤醒,或达到指定时间。sleep结束时机是到达指定时间。

wait()方法有三个重载方法:

  • wait():调用此方法,线程进入“等待状态”;
  • wait(毫秒):调用此方法,线程进入“超时等待状态”;
  • wait(毫秒, 纳秒):调用此方法,线程进入“超时等待状态”。

8.2 两个线程交替输出

要求创建两个线程交替输出1-100如下:

t1–>1
t2–>2
t1–>3
t2–>4
t1–>5
t2–>6

直到100

需要交替输出则需要进行线程间的通信,代码如下:

package threadtest.thread18;

public class ThreadTest18 {
    public static void main(String[] args) {
        Runnable r = new MyRunnable();

        Thread t1 = new Thread(r);
        t1.setName("t1");

        Thread t2 = new Thread(r);
        t2.setName("t2");

        t1.start();
        t2.start();
    }
}

class MyRunnable implements Runnable{

    private int count = 0;

    @Override
    public void run() {
        synchronized (this) {
            while (true) {
                //前面t1释放锁后,t2线程开始占用对象锁,开始执行这里同步代码块的内容,
                //这里需要记得唤醒t1线程
                //t2线程执行过程中把t1唤醒了,但是由于t2仍然占用对象锁,所以即使t1醒了,也不会往下执行,需要等到t2释放对象锁
                this.notify();
                if (count >= 100) {
                    break;
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->" + (++count));
                try {
                    //让其中一个线程等待,这个等待的线程可能是t1,也可能是t2
                    //假设目前执行的是t1线程,则t1线程释放对象锁,进入无限期的等待,直到notify()唤醒
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

8.3 三个线程交替输出

要求创建三个线程交替输出A、B、C如下:

t1–>A
t2–>B
t3–>C
t1–>A
t2–>B
t3–>C

按照以上输出10遍(以上是2遍的示例)

代码如下:

package threadtest.thread19;

public class ThreadTest19 {

    // 三个静态输出标记值,初始值表示第一次输出的时候,t1先输出
    static boolean t1Output = true;
    static boolean t2Output = false;
    static boolean t3Output = false;
    public static void main(String[] args) {
        
        //共享对象(t1、t2、t3线程共享一个对象)
        Object lock = new Object();


        //t1线程,负责输出A
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                //同步代码块
                synchronized (lock){
                    for (int i = 0; i < 10; i++) {
                        while (!t1Output){ //只要不是t1输出,t1Output为false,则让线程进入等待状态
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //轮到t1输出了,并且t1线程被唤醒了
                        System.out.println(Thread.currentThread().getName()+"-->A");

                        //修改布尔标记的值
                        t1Output = false;
                        t2Output = true;
                        t3Output = false;

                        //唤醒所有线程
                        lock.notifyAll();

                    }
                }
            }
        });
        t1.setName("t1");
        t1.start();

        //t2线程,负责输出B
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    for (int i = 0; i < 10; i++) {
                        while (!t2Output){
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //轮到t2输出了,并且t2线程被唤醒了
                        System.out.println(Thread.currentThread().getName()+"-->B");

                        //修改布尔标记的值
                        t1Output = false;
                        t2Output = false;
                        t3Output = true;

                        //唤醒所有线程
                        lock.notifyAll();

                    }
                }
            }
        });
        t2.setName("t2");
        t2.start();

        //t3线程,负责输出C
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock){
                    for (int i = 0; i < 10; i++) {
                        while (!t3Output){
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }

                        //轮到t3输出了,并且t3线程被唤醒了
                        System.out.println(Thread.currentThread().getName()+"-->C");

                        //修改布尔标记的值
                        t1Output = true;
                        t2Output = false;
                        t3Output = false;

                        //唤醒所有线程
                        lock.notifyAll();

                    }
                }
            }
        });
        t3.setName("t3");
        t3.start();
    }
}

8.4 线程通信-生产者和消费者模式

线程通信可以实现生产者和消费者均衡:
在这里插入图片描述

9.线程生命周期的回顾与补充

在这里插入图片描述
完善线程的生命周期图:
在这里插入图片描述

10.单例模式

10.1 饿汉式单例模式

package singleton;

/**
 * 饿汉式单例模式
 */
public class Singleton {
    private static Singleton singleton = new Singleton();

    private Singleton(){

    }

    public static Singleton getSingleton(){
        return singleton;
    }

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1 == singleton2); //true
    }
}

10.2 懒汉式单例模式

package singleton;

/**
 * 懒汉式单例模式
 */
public class Singleton {
    private static Singleton singleton;

    private Singleton(){

    }

    public static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }

    public static void main(String[] args) {
        Singleton singleton1 = Singleton.getSingleton();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1 == singleton2);  //true
    }
}

10.3 懒汉式单例模式可能会造成线程安全问题

package threadtest.thread20;

public class ThreadTest20 {

    //静态变量
    private static Singleton s1;
    private static Singleton s2;

    public static void main(String[] args) {

        //创建线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getSingleton();
            }
        });

        //创建线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getSingleton();
            }
        });

        //启动线程
        t1.start();
        t2.start();

        //保证t1、t2线程在main方法结束前执行(需要线程先start才能起作用)
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //判断两个Singleton对象是否一样。这里为什么不一样啊
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

/**
 * 懒汉式单例模式
 */
class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("构造方法执行了!");
    }

    public static Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

运行结果(出现构造方法执行2次,创建了2个对象):
在这里插入图片描述
解决方案1:同步方法

package threadtest.thread20;

public class ThreadTest20 {

    //静态变量
    private static Singleton s1;
    private static Singleton s2;

    public static void main(String[] args) {

        //创建线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getSingleton();
            }
        });

        //创建线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getSingleton();
            }
        });

        //启动线程
        t1.start();
        t2.start();

        //保证t1、t2线程在main方法结束前执行(需要线程先start才能起作用)
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //判断两个Singleton对象是否一样。这里为什么不一样啊
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

/**
 * 懒汉式单例模式
 */
class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("构造方法执行了!");
    }

        //第一种方法(同步方法):在方法声明处加上synchronized关键字,由于又是静态方法,让线程排队执行,去找类锁
    public static synchronized Singleton getSingleton(){
        if(singleton == null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

运行结果:
在这里插入图片描述

解决方案2:同步代码块

package threadtest.thread20;

public class ThreadTest20 {

    //静态变量
    private static Singleton s1;
    private static Singleton s2;

    public static void main(String[] args) {

        //创建线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getSingleton();
            }
        });

        //创建线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getSingleton();
            }
        });

        //启动线程
        t1.start();
        t2.start();

        //保证t1、t2线程在main方法结束前执行(需要线程先start才能起作用)
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //判断两个Singleton对象是否一样。这里为什么不一样啊
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

/**
 * 懒汉式单例模式
 */
class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("构造方法执行了!");
    }

    //第二种方法(同步代码块):方法内部设置同步代码块,让线程排队执行,也去找类锁
    public static Singleton getSingleton(){
        synchronized (Singleton.class){ //Singleton.class为反射机制中的内容,获取Singleton类
            if(singleton == null){
                singleton = new Singleton();
            }
        }

        return singleton;
    }
}

运行结果:
在这里插入图片描述

解决方案3:针对方案2进行优化,提高效率

package threadtest.thread20;

public class ThreadTest20 {

    //静态变量
    private static Singleton s1;
    private static Singleton s2;

    public static void main(String[] args) {

        //创建线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getSingleton();
            }
        });

        //创建线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getSingleton();
            }
        });

        //启动线程
        t1.start();
        t2.start();

        //保证t1、t2线程在main方法结束前执行(需要线程先start才能起作用)
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //判断两个Singleton对象是否一样。这里为什么不一样啊
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

/**
 * 懒汉式单例模式
 */
class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("构造方法执行了!");
    }

    //第二种方法(同步代码块+外面再嵌套一个if语句,减少一次找类锁的时间)
    public static Singleton getSingleton(){
        if(singleton== null) {
            synchronized (Singleton.class) { //Singleton.class为反射机制中的内容,获取Singleton类
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

解决方案4:使用可重入锁ReentrantLock(看下面介绍)

11.可重入锁ReentrantLock

Java还有一个Lock接口,从JDK5开始引入,Lock接口下有一个实现类:可重入锁(ReentrantLock),其也可以实现线程安全,且比synchronized更推荐使用,因为其更加灵活,可以更细粒度地控制同步代码,但是一定要记住解锁!!!!

注意:要想使用ReentrantLock达到线程安全,假设要让t1、t2、t3线程同步,就需要让t1、t2、t3共享同一个ReentrantLock对象。

语法:
保证多个线程共享一个ReentrantLock对象,比如如下的:private static final ReentrantLock lock = new ReentrantLock();,然后在需要同步的代码块前面加锁,即lock.lock();,并在后面解锁,即lock.unlock();

上述懒汉式单例模式保证线程安全:

package threadtest.thread21;

import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest21 {

    //静态变量
    private static Singleton s1;
    private static Singleton s2;

    public static void main(String[] args) {

        //创建线程t1
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                s1 = Singleton.getSingleton();
            }
        });

        //创建线程t2
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                s2 = Singleton.getSingleton();
            }
        });

        //启动线程
        t1.start();
        t2.start();

        //保证t1、t2线程在main方法结束前执行(需要线程先start才能起作用)
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //判断两个Singleton对象是否一样。这里为什么不一样啊
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s1 == s2);
    }
}

/**
 * 懒汉式单例模式
 */
class Singleton {
    private static Singleton singleton;

    private Singleton(){
        System.out.println("构造方法执行了!");
    }
	
	//创建共享锁对象,需要设置为静态的,才是共享的!
    private static final ReentrantLock lock = new ReentrantLock();

    //第四种方法:使用可重入锁ReentrantLock
    public static Singleton getSingleton(){
        try {
            //加锁
            lock.lock();
            if (singleton == null) {
                singleton = new Singleton();
            }
        } finally {
            //解锁(需要100%保证解锁,所以需要使用finally)
            lock.unlock();
        }
        return singleton;
    }
}

运行结果:
在这里插入图片描述

12.实现线程的第三种方式:实现Callable接口

实现线程的第三种方式:实现Callable接口。这种方式实现线程可以获取到线程的返回值。

package threadtest.thread22;

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

/**
 * 实现线程的第三种方式:实现Callable接口,覆写call()方法
 */
public class ThreadTest22 {
    public static void main(String[] args) {
        //创建“未来任务”对象,设置的泛型为线程的返回类型
        FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception { //与run()方法不一样的是,call方法可以抛出异常
                //处理业务
                Thread.sleep(5000);
                return 1;
            }
        });

        //创建线程对象
        Thread t = new Thread(task);
        t.setName("t");

        //启动线程
        t.start();

        try {
            //获取“未来任务”线程的返回值
            //会阻塞当前线程,等待“未来任务”结束并返回
            //拿到返回值,当前线程的阻塞才会解除,继续执行。(这里则表现为会等待5秒)
            Integer i = task.get();
            System.out.println("t线程返回值:" + i);
        } catch(Exception e){
            e.printStackTrace();
        }
    }
}

运行结果:在这里插入图片描述

13.实现线程的第四种方式:使用线程池

线程池本质上就是一个缓存(cache)。一般都是服务器在启动的时候,初始化线程池,即服务器在启动的时候创建多个线程对象,直接放到线程池中,需要使用线程对象的时候,直接从线程池中获取。

package threadtest.thread23;

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

public class ThreadTest23 {
    public static void main(String[] args) {

        //创建一个线程池对象(线程池中有3个线程)
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        //将任务交给线程池(无需触碰到这个线程对象,只需要将要处理的任务交给线程池即可)
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "-->" + i);
                }
            }
        });

        //最后记得关闭线程池
        executorService.shutdown();

    }
}

运行结果(这里只用到了线程池中的一个线程):在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/515433.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

硬件了解 笔记 2

CPU 内存控制器&#xff1a;负责读写数据 代理系统和平台IO&#xff1a;与主板上的芯片组通信&#xff0c;并管理PC中其他组件之间的数据流 主板&#xff1a;巨大的印刷电路板 Chipset&#xff1a;芯片组&#xff0c;位于散热器下方&#xff0c;直接连接到CPU的系统代理部分 …

应急响应-网站入侵篡改指南Webshell内存马查杀漏洞排查时间分析

知识点 网站入侵篡改防范应对指南 主要需了解&#xff1a;异常特征&#xff0c;处置流程&#xff0c;分析报告等 主要需了解&#xff1a;日志存储&#xff0c;Webshell检测&#xff0c;分析思路等掌握 中间件日志存储&#xff0c;日志格式内容介绍&#xff08;IP,UA头,访问方…

KV260 BOOT.BIN更新 ubuntu22.04 netplan修改IP

KV260 2022.2设置 BOOT.BIN升级 KV260开发板需要先更新BOOT.BIN到2022.2版本&#xff0c;命令如下&#xff1a; sudo xmutil bootfw_update -i “BOOT-k26-starter-kit-202305_2022.2.bin” 注意BOOT.BIN应包含全目录。下面是更新到2022.1 FW的示例&#xff0c;非更新到2022.…

IPSec VPN

IP Security,IP安全 1、特点 L3的VPN 缺:不支持组播、配置复杂、延迟增加、资源消耗较多 优:具备访问控制、密码学四个维度、抗重放打击 2、组件 ①安全协议 1)验证头技术(AH) IP协议号51 提供数据完整性检查,身份验证,抗重放攻击 无法做数据的机密性 AH的完…

【异常错误】 Expected to have finished reduction in the prior iteration before star、find_unused_parameters

运行代码时出现了错误&#xff1a; RuntimeError: Expected to have finished reduction in the prior iteration before starting a new one. This error indicates that your module has parameters that were not used in producing loss. You can enable unused parameter …

【Django学习笔记(四)】JavaScript 语言介绍

JavaScript 语言介绍 前言正文1、JavaScript 小案例2、代码位置2.1 在当前 HTML 文件中2.2 在其他 js 文件中 3、代码注释3.1 HTML的注释3.2 CSS的注释3.3 Javascript的注释 4、变量 & 输出4.1 字符串4.2 数组4.3 对象(python里的字典) 5、条件语句6、函数7、DOM7.1 根据 I…

【树上倍增】【内向基环树】【 图论 】2836. 在传球游戏中最大化函数值

本文涉及知识点 树上倍增 内向基环树 图论 LeetCode2836. 在传球游戏中最大化函数值 给你一个长度为 n 下标从 0 开始的整数数组 receiver 和一个整数 k 。 总共有 n 名玩家&#xff0c;玩家 编号 互不相同&#xff0c;且为 [0, n - 1] 中的整数。这些玩家玩一个传球游戏&am…

ideaSSM图书借阅管理系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 SSM 图书借阅管理系统是一套完善的信息管理系统&#xff0c;结合SSM框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码 和数据库&#xff0c;系统主…

【爬虫+数据清洗+数据分析+可视化】“淄博烧烤”现象热评舆情python数据分析大屏

一、开发背景 您好&#xff0c;我是马哥小迷弟132 &#xff0c;一枚10年程序猿。 自从2023.3月以来&#xff0c;"淄博烧烤"现象持续占领热搜流量&#xff0c;体现了后疫情时代众多网友对人间烟火气的美好向往&#xff0c;本现象级事件存在一定的数据分析实践意义。…

Java零基础入门-java8新特性(上篇)

一、本期教学目标 java8有哪些新特性什么是函数式接口什么是Lambda表达式掌握Stream ApiStream和Collect集合区别Stream创建方式Stream操作三步骤 二、概述 上几期&#xff0c;我们是完整的学完了java异常类的学习及实战演示、以及学习了线程进程等基础概念&#xff0c;而这一…

Cache多核之间的一致性MESI

快速链接: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; 思考: 1、为什么要学习MESI协议&#xff1f; 哪里用到了&#xff1f;你确定真的用到了&#xff1f; 2、MESI只是一个协议&#xff0c;总得依赖一个硬件去执行该协议吧&#xff0c…

电商技术揭秘一:电商架构设计与核心技术

文章目录 引言一、电商平台架构概述1.1 架构设计原则与架构类型选择1.2 传统电商平台架构与现代化架构趋势分析 二、高并发处理与负载均衡2.1 高并发访问特点分析与挑战2.2 负载均衡原理与算法选择 三、分布式数据库与缓存技术3.1 分布式数据库设计与一致性考量3.2 缓存策略与缓…

C++实现二叉搜索树的增删查改(非递归玩法)

文章目录 一、二叉搜索树的概念结构和时间复杂度二、二叉搜索树的插入三、二叉搜索树的查找四、二叉搜索树的删除&#xff08;最麻烦&#xff0c;情况最多&#xff0c;一一分析&#xff09;3.1首先我们按照一般情况下写&#xff0c;不考虑特殊情况下4.1.1左为空的情况&#xff…

分享:搭建企微知识库简单易学步骤

说起企微知识库&#xff0c;可能有些人还不太清楚&#xff0c;为什么现在很懂企业选择搭建企微知识库&#xff1f;其实&#xff0c;企微知识库就是一个装满了企业的各种知识、经验和资料的载体。目的是为了方便员工随时查找和学习、有助于知识的传承和共享、加强团队协作和沟通…

自然语言处理: 第二十一章大模型基底之llama2

文章地址: LLaMA:OpenandEfficient Foundation Language Models 项目地址: meta-llama/llama: Inference code for Llama models (github.com) 前言 在LLaMa1的基础之上有兴趣的可以看看我上一篇博客自然语言处理: 第二十一章大模型基底之llama1。Meta 又继续推出了LLaMa2&a…

windows安装OpenUSD

一、下载OpenUSD git clone https://github.com/PixarAnimationStudios/OpenUSDOpenUSD&#xff0c;原名USD&#xff08;Universal Scene Description&#xff0c;通用场景描述&#xff09;&#xff0c;是由皮克斯动画工作室开发的一种开放数据格式。OpenUSD主要用于在虚拟世界…

AI论文速读 |【综述】 时序分析基础模型:教程与综述

论文标题&#xff1a;Foundation Models for Time Series Analysis: A Tutorial and Survey 作者&#xff1a; Yuxuan Liang&#xff08;梁宇轩&#xff09;, Haomin Wen&#xff08;温浩珉&#xff09;, Yuqi Nie&#xff08;PatchTST一作&#xff09;, Yushan Jiang, Ming J…

机器学习全攻略:概念、流程、分类与行业应用案例集锦

目录 1.引言 2.从零开始认识机器学习&#xff1a;基本概念与重要术语 3.五步走&#xff1a;掌握机器学习项目执行的完整流程 3.1.问题定义与数据收集 3.2.数据预处理与特征工程 3.3.模型选择与训练 3.4.模型评估与优化 3.5.模型部署与监控 4.深入了解各类机器学习方法…

计算机网络—TCP协议详解:特性、应用(2)

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;マリンブルーの庭園—ずっと真夜中でいいのに。 0:34━━━━━━️&#x1f49f;──────── 3:34 &#x1f504; ◀️…

基于卷积神经网络的苹果等级分类系统(pytorch框架)【python源码+UI界面+前端界面+功能源码详解】

功能演示&#xff1a; 苹果等级分类系统&#xff0c;基于vgg16&#xff0c;resnet50卷积神经网络&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神经网络的苹果等级分类系统是在pytorch框架下实现的&#xff0c;系统中有两…