Java——多线程和锁

news2024/12/23 4:40:31

多线程

前言:当我们打开一个网站时,不同部分的加载并不是先后出现的,是并行出现的,没有出现一个地方没加载完,别的地方就也加载不出来这种事。这个就是多线程并行运行。

当其中一个线程发生阻塞时,操作系统会自动执行新的线程保证cpu不会闲置???

实现方式1:

继承一个Thread类并重写其中的run()函数

对于想要实现的多线程逻辑直接写在run函数里面即可 

class worker extends Thread {
    @Override
    public void run(){
        
        for(int i=0;i<10;i++)
        {
            System.out.println("Helo!"+this.getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        
    }
}

在main函数中实现两个线程并启动

package test;

import static java.lang.Thread.sleep;

class worker extends Thread {
    @Override
    public void run(){

        for(int i=0;i<10;i++)
        {
            System.out.println("Helo!"+this.getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

public class test {
    public static void main(String[] args) throws InterruptedException {
        worker w1=new worker();
        worker w2=new worker();
        w1.setName("线程1");
        w2.setName("线程2");
        w1.start();
        w2.start();
        for(int i=0;i<10;i++)
        {
            System.out.println("这是主线程");
            sleep(1000);
        }
    }
}

原本的main方法就是一个主线程,当执行到w1.start时就会新开一个线程,执行到w2.start时会再新开一个线程,start方法会自动调用run()方法。然后就会有三个线程在同时执行。

运行代码的输出结果如下图所示,可以看见三个线程在先后的运行,在cpu当中运行时则是一个一个线程进cpu运行输出结果,在外面看起来就是差不多同时。

 实现方法2:

实现Runnable接口

用这个方法实现有多少种线程就要创建多少个Runnable的实现类,并且这个里面是没有setName方法。在开启线程时需要new一个实现类的实例作为参数传进Thread()里面,,然后调用start()方法,会自动运行run()函数。

要么也可以共用一个实例,上面继承的写法是每一个都要有一个实例。

共用一个实例更好实现下面的锁的同步机制

package test;

class worker1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i ++ ) {
            System.out.println("Hello! " + "thread-1");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
class worker2 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i ++ ) {
            System.out.println("Hello! " + "thread-2");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class test {
    public static void main(String[] args) throws InterruptedException {
        new Thread(new worker1()).start();
        new Thread(new worker2()).start();
    }
}

常用的API 

start():开启一个线程
Thread.sleep(): 休眠一个线程
join():等待线程执行结束  //等当前线程执行结束后才会继续往下执行,前提是该线程已经开始执行了,也可以设定等待时间
interrupt():从休眠中中断线程 //给当前线程发送一个中断,至于线程如何处理就要看run函数的内部是怎么样的,这个方法只有在run函数内有抛出中断时才有影响。
setDaemon():将线程设置为守护线程。当只剩下守护线程时,程序自动退出,java中有一个垃圾回收机制,当所有用户的线程结束之后就要自动结束,守护线程就可以用于设置一些没用的线程,当别的有用的线程都结束了就自动结束这些无用的 线程。

在如下的代码当中w2设置为了守护线程,w1设置了一个中断结束,因此在w1结束的时候w2也会一起结束。

package test;

import static java.lang.Thread.sleep;

class worker extends Thread {
    @Override
    public void run(){

        for(int i=0;i<10;i++)
        {
            System.out.println("Helo!"+this.getName());
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(e.getMessage());
                break;
            }
        }
    }
}

public class test {
    public static void main(String[] args) throws InterruptedException {
        worker w1=new worker();
        worker w2=new worker();
        w1.setName("线程1");
        w2.setName("线程2");
        w2.setDaemon(true);
        w1.start();
        w2.start();
        w1.interrupt();

    }
}

输出如下所示

锁锁锁——锁锁world new new!

锁的必要性:对于一个整型变量cnt=5,现在有两个操作同时发生,一个是读,一个是写(自增1)

先读后写读到的就是5

先写后读,读到的就是6。正常情况下不能确定是哪一个结果,我们要的是哪一个结果也不能确定。

锁的意义在这里就体现了出来,当一个线程在对一个变量进行操作时不允许别的线程也同时过来操作。

在上面的多线程也可以实现这一点,使用join函数保证在当前线程结束前别的线程不会启动。

如在下面这个多线程实现类当中,所有实例都共享一个锁和一个cnt属性,一个线程在的执行cnt++之前都会先上锁,不让别的线程修改cnt的值,直到cnt获得了新值之后才会解锁。

class worker extends Thread {

    private static final ReentrantLock lock=new ReentrantLock();
    private static int cnt=0;

    @Override
    public void run(){

        for(int i=0;i<10;i++)
        {
            lock.lock();
            try{
                cnt++;
            }finally {
                lock.unlock();
            }
        }
    }
}
public class test {
    public static void main(String[] args) throws InterruptedException {
        worker w1=new worker();
        worker w2=new worker();
        w1.setName("线程1");
        w2.setName("线程2");
        w2.setDaemon(true);
        w1.start();
        w2.start();
        w1.join();
        w2.join();
        System.out.println(w1.getcnt());
    }
}

上面代码执行输出如下

成功执行了20次

下面是没加锁的情况 

在操作系统中使用pv操作实现

锁的语法糖

同步(Synchronized)

synchronized(对象) {代码}:当加上这个之后,任何想要访问这个对象的线程都会被阻塞。直到里面的代码执行完成。

写法1:将Synchronized加到代码块上

如下的代码就是锁住了一段代码的count属性,这个count参数是被所有worker实例线程共享的,直到该段代码执行完毕或锁被释放才会执行别的线程的这段代码

package test;

class Count{
    public int cnt=0;
}
class worker extends Thread {

  public    final Count count;
  public worker(Count count){
      this.count=count;
  }
    @Override
    public void run(){

      synchronized (count) {
          for (int i = 0; i < 10000; i++) {
              count.cnt++;
          }
      }
    }
}

public class test {
    public static void main(String[] args) throws InterruptedException {
        Count count=new Count();
        worker w1=new worker(count);
        worker w2=new worker(count);
        w1.setName("线程1");
        w2.setName("线程2");
        w2.setDaemon(true);
        w1.start();
        w2.start();
        w1.join();
        w2.join();
        System.out.println(w1.count.cnt);
    }
}

写法2:将Synchronized加到函数上(锁加到了this对象上)

如果是使用继承写法加到函数上的话像下面

class worker extends Thread {
    public static Integer cnt=0;

    private synchronized  void work(){
        
            for (int i = 0; i < 10000; i++) {
                cnt = cnt + 1;
            }
        
    }
    @Override
    public void run(){
      work();
    }
}

输出结果是错误的

上面的等价于如下的写法

    private  void work(){
        synchronized (this) {
            for (int i = 0; i < 10000; i++) {
                cnt = cnt + 1;
            }
        }
    }

 synchronized里面的参数时是当前这个线程的对象,因为有两个线程,两个对象,所以实际是不会互相影响的,所以在函数上加synchronized不适用于这种多个对象的线程

使用接口形式实现的同一个对象可以运行在多个不同的线程上,接口实现的多线程创建一个实例传进两个线程里面运行,这时候synchronized加在函数上面就可以提现其作用了。

package test;

class Worker implements Runnable {
    public static int cnt = 0;

    private synchronized void work() {
        for (int i = 0; i < 100000; i ++ ) {
            cnt ++ ;
        }
    }

    @Override
    public void run() {
        work();
    }
}

public class test {
    public static void main(String[] args) throws InterruptedException {
        Worker worker = new Worker();
        Thread worker1 = new Thread(worker);
        Thread worker2 = new Thread(worker);

        worker1.start();
        worker2.start();
        worker1.join();
        worker2.join();

        System.out.println(Worker.cnt);
    }
}

 wait与notify

一个是等待一段时间,一个是从等待中退出

needWait=true表示当前线程需要等待

下面代码的逻辑是先开启了五个线程,都被阻塞在了同一个地方,也就是下面的odject.wait()那里,然后使用一个新的线程来随机唤醒一个旧的线程,一个旧线程被唤醒会先输出一个被唤醒的信息,然后经过一秒再去唤醒其他随机一个线程。

也可以使用notifyAll()直接唤醒所有被睡眠的线程。

wait()当中可以传参进去,wait(1000)表示睡眠一秒就自动唤醒了。

package test;

class worker extends Thread {
    private static final Object object=new Object();
    private final boolean needWait;
    public worker(boolean needWait){
        this.needWait=needWait;
    }

    @Override
    public void run() {
        synchronized (object){
                try {
                    if(needWait) {
                        System.out.println(this.getName()+"婴儿般的睡眠");
                        object.wait();//进入等待后就一直卡在这里
                        System.out.println(this.getName()+"被唤醒了");//直到的外部有操作来唤醒了
                        Thread.sleep(1000);
                        object.notify();
                    }else{
                            object.notify();//全部唤醒 ,单一个notify就是随便唤醒一个
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }
    }
}

public class test {
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++)
        {
            worker worker=new worker(true);
            worker.setName("thread-"+i);
            worker.start();
        }
        worker worker=new worker(false);  //传个false进去唤醒其余线程
        worker.setName("thread-救世主");
        worker.start();
    }
}

Thread.sleep(1100)在哪个线程使用就睡眠哪个线程。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统集成项目管理工程师 笔记(第12章:项目沟通管理和干系人管理)

文章目录 12.1.2 沟通的方式 404沟通管理计划的编制过程12.2.2 制订沟通管理计划的工具 4114、沟通方法 12.3.2 管理沟通的工具 41312.4.2 控制沟通的技术和方法 4163、会议 12.5.1 项目干系人管理所涉及的过程 420项目干系人管理的具体内容&#xff1a;&#xff08;1&#xff…

C/C++文件操作/IO流

学习任务&#xff1a; ⭐认识文件。⭐学习C语言中文件如何打开和关闭。⭐学习C语言中文件的读写方法&#xff08;包括顺序读写和随机读写&#xff09;。⭐学习C语言文件操作中如何判断文件读取结束。⭐简单了解FILE缓冲区。⭐认识流。⭐学习C的IO流&#xff0c;包括标准IO流和文…

【CMake】给一个库添加用法需求(Usage Requirements)

3. 给一个库添加用法需求&#xff08;Usage Requirements&#xff09; 1. usage requirements 目标参数的用法要求&#xff08;不知道有没有更好的翻译方式&#xff09;可以更好地控制库或可执行的链接&#xff0c;并包括行&#xff0c;同时还可以更好地控制CMAKE内部目标的传…

【哈士奇赠书活动 - 17期】-〖uni-app跨平台开发与应用从入门到实践〗

文章目录 ❤️‍&#x1f525; 赠书活动 - 《uni-app跨平台开发与应用从入门到实践》❤️‍&#x1f525; 编辑推荐❤️‍&#x1f525; 抽奖方式与截止时间❤️‍&#x1f525; 赠书活动 → 获奖名单 ❤️‍&#x1f525; 赠书活动 - 《uni-app跨平台开发与应用从入门到实践》…

日志技术-Logback

一. 日志是什么&#xff1a; 输出语句的弊端&#xff1a;它只能在控制台展示&#xff0c;它不能记录在本地文件当中。 日志可以将程序运行过程中的信息直接记录在文件里面&#xff0c;做永久存储。 性能较好&#xff0c;简单来说就是运行的速度会比较快。 二. 日志技术体系、L…

3_docker应用部署:MySQL-Tomcat-Nginx-Redis

Docker 应用部署 一、部署MySQL 案例需求&#xff1a;在Docker容器中部署MySQL&#xff0c;并通过外部mysql客户端操作MySQL Server。 分析&#xff1a; 容器内的网络服务和外部机器不能直接通信 外部机器和宿主机可以直接通信 宿主机和容器可以直接通信 当容器中的网络服…

iOS 17 开放侧载,微信双开要来了?

关于苹果北京时间 6 月 6 日召开的 WWDC23 全球开发者大会&#xff0c;外媒彭博记者 Mark Gurman 带来了最新消息。 大致更新概览 他认为&#xff0c;本次 WWDC23 大会将会主要推出这几款产品和软件&#xff1a;iOS 17、iPadOS 17、macOS 14、watchOS 10、新的 MacBooks 、混…

文件上传下载系列——如何实现文件秒传

文章目录 &#x1f383;简介&#xff1a; &#x1f47b;核心思想&#xff1a; MD5是什么&#xff1f; 实现步骤&#xff1a; &#x1f384;实操&#xff1a; 1、java生成文件MD5码 2、javascript生成文件MD5码 ⛳️基于秒传的分片上传下载 上传&#xff1a; 下载&…

Afkayas.1(★)

软件运行 要输入正确的Name和Serial 查壳 一个VB程序&#xff0c;没有加壳 载入OD 直接开搜索字符串。 这里看到了错误的提示&#xff0c;“You Get It”应该就是成功的字符串了。 前面的“AKA-”应该是在什么时候拼接的字符串 去成功的字符串附近看看 这个字符串上面…

LVS负载均衡群集——DR模式

一、LVS-DR集群介绍 LVS-DR&#xff08;Linux Virtual Server Director Server&#xff09;工作模式&#xff0c;是生产环境中最常用的一 种工作模式。 1、LVS-DR 工作原理 LVS-DR 模式&#xff0c;Director Server 作为群集的访问入口&#xff0c;不作为网关使用&#xff0…

第九章 子查询

文章目录 前言一、.需求分析与问题解决1 、实际问题2 、子查询的基本使用3 、子查询的分类 二、单行子查询1、单行比较操作符2、代码示例3、 HAVING 中的子查询4、CASE中的子查询5、 子查询中的空值问题6、非法使用子查询 三、多行子查询1、 多行比较操作符2、代码示例3 、空值…

客快物流大数据项目(一百一十八):配置中心 Spring Cloud Config

文章目录 配置中心 Spring Cloud Config 一、​​​​​​​Config 简介

STM32-HAL-SPI-W25Q128FV简单读写测试(2)

文章目录 一、Flash的基本读写操作1.1 向芯片中的某个地址&#xff08;addr:0x02&#xff09;连续写入不定长的数据并读取代码示例读写流程分析函数分析 1.2 向芯片中的某个地址&#xff08;addr:0x00&#xff09;写入一个数值代码示例&#xff1a;读写流程分析 具体的配置接上…

react native ios 添加启动页 xcode14

最近更新xcode&#xff0c;有些配置有些不同&#xff0c;网上查的方法都是过时的&#xff0c;导致配了一段时间卡在这里&#xff0c;最后访问官网才弄好了&#xff0c;所以以后解决问题的办法先看官网再查其他各路神仙的办法。 官网的步骤&#xff1a;https://github.com/crazy…

MAC 查看已装载的卷宗

查看卷宗目录命令 ls /Volumes网络卷宗&#xff1a;本机、系统报告。 参考资料&#xff1a;https://blog.csdn.net/qq_41731201/article/details/125407204

建筑行业如何进行高效管理文件?

建筑设计行业在日常办公中会产出大量文件&#xff0c;如设计图纸协作商议&#xff0c;项目资料等&#xff0c;建筑行业该如何高效管理文件呢&#xff1f; 建筑设计行业团队可能遇到的文件管理问题&#xff1a; 1&#xff0c;设计图纸一般较大&#xff0c;来回传输不仅麻烦&…

【矩阵快速幂 | 斐波那契数列 | 矩阵加速】

文章目录 基础知识1. 矩阵结构2. 重载 * 运算符3. 矩阵快速幂 例题1&#xff1a; 矩阵幂求和例题2&#xff1a; 矩阵快速幂例题3&#xff1a; 斐波那契数列例题4&#xff1a; 矩阵加速例题5&#xff1a; 广义斐波那契例题6&#xff1a; 斐波那契公约数例题7&#xff1a; 这个勇…

设计模式——设计模式介绍和单例设计模式

目录 一、设计模式概述和分类 1.1 设计模式介绍 1.2 设计模式分类 二、创建型设计模式-单例模式 2.1 介绍 2.2 八种单例模式的创建方式 2.2.1 饿汉式&#xff08;静态常量&#xff09; 2.2.2 饿汉式&#xff08;静态代码块&#xff09; 2.2.3 懒汉式&#xff08;线程…

TCP/IP协议基础

1.TCP/IP模型的分层 网络接口层&#xff08;Network Interface Layer&#xff09;&#xff1a;&#xff08;数据链路层&#xff09; 功能&#xff1a; ①将数据帧发送到物理网络&#xff0c;并从物理网络接收数据帧。 ②处理硬件地址&#xff0c;如MAC地址。 主要协议&#xff…

[独家]自动播放K线图训练盘感能力!股票量化分析工具QTYX-V2.3.5

K线量价的重要性 K线图对炒股的朋友来说太熟悉不过了&#xff0c;每一根K线包含了开盘价、收盘价、最高价和最低价这四个价位信息&#xff0c;分别用红和绿两种颜色来表示上涨或下跌&#xff0c;反映了单位时间周期内价格变动的情况。 不过K线的功效可不仅仅用来记录价格的变动…