多线程的使用、同步和异步、synchronized、线程安全的单例模式、死锁、解决死锁

DAY6.1 Java核心基础

多线程

线程同步

java中是允许线程并行访问的,线程在同一时间段的时候会完成各自的操作

为什么要使用线程同步

示例代码:

创建一个Account类来统计一个访问量

java 复制代码
public class Account implements Runnable{
    private int num = 0;
    @Override
    public void run() {
        num++;
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"是第 "+num+" 位访客");
    }
}
java 复制代码
public static void main(String[] args) {
    Account account = new Account();
    Thread thread1 = new Thread(account);
    Thread thread2 = new Thread(account);
    thread1.setName("张三");
    thread2.setName("李四");
    thread1.start();
    thread2.start();
}

输出:

可以看见张三和李四都是第2位访客,为什么呢,因为先进来的线程在遇到了Thread.sleep(200)休眠导致线程阻塞,然后另外一个线程现在也进来了,最后导致num++执行了两次,之后两个线程再执行的各自的输出

怎么解决呢?

是因为休眠导致的吗?答案是否定的

就算把休眠关闭,还是会有几率导致num++执行两次,因为首先进入程序的线程如果再num++之后被阻塞了,后面进来的线程抢占了CPU资源,然后之后的线程执行了num++,还是会导致num++两次

知识小点

  • 同步:多个线程按顺序执行,每次只能一个线程执行,执行完毕之后才能让第二个线程开始执行
  • 异步:多个线程同时执行,不需要排队按顺序执行

解决方法:加线程锁,这里只讲最简单的synchronized关键字

简单来说就是一个ATM机取钱,然后每个线程进去取钱的时候会关门锁上,然后等待取完钱再开门,别人才能进来

synchronized:同步关键字

synchronized 修饰实例方法 (非静态方法,没有static修饰的方法)

形参:上锁资源,钥匙

java 复制代码
@Override
public synchronized void run() {
    try {
        num++;
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
}

加完锁之后再运行就正常输出了

synchronized 修饰静态方法

java 复制代码
public class Account implements Runnable {
    private int num = 0;

    @Override
    public synchronized void run() {
        try {
            num++;
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
    }
    public synchronized static void test(){
        System.out.println("begin...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("end.......");
    }
}
java 复制代码
public static void main(String[] args) {
    new Thread(Account::test).start();
    new Thread(Account::test).start();
    new Thread(Account::test).start();
    new Thread(Account::test).start();
}

synchronized 修饰代码块

上面锁方法的时候效率不是很高,因为锁住整个方法会导致整个方法都是同步执行的,而其实有些无关代码可以设置为异步执行,效率更高

java 复制代码
@Override
public void run() {
    System.out.println("不需要锁的代码");
    System.out.println("不需要锁的代码");
    synchronized (this){
        try {
            num++;
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName() + "是第 " + num + " 位访客");
    }
    System.out.println("不需要锁的代码");
    System.out.println("不需要锁的代码");
}

线程安全的单例模式

是一种常见的设计模式

核心思想:一个类只能有一个实例方法,多个线程共享实例对象,spring里面的bean对象就是单例模式

1、将构造函数私有化 private

2、提供静态方法来返回实例化对象

java 复制代码
public class SingleDemo {
    private static SingleDemo singleDemo;

    private SingleDemo() {
        System.out.println("创建了SingleDemo实例化对象");
    }

    public static SingleDemo getInstance() {
        synchronized (SingleDemo.class) {
            if (singleDemo == null)
                singleDemo = new SingleDemo();
        }
        
        return singleDemo;
    }
}

测试类创建10个线程异步执行,测试是否为单例

java 复制代码
public static void main(String[] args) {
    for (int j=0;j<10;j++) {
        new Thread(()->{
            for (int i = 0; i < 5; i++)
                System.out.println(SingleDemo.getInstance());
        }).start();
    }
}

部分输出

可以看见每一个SIngleDemo地址都是一样的,只创建了一个对象

死锁

使用synchronized 可以实现线程同步,解决并行访问的数据安全问题

但是也可能带来新的问题

死锁就是线程争夺同一个资源而带来的互斥问题

简单来说:死锁就是老王和老李想看有些画,然后老王正在看蒙娜丽莎,老李在看向日葵,然后老王不满足,在看蒙娜丽莎的同时也要看向日葵,不然就不走了,同时老李也是在看向日葵的时候也要看蒙娜丽莎,不然也不走,这时候就形成了死锁,资源不释放,谁都得不到资源,所以线程都处于阻塞状态

如何解决死锁

某个线程做出让步,贡献自己的资源给其它资源使用

示例代码:

区分老王和老李

java 复制代码
public class TastRunnable implements Runnable{
    private String name;
    private static Draw draw1 =new Draw("蒙娜丽莎");
    private static Draw draw2 =new Draw("向日葵");

    public TastRunnable(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if(name.equals("老王")){
            //老王需要获取蒙娜丽莎
            synchronized (draw1){
                try {
                    System.out.println("老王获取了蒙娜丽莎,等待获取向日葵");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //老王还想获取向日葵
                synchronized (draw2){
                    System.out.println("老王获取了蒙娜丽莎和向日葵");
                }
            }

        }else if(name.equals("老李")){
            //老李获取向日葵
            synchronized (draw2){
                try {
                    System.out.println("老李获取了向日葵,等待获取蒙娜丽莎");
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //老李还想获取蒙娜丽莎
                synchronized (draw1){
                    System.out.println("老李获取了蒙娜丽莎和向日葵");
                }
            }
        }
    }
}
java 复制代码
public static void main(String[] args) {
    TastRunnable task1 = new TastRunnable("老王");
    TastRunnable task2 = new TastRunnable("老李");
    Thread thread1 = new Thread(task1);
    Thread thread2 = new Thread(task2);
    thread1.start();
    thread2.start();
}

出现死锁的情况,程序还在运行,但是卡住了

如何解决

解决方法1:

不要让他们一起执行,让老王和老李某一个慢一点启动,在主线程加一个休眠就行了

java 复制代码
public static void main(String[] args) {
    TastRunnable task1 = new TastRunnable("老王");
    TastRunnable task2 = new TastRunnable("老李");
    Thread thread1 = new Thread(task1);
    Thread thread2 = new Thread(task2);
    thread1.start();
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    thread2.start();
}

结果:

相关推荐
小狗很可爱2 分钟前
构建一个Django的应用程序
python·django·sqlite
曹天骄30 分钟前
Spring Boot Gradle 项目中使用 @Slf4j 注解
java·spring boot·后端
老胖闲聊37 分钟前
Python Flask框架学习汇编
汇编·python·学习·flask
虾球xz40 分钟前
游戏引擎学习第133天
java·学习·游戏引擎
HerrFu44 分钟前
可狱可囚的爬虫系列课程 16:爬虫重试机制
爬虫·python
吉星9527ABC1 小时前
Linux下的c进程和java进程的通信-UnixSocket
java·linux·c语言·unixsocket通讯
李卓璐1 小时前
vscode远程ssh链接服务器
vscode·python
浪九天1 小时前
Java常用正则表达式(身份证号、邮箱、手机号)格式校验
java·开发语言·正则表达式
恋恋西风1 小时前
vtk 3D坐标标尺应用 3D 刻度尺
python·3d·vtk·pyqt
赔罪1 小时前
Python 面向对象高级编程-定制类
服务器·开发语言·前端·python