java多线程基本操作方法

news2024/11/15 16:59:22

目录

一、isAlive()

二、 join()

三、start和run

四、 volatile()

五、synchronized

1、synchronized 引入

2、死锁 

第一种情况:反复加锁

第二种情况 对不同对象嵌套加锁的死锁

3、形成死锁条件 

六、wait和notify


一、isAlive()

isAlive表示线程时候结束,判断时候回调函数(这里是run)时候结束.

public class Test2_isAlive {
    public static void main(String[] args) {
        Thread t=new Thread(()->{
            System.out.println("线程开始");
            try {
                Thread.sleep(1000);//只是用来让程序暂时休息下,方便看结果
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("线程结束");
        });
        t.start();//其次被执行
        System.out.println(t.isAlive());//首先被执行,因为t.start需要准备一些资源
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(t.isAlive());//最后执行,会是false,也可能run中执行的慢也会被提前执行,true
    }
}

第一种情况,run还没有执行完毕,也就是回调函数还没有执行完毕,执行了最后一次System.out.println(t.isAlive());

 

第二种情况,run执行完毕了,在执行最后一次 System.out.println(t.isAlive());

   我们可以加大sleep()的时间来选择这两次结果

二、 join()

这是一个等待函数,创建两个线程A 和 B,对应的实例分别是a和b,在A中用b调用了join(),那么A线程会被暂停,等待B线程执行完毕在执行

public class Thread_join {
    public static void main(String[] args) throws InterruptedException {
      Thread t=new Thread(()->{
          for (int i = 0; i < 5; i++) {
              System.out.println("t线程工作中");
              try {
                  Thread.sleep(1000);
              } catch (InterruptedException e) {
                  throw new RuntimeException(e);
              }
          }
      });
      t.start();
        System.out.println("join开始等待");
        t.join();
        System.out.println("join等待结束");
    }
}

描述: 我们在main线程中使用Thread的t实例调用了join()方法,main需要等待Thread 执行完毕在执行

三、start和run

四、 volatile()

我们先来聊聊什么叫内存可见性?

我们先来看一下下面代码,这里定义了一个变量flag通过线程t1判断flag的值看是否循环,通过改变t2来改变flag的值终止t1的循环,我们预期输入1来终止t1的循环

public class Thread_volatile {
    public static int flag=0;

    public static void main(String[] args) {
        Thread t1=new Thread(()->{
            while (flag==0){
                //这个循环一秒钟会执行很多次
            }
            System.out.println("t线程推出");
        });
        t1.start();
        Thread t2=new Thread(()->{
            System.out.println("请输入flag值");
            Scanner scan=new Scanner(System.in);
            flag=scan.nextInt();
        });
        t2.start();
    }
}

程序进入了死循环,为啥呢? 

这就是内存的可见性问题,编译器在编译t1的进程while循环时,发现每次需要从内存中读取flag的值在进行while条件判断,由于这个while循环执行非常快,短时间内需要大量读flag的值,在判断,但是这个从内存读取flag的值的速度时判读的速度的千分之一甚至是万分之一倍,所以编译器决定只进行一次读取,进行大量判断,编译器初心是为了提高代码性能,如果放在单线程中没有问题,但是这是多线程。

解决方案(不让编译器自动优化代码了,就是进行多次读取)

给flag加上volatile修饰

