首页 > 其他分享 >多线程学习第四篇

多线程学习第四篇

时间:2023-09-02 23:12:21浏览次数:40  
标签:同步 Thread synchronized 学习 线程 new 第四篇 多线程 public

4、线程同步机制

  • 并发:同一对象被多个线程同时操作(抢票)
  • 线程同步是一个等待机制,多个需要同时访问次对象的线程进入这个对象的等待池形成队列,等待前一个线程使用完毕,下一个线程才能使用。

形成线程安全的条件:队列和锁

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制

synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

存在以下问题:

一个线程持有锁会导致其他所有需要此锁的线程挂起;

在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;

如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题.

(提高了安全性,必然会引起性能问题)

4.1、线程不安全举例

4.1.1、不安全的售票

package com.thread.unsafe;

//线程不安全,买票重复
public class UnsafeBuyTicket {

    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();


        new Thread(buyTicket, "我").start();
        new Thread(buyTicket, "你").start();
        new Thread(buyTicket, "他").start();
    }
}

class BuyTicket implements Runnable {
    //票
    private int ticket = 10;
    //外部停止标志
    boolean flag = true;


    @Override
    public void run() {
        //买票
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // synchronized:同步方法,锁的是this
    public synchronized void buy() throws InterruptedException {
        if (ticket <= 0) {
            flag = false;
            return;
        }
        //模拟延时
        Thread.sleep(100);

        System.out.println(Thread.currentThread().getName() + "买到" + ticket--);
    }
}

4.1.2、不安全取钱

package com.thread.unsafe;

//取钱线程不安全
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(1000, "结婚基金");
        Drawing you = new Drawing(account, 500, "你");
        Drawing girlFriend = new Drawing(account, 1000, "girlFriend");


        you.start();
        girlFriend.start();

    }
}


class Account {
    //账户名称
    String name;
    //账户余额
    double money;

    public Account(double money, String name) {
        this.name = name;
        this.money = money;
    }

}


class Drawing extends Thread {

    Account account;//银行账户

    double drawingMoney;//取了多少钱

    double nowMoney;//剩余多少钱


    public Drawing(Account account, double drawingMoney, String name) {
        this.account = account;
        this.drawingMoney = drawingMoney;
        super.setName(name);
    }


    //取钱
    @Override
    public void run() {

        //synchronized块:锁的是变化的量
        synchronized (account) {

            if (account.money - drawingMoney < 0) {
                System.out.println("卡内余额不足,取不了!");
                return;
            }


            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            // 卡内余额 = 余额–你取的钱
            account.money = account.money - drawingMoney;

            //你手里的钱
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name + "余额为:" + account.money);

            //Thread.currentThread().getName() == this.getName()
            System.out.println(this.getName() + "手里的钱" + nowMoney);

        }
    }

}

4.1.3、不安全的集合

package com.thread.unsafe;

import java.util.ArrayList;
import java.util.List;

//线程不安全的集合
public class UnsafeList {
    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(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

4.2、同步方法及同步块

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized
  • 同步方法 public synchronized void method(int args){ }
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

缺陷:若将一个大的方法申明为 synchronized 将会影响效率。

synchronized 块:

同步块:synchronized(Obj){}

Obj 称之为同步监视器

  • Obj 可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this ,就是这个对象本身,或者是 class
  • 同步监视器的执行过程
  1. 第一个线程访问,锁定同步监视器,执行其中的代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

4.3、死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

某一个同步块同时拥“两个以上对象的锁”时,就可能会发生“死锁”的问题。

产生死锁的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上述四个条件,只要破坏其任意一个或多个条件就可避免死锁的发生

案例:

package com.thread.lock;

// 死锁:多个线程互相拥有对方需要的资源,形成僵持
public class DeadLock {
    public static void main(String[] args) {
        Makeup moore = new Makeup(0, "白雪公主");
        Makeup dove = new Makeup(1, "皇后");
        moore.start();
        dove.start();
    }
}

class Lipstick{
}
class Mirror{
}

class Makeup extends Thread{

    // 需要的资源只有一份,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice; // 选择
    String girlName; // 使用化妆品的人

    Makeup(int choice, String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }
    @Override
    public void run() {
        // 化妆
        try {
            Makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 化妆 互相持有对方的锁,就是需要拿到对方的资源
    private void Makeup() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){
                // 获得口红的锁
                System.out.println(this.girlName + "获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){
                // 获得镜子的锁
                System.out.println(this.girlName + "获得镜子的锁");
            }
        }else{
            synchronized (mirror){
                // 获得口红的锁
                System.out.println(this.girlName + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick){
                // 获得镜子的锁
                System.out.println(this.girlName + "获得口红的锁");
            }
        }
    }
}

4.4、lock锁

从 JDK 5.0 开始,Java 提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。

同步锁使用 Lock对象充当java.util.concurrent.locks.Lock 接口,是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是 ReentrantLock ,可以显示加锁释放锁。

案例:

package com.thread.lock;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    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 {

    int ticketNums = 10;

    //定义lock锁:可重入锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//加锁
                if (ticketNums > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                } else {
                    break;
                }
            } finally {
                lock.unlock();//解锁
            }
        }
    }
}

