Java——多线程和锁

这篇具有很好参考价值的文章主要介绍了Java——多线程和锁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

多线程

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

当其中一个线程发生阻塞时,操作系统会自动执行新的线程保证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()方法。然后就会有三个线程在同时执行。

Java——多线程和锁

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

Java——多线程和锁

 实现方法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();

    }
}

输出如下所示

Java——多线程和锁

锁锁锁——锁锁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());
    }
}

上面代码执行输出如下

Java——多线程和锁成功执行了20次

下面是没加锁的情况 

Java——多线程和锁

在操作系统中使用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();
    }
}

输出结果是错误的

Java——多线程和锁

上面的等价于如下的写法

    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)在哪个线程使用就睡眠哪个线程。文章来源地址https://www.toymoban.com/news/detail-429122.html

到了这里,关于Java——多线程和锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java Excel 打开文件报发现“xx.xlsx”中的部分内容有问题。是否让我们尽量尝试恢复问题解决

    发现“文件.xlsx”中的部分内容有问题。是否让我们尽量尝试恢复? 1、后端的导出接口写的不对,又返回流数据,又返回响应体数据,导致前端将流数据和响应体数据都下载到了excel文件中。  解决办法: 接口仅返回流数据即可。

    2024年02月13日
    浏览(43)
  • Java21对虚拟线程进行http压测使用不同的GC

    JDK21默认GC是G1. JDK21除了G1外,还可以使用ZGC(Java11预览、Java15正式版),Java21在ZGC基础上继续推出了分代ZGC,目前还是试行阶段。 开启ZGC: java -XX:+UseZGC -jar myapp.jar 开启ZGC,并试用分代ZGC java -XX:+UseZGC -XX:+ZGenerational -jar myapp.jar 以下的对一个启用tomcat虚拟线程的spring boot 项目

    2024年02月05日
    浏览(39)
  • Java/Python/Go不同开发语言在进程、线程和协程的设计差异

    在多线程项目开发时,最常用、最常遇到的问题是 1,线程、协程安全 2,线程、协程间的通信和控制 本文主要探讨不同开发语言go、java、python在进程、线程和协程上的设计和开发方式的异同。 进程 进程是 操作系统进行资源分配的基本单位,每个进程都有自己的独立内存空

    2024年01月23日
    浏览(38)
  • Java体系性能测试进阶必须了解的知识点——死锁分析和锁竞争分析

    所谓 死锁 ,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。对于锁更好的理解,先要理解monitor这个概念! monitor直译过来是监视器的意思,专业一点叫管程。monitor是属于编程语言级别的,它的出现

    2024年02月07日
    浏览(34)
  • 【Java】实现一个简单的线程池

       📝个人主页:哈__ 期待您的关注  线程池顾名思义就是管理线程的一个池子,我们把创建线程的过程交给线程池来处理,而这个线程池当中的线程都会从阻塞队列当中取获取任务执行。 我们不在直接把任务的创建过程写到我们初始化的线程对象中,而是通过调用线程池的

    2024年04月24日
    浏览(25)
  • 【用java 写一个web网站流程】

    要用 Java 写一个 Web 网站,需要掌握 Java 编程语言、Java Web 开发框架(如Spring、Struts等)、Web 前端技术(如HTML、CSS、JavaScript等)和数据库相关知识(如MySQL、Oracle等)。 下面是一个简单的 Java Web 网站开发流程: 首先,需要选择一个 Java Web 开发框架,如Spring、Struts等。在这

    2024年02月06日
    浏览(71)
  • Java中不同对象调用该实例方法返回值是同一个地址空间吗?

    结论 不一定。 基本类型返回的是值 引用数据类型返回的是引用地址(是否同一个引用看是否用到常量池) \\\'==\\\' 基本类型比较的是两者的值是否相同 而引用类型比较两者的是引用地址是否相同 返回的值相同 \\\'==\\\'就为true 返回的引用地址相同 \\\'==\\\'就是true

    2023年04月25日
    浏览(31)
  • 一个Java线程的线生(线生 vs 人生)

    下面我们看下Java的多线程 作者: 博学谷狂野架构师 GitHub: GitHub地址 (有我精心准备的130本电子书PDF) 只分享干货、不吹水,让我们一起加油!😄 1.1 java天生就是多线程的 一个Java程序从main()方法开始执行,然后按照既定的代码逻辑执行,看似没有其他线程参与,但实际上

    2023年04月18日
    浏览(28)
  • 在 Java 中优雅地移除字符串最后一个字符:不同级别程序员的实践指南

    引言: 处理字符串是编程中非常常见的任务。本文将详细介绍四种在 Java 中优雅地移除字符串最后一个字符的方法,并针对不同级别的程序员进行讨论。我们将从简单的方法入手,逐步介绍更高级的技术,以帮助程序员根据自己的水平和需求选择最合适的解决方案。 这是一

    2024年02月13日
    浏览(46)
  • 基于Java Socket写一个多线程的聊天室(附源码)

    Socket编程是在TCP/IP上的网络编程,但是Socket在上述模型的什么位置呢。这个位置被一个天才的理论家或者是抽象的计算机大神提出并且安排出来 ​ 我们可以发现Socket就在应用程序的传输层和应用层之间,设计了一个Socket抽象层,传输层的底一层的服务提供给Socket抽象层,S

    2024年02月10日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包