 public volatile static int flag=0;

五、synchronized

1、synchronized 引入

synchronized 会起到互斥效果 , 某个线程执行到某个对象的 synchronized 中时 , 其他线程如果也执行到 同一个对象 synchronized 就会 阻塞等待
  • 进入 synchronized 修饰的代码块, 相当于 加锁
  • 退出 synchronized 修饰的代码块, 相当于 解锁

我们先来看一组简单代码:

public class Test_Synchronized {
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            for (int i = 0; i < 5000; i++) {
                count++;
            }
        });
        Thread t2=new Thread(()->{
            for (int i = 0; i <5000 ; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();//这里不加join会在没有加完情况下打印count的值
        t2.join();
        System.out.println(count);
    }
}

描述:创建两个线程每个线程让count变量加5k次,最后我们的预期结果应该是1w。可是如下:

 

原因分析: count++这个操作本质上,是分为三部进行的

  1. load  把数据从内存读取到cpu寄存器中
  2. add   把寄存器中的数据加1
  3. save  把寄存器中的数据保存到内存

我们的理想状态是两个线程分别执行count++,可是线程之间是并发执行的呀,假设count开始值是0,如果我们在执行a线程的count++的时候就是上面count++三部曲,执行到了第二步,如果这个时候b线程也开始执行三部曲的第一步开时读取count的值那肯定是和a线程读取的一样都是0,因为这时候a线程还没有改变count的值。继续往后走,当a线程执行完毕三部曲,count变成1,a线程继续读取继续执行,这时候b线程执行完了,把count也变成了1,b线程继续执行。发现问题了吗?

解决方案一

我们先执行完毕一个线程,在执行一个另外一个线程,代码如下:

        t1.start();
        t1.join();//这里不加join会在没有加完情况下打印count的值
        t2.start();
        t2.join();
        System.out.println(count);

解决方案二

使用synchronized加锁,如果加锁的对象一样那么后者需要等待前者加锁部分执行完毕在执行

举个例子理解:多个人等待上一个厕所,这个厕所同一时间只能允许一个上,上厕所都要锁门,假设现在a在上厕所,后面依次是b,c,d在排队,当a上完厕所出来,后面下一个上厕所的人不一定是b这是有优先级的,而且也不是下一个人立刻上厕所,有一个cpu调用过程

public class Test_Synchronized {
    public static int count=0;
    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();
        Thread t1=new Thread(()->{
           synchronized (object){
               for (int i = 0; i < 5000; i++) {
                   count++;
               }
           }
        });
        Thread t2=new Thread(()->{
            synchronized (object){
                for (int i = 0; i < 5000; i++) {
                    count++;
                }
            }
        });
        t1.start();
        t2.start();
        t1.join();//这里不加join会在没有加完情况下打印count的值
        t2.join();
        System.out.println(count);
    }
}

2、死锁 

第一种情况:反复加锁

举个例子:我们已经对一个对象加锁a了,但是现在我们在这个锁里面在对这个对象加锁b,这样外层锁执行到b锁的时候,b锁要等待这个已经加a锁的对象执行完毕在执行,可是a锁又要执行b锁才能执行完,程序就卡住了

解决方案 

第二种情况 对不同对象嵌套加锁的死锁

举个例子:你想开车,但是车钥匙在房间里,你想进房间拿钥匙,但是房间钥匙在锁在了你想开的车里,很离谱把,就是这么离谱,哈哈哈

代码什么情况下会出现死锁?