synchronized 与 Lock 的对比:

  • Lock是显示锁,需要手动开启和关闭,synchronized为隐式锁,出了作用域自动释放
  • lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用lock锁,jvm将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性
  • 优先使用顺序
    • Lock > 同步代码快(已经进入方法体,分配了相应资源)> 同步方法(在方法体之外)

标签:同步,Thread,synchronized,学习,线程,new,第四篇,多线程,public
From: https://www.cnblogs.com/wangshow/p/wangshow_3.html

相关文章

  • 多线程学习第五篇
    5、线程协作(线程通信)应用场景:生产者和消费者问题假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。如果仓库中没有产品,则将生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以......
  • 多线程学习第三篇
    3、线程状态线程五大状态:创建状态:通过new创建线程就绪状态:通过start()启动线程进入就绪状态阻塞状态:通过CPU调配进入运行状态运行状态:在运行状态时,可以进行如sleep,wait等方法使线程进入阻塞状态死亡状态:自然执行完毕、外部干涉终止线程具体流程为:3.1、线程的常用......
  • 设计模式学习1 设计原则
    设计原则1.开闭原则对扩展开放,修改关闭。在程序需要扩展的时候,不能去修改原有代码,实现一个热插拔的效果。为了使程序的扩展性好,易于维护和升级为了达到这样的效果,我们需要使用接口和抽象类2.里氏代换原则任何基类可以出现的地方,子类一定可以出现。也就是子类继承父类时,除了添......
  • 多线程第一篇(认识多线程)
    多线程任务,进程,线程,多线程Process:进程Thread:线程1、基本概念进程在操作系统中运行的程序就是进程。程序是指令和数据的有序集合,其本身没有任何运行的含义,是静态的。进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位通常再一个进程中可......
  • 魔鬼冲刺学习笔记
    \[\huge{\textbf{魔鬼冲刺}\quad\textbf{2023.8.31-?}}\]高二是大部分OIer的最后一段竞赛时光,这真是“\(One\Last\Olympiad\)”了。所以我们开始魔鬼冲刺了!这里就用来记录这段时期的一些收获,还有学到的知识。由于停课后学习笔记给人的感觉略显凌乱,故在本文中笔者简......
  • 每周总结-第八周 多线程
    多线程概述:充分利用计算机资源,同时执行不同的操作1.计算机操作系统进程和线程2.使用java来完成多线程的编码3.线程中的常用方法4.线程同步(重点)5.死锁6.生产者消费者模型异步操作系统简介操作系统:本质上就是一个运行在一堆硬件上的巨型软件没有操作系统的话,程序想要操控......
  • 《C和指针》学习笔记
    C和指针学习笔记前置条件1.1配置环境下载vscode安装编译器:这里以MinGw-w64为例。下载MinGw-w64的安装包并解压。添加到系统环境编辑tasks.json(该文件负责项目的编译,如果需要同时编译多个文件,需要对该文件进行如下注释内的修改):{"tasks":[{......
  • 折半搜索 学习笔记
    关于算法折半搜索,又称meetinthemiddle算法。顾名思义,就是将整个搜索的过程分成两个部分分别进行搜索,然后再将两个部分搜索出来的答案进行合并,得到最终的答案。dfs搜索算法一般都是指数级别的,那么我们假如每次dfs时都有两种决策,那么我们执行dfs算法的时间复杂度为\(O......
  • 【学习笔记】二分图基础
    二分图与网络流基础(网络流待学)查看目录目录前置知识:二分图:二分图的定义:二分图的判定:例题:[NOIP2010提高组]关押罪犯二分图的匹配:匈牙利算法:例题:[ABC317G]Rearranging[ABC317G]Rearranging前置知识:tarjan强连通分量:有向图中几个点可以相互到达,就称这几个点是强连通......
  • 莫队学习笔记(如何处理增量)
    题目传送门:序列考虑我们已经求出了区间\([l,r]\)的答案,现在要求\([l,r+1]\)的答案。很明显增多的子序列有\((l,r+1),(l+1,r+1)...(r+1,r+1)\)。考虑求出\([l,r+1]\)中的最小值的位置\(p\)(可以用\(rmq~O(1)\)求出),那么\(a_p\)的贡献就是\(a_p\times(p-l+1)\),现在我......