public class Deal_Synchronized2 {
    public static void main(String[] args) {
        Object object1=new Object();
        Object object2=new Object();

        Thread t1=new Thread(()->{
            synchronized (object1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (object2){
                    System.out.println("t1加锁完毕");
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (object2){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (object1){
                    System.out.println("t2加锁完毕");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

描述:t1线程和t2线程开始都会先获得object1的锁和object2的锁,当t1线程执行到synchronized (object2)的时候,t2对象已经对object2加上锁了,t1进程进入阻塞态等待object2释放,但是t2线程的object2需要等待t1线程执行完毕,所以程序进入阻塞态

解决方案

在编写锁的时候,注意先编写序号小的锁,后编写序号大的锁(或先大后小)要有一定规则,修改代码如下:

public class Deal_Synchronized2 {
    public static void main(String[] args) {
        Object object1=new Object();
        Object object2=new Object();

        Thread t1=new Thread(()->{
            synchronized (object1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (object2){
                    System.out.println("t1加锁完毕");
                }
            }
        });
        Thread t2=new Thread(()->{
            synchronized (object1){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (object2){
                    System.out.println("t2加锁完毕");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

3、形成死锁条件 

  1. 互斥使用.(锁的基本特性)当一个线程持有一把锁的时候,另一个线程也想获取到锁,就要阻塞等待
  2. 不可抢占(锁的基本特性)当锁已经被线程1拿到之后,线程2只能等待线程1主动释放
  3. 请求保持(代码决定)一个线程尝试获取多把锁(有锁1了还想得到锁2,在获取锁2的过程不会释放锁1)
  4. 循环嵌套锁,你想开车,但是车钥匙在房间里,你想进房间拿钥匙,但是房间钥匙在锁在了你想开的车里

我们在以后解决的主要针对第4条就可以,即注意锁的编号

六、wait和notify

wait是在线程还没有结束的时候进入阻塞,join是影响线程的结束顺序,wait需要用notify唤醒

public class Wait_Notify {
    public static void main(String[] args) {
        Object object=new Object();
        Thread t1=new Thread(()->{
            synchronized (object){
                System.out.println("wait之前");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("wait之后");
            }
        });
        Thread t2=new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (object){
                System.out.println("进行通知");
                object.notify();
            }
        });
        t1.start();
        t2.start();
    }
}

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

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

相关文章

【正点原子FreeRTOS学习笔记】————(10)FreeRTOS时间管理

这里写目录标题 一、延时函数介绍&#xff08;了解&#xff09;二、延时函数解析&#xff08;熟悉&#xff09;三、延时函数演示实验&#xff08;掌握&#xff09; 一、延时函数介绍&#xff08;了解&#xff09; 相对延时&#xff1a;指每次延时都是从执行函数vTaskDelay()开始…

前缀和算法(1)

一维前缀和[模板] 一、题目描述 OJ题目链接&#xff1a;【模板】前缀和_牛客题霸_牛客网 二、思路解析 三、代码 #include <iostream> using namespace std; const int N 100010; long long arr[N], dp[N]; int n, q; int main() {cin >> n >> q;// 读取…

冗余双写方案下数据一致性问题解决及延申问题处理方案

主要整理了采用冗余双写方案后的问题解决方案。 1、问题&#xff1a;冗余双写场景下&#xff0c;如何解决数据一致性问题&#xff1f; 方案一&#xff1a; 直接RPC调用Seata分布式事务框架&#xff0c;采用该方式实现了事务的强一致性&#xff0c;代码逻辑简单的同时业务侵入…

蓝桥杯-正则问题

#include<iostream> #include<string> #include<algorithm> using namespace std;int pos 0, l;//pos当前遍历到的位置&#xff0c;l是正则式的长度 string s;int dfs(){int temp 0, ans 0;//temp保存左半部分 while(pos < l){if(s[pos] (){pos;//跳…

【C语言】C语言基础习题详解(牛客网)二分查找逻辑

主页&#xff1a;醋溜马桶圈-CSDN博客 专栏&#xff1a;C语言_醋溜马桶圈的博客-CSDN博客 gitee&#xff1a;mnxcc (mnxcc) - Gitee.com 目录 1.三目运算符的使用 三目运算符&#xff0c;即a>b?a:b类型的&#xff0c;很多时候适当的使用三目运算符可以使得代码更简洁有序&…

在Python中进行封装

在Python中&#xff0c;封装是一种面向对象编程&#xff08;OOP&#xff09;的特性&#xff0c;它允许我们将数据&#xff08;属性&#xff09;和操作这些数据的方法&#xff08;函数&#xff09;捆绑在一起&#xff0c;形成一个独立的对象。封装的主要目的是隐藏对象的内部状态…

四年创作,心路历程

四年创作&#xff0c;心路历程 前言初识收获日常憧憬 前言 这是我在这个网站整理的笔记,有错误的地方请指出&#xff0c;关注我&#xff0c;接下来还会持续更新。 作者&#xff1a;神的孩子都在歌唱 前言 今天打开csdn&#xff0c;发现官方发送了一条私信,原来我已经在计算机这…

文件操作(基础知识篇)

1. 文件操作的作用 可以将内存中的数据持久化地存储到文件中。我们所运行的程序的数据是存储在电脑的内存中的&#xff0c;如果不将数据持久化地存储起来&#xff0c;那么在程序退出时&#xff0c;内存中的数据就会随内存的回收而消失。 可以持久化保存数据的&#xff0c;就被…

文件操作(上)(想要了解如何操作文件,那么看这一片就足够了!)

前言&#xff1a;在我们写的程序的的时候&#xff0c;数据是存储在电脑的内存中&#xff0c;如果程序退出&#xff0c;内存回收&#xff0c;数据就丢失了&#xff0c;等再次运行程序&#xff0c;是看不到上次程序的数据的&#xff0c;那么如果要将数据进行持久化的保存&#xf…

逻辑解析(logical decoding)

pg_create_logical_replication_slot select * from pg_replication_slots; select pg_export_snapshot(); select pg_create_logical_replication_slot(‘logical_slot1’, ‘test_decoding’); CREATE TABLE t_logical(id int4); pg_logical_slot_get_changes SELECT *…

git下载安装教程

git下载地址 有一个镜像的网站可以提供下载&#xff1a; https://registry.npmmirror.com/binary.html?pathgit-for-windows/图太多不截了哈哈&#xff0c;一直next即可。

操作系统的理解|冯·若依曼体系结构|进程的状态

操作系统的理解 冯诺伊曼体系结构为什么必须通过内存然后到cpu存储金字塔冯诺伊曼结构的改进在哪&#xff1f;我们可不可以全部用寄存器来做存储器在硬件数据流动角度学以致用&#xff1a;解释程序运行为什么要加载到内存程序没被运行之前存在哪里&#xff1f; 操作系统概念广义…

H5小程序视频方案解决方案,实现轻量化视频制作

对于许多企业而言&#xff0c;制作高质量的视频仍然是一个技术门槛高、成本高昂的挑战。针对这一痛点&#xff0c;美摄科技凭借其深厚的技术积累和创新能力&#xff0c;推出了面向企业的H5/小程序视频方案解决方案&#xff0c;为企业提供了一种轻量化、高效、便捷的视频制作方式…

flutter布局更新

理论上&#xff0c;某个组件的布局变化后&#xff0c;就可能会影响其他组件的布局&#xff0c;所以当有组件布局发生变化后&#xff0c;最笨的办法是对整棵组件树 relayout&#xff08;重新布局&#xff09;&#xff01;但是对所有组件进行 relayout 的成本还是太大&#xff0c…

目前2024年腾讯云4核8G服务器租用优惠价格表

2024年腾讯云4核8G服务器租用优惠价格&#xff1a;轻量应用服务器4核8G12M带宽646元15个月&#xff0c;CVM云服务器S5实例优惠价格1437.24元买一年送3个月&#xff0c;腾讯云4核8G服务器活动页面 txybk.com/go/txy 活动链接打开如下图&#xff1a; 腾讯云4核8G服务器优惠价格 轻…

车载以太网AVB交换机 TSN交换机 时间敏感网络 6端口 百兆 SW100TSN

SW100 TSN时间敏感网络AVB交换机 为6端口百兆车载以太网交换机&#xff0c;其中包含5通道100BASE-T1泰科MATEnet接口和1个通道100/1000BASE-T标准以太网(RJ45接口)&#xff0c;可以实现纳米级时间同步&#xff0c;车载以太网多通道交换&#xff0c;Bypass数据采集和监控等功能&…

B2 PRO WordPress主题:多功能商用主题,助力资讯、资源、社交、商城、圈子、导航一站式解决

B2 PRO WordPress主题&#xff1a;多功能商用主题&#xff0c;助力资讯、资源、社交、商城、圈子、导航一站式解决 一、产品概述 B2 PRO WordPress主题&#xff0c;作为一款多功能商用主题&#xff0c;致力于为用户提供一站式的内容管理与网站建设服务。它集资讯发布、资源共享…

4.常用CMD命令

扩展一个小点&#xff1a; 在很多资料中都说成是DOS命令&#xff0c;其实是不对的。真正的DOS命令是1981年微软和IBM出品的MS-DOS操作系统中的命令才叫做DOS命令。 而在Windows中&#xff0c;win98之前的操作系统是以非图形化的DOS为基础的&#xff0c;可以叫做DOS命令。到了…

精酿啤酒:特殊酵母的发酵特性与风味表现

Fendi Club啤酒在酿造过程中采用了特殊的酵母&#xff0c;这些酵母具有与众不同的发酵特性和风味表现&#xff0c;为啤酒带来了与众不同的风味和口感。 Fendi Club啤酒使用的酵母种类繁多&#xff0c;包括艾尔酵母和拉格酵母等。这些不同种类的酵母在发酵过程中具有不同的特性和…

unity学习(70)——编译游戏发生错误2

1.全屏问题其实无所谓&#xff0c;windows用tab可以切出来的。 2.现在主要问题是服务器try了以后虽然不崩溃了&#xff0c;但不再显示2个实例对象了&#xff0c;unity和exe此时都只能看到一个实例对象 2.1把之前报错位置的try-catch先注释掉 2.2 unity中此时登录666账号&…