范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Java多线程上基本概念及操作

  目录多线程定义:线程:操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位  进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。  线程与进程区别:  调度:进程是资源管理的基本单位,线程是程序执行的基本单位。  切换:线程上下文切换比进程上下文切换要快得多。  拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源。  系统开销: 创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等,OS所付出的开销显著大于在创建或撤销线程时的开销,进程切换的开销也远大于线程切换的开销。  多线程编程
  首先废话不多说,我们先来运行如下一段代码感受一下多线程:  package demo1;import java.util.Random;public class ThreadDemo {     private static class MyThread extends Thread {         @Override        public void run() {             Random random = new Random();            while (true) {                 // 打印线程名称                System.out.println(Thread.currentThread().getName());                try {                     // 随机停止运行 0-9 秒                    Thread.sleep(random.nextInt(10));                }                catch (InterruptedException e) {                     e.printStackTrace();                }            }        }    }    public static void main(String[] args) {         MyThread t1 = new MyThread();        MyThread t2 = new MyThread();        MyThread t3 = new MyThread();        t1.start();        t2.start();        t3.start();        Random random = new Random();        while (true) {             // 打印线程名称            System.out.println(Thread.currentThread().getName());            try { Thread.sleep(random.nextInt(10));            } catch (InterruptedException e) {                 // 随机停止运行 0-9 秒                e.printStackTrace();            }        }    }}
  感受到了吧,我猜你只发现了他会一直不停的运行…
  所以接下来我们就正式来看看这到底是怎么个东西。  创建线程方法继承 Thread 类  创建一个线程类继承Thread方法  用线程类实例化对象  启动线程  public class ThreadDemo {     private static class MyThread extends Thread {         //新建类继承Thread,使这个类变成线程类        @Override        public void run() {             System.out.println("这里是线程运行的代码");        }    }    public static void main(String[] args) {         //用线程类实例化对象        MyThread thr1 = new MyThread();        //启动线程        thr1.start();    }}实现 Runnable 接口  实现 Runnable 接口  创建 Thread 类实例, 调用Thread的构造方法时将Runnable对象作为target参数  调用start方法启动线程  public class demo2 {     static class MyRunnable implements Runnable{         //新建线程类实现Runnable接口        @Override        public void run() {             System.out.println("这里是线程运行的代码");        }    }    public static void main(String[] args) {        Thread thr1 = new Thread(new MyRunnable());       thr1.start();    }}当然还有他们的几种变形  匿名内部类创建 Thread 子类对象  public class demo3 {     public static void main(String[] args) {         // 使用匿名类创建Thread 子类对象        Thread t1 = new Thread() {             @Override            public void run() {                 System.out.println("使用匿名类创建 Thread 子类对象");            }        };        t1.start();    }}匿名内部类创建 Runnable 子类对象  public class 匿名内部类创建Runnable子类对象 {     public static void main(String[] args) {         // 使用匿名类创建 Runnable 子类对象        Thread t2 = new Thread(new Runnable() {             @Override            public void run() {                 System.out.println("使用匿名类创建 Runnable 子类对象");            }        });        t2.start();    }}
  lambda 表达式创建  public class lambda表达式创建线程 extends Thread{     public static void main(String[] args) {         new Thread(()->{             System.out.println("lambda方式创建线程");        }).start();    }}
  氮气加速——多线程:
  故名思义,使用多线程的好处就是加速,在某些场合下是可以提高程序的整体运行效率的。
  首先我们来看一段代码及结果感受一下  public class ThreadAdvantage {     // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的    private static final long count = 10000000;    public static void main(String[] args) throws InterruptedException { // 使用并发方式        concurrency();// 使用串行方式        serial();    }    private static void concurrency() throws InterruptedException {         long begin = System.nanoTime();        // 利用一个线程计算 a 的值        Thread thread = new Thread(new Runnable() {             @Override            public void run() {                 int a = 0;                for (long i = 0; i < count; i++) {                     a--;                }            }        });        thread.start();// 主线程内计算 b 的值        int b = 0;        for (long i = 0; i < count; i++) {             b--;        }// 等待 thread 线程运行结束        thread.join();// 统计耗时        long end = System.nanoTime();        double ms = (end - begin) * 1.0 / 1000 / 1000;        System.out.printf("并发: %f 毫秒%n", ms);    }    private static void serial() { // 全部在主线程内计算 a、b 的值        long begin = System.nanoTime();        int a = 0;        for (long i = 0; i < count; i++) {             a--;        }        int b = 0;        for (long i = 0; i < count; i++) {             b--;        }        long end = System.nanoTime();        double ms = (end - begin) * 1.0 / 1000 / 1000;        System.out.printf("串行: %f 毫秒%n", ms);    }}
  运行结果如下:
  Thread 类及常见方法
  前面一直介绍Thread类,我们也听了很多遍了,下面来正式了解一下这个类和其常见方法  构造方法  常见属性
  JVM会在一个进程的所有非后台线程结束后,才会结束运行。  启动问题(start() 与 run())
  记住线程的启动是start()  start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。  run: run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。  总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
  看完那个解释是不是更晕了,嗯…,知道会晕所以我们来看一图就明白了
  调用 start 方法, 才真的在操作系统的底层创建出一个线程哦
  start()也只是加入就绪队列,真正的开始执行由cpu决定  中断线程等待一个线程-join()
  这个用法没啥讲的,可以看下面这段代码运行感受一下  public static void main(String[] args) throws InterruptedException {         Runnable target = () -> {             for (int i = 0; i < 10; i++) {                 try {                     System.out.println(Thread.currentThread().getName() + ": 我还在工作!");                    Thread.sleep(1000);                } catch (InterruptedException e) {                     e.printStackTrace();                }            }            System.out.println(Thread.currentThread().getName() + ": 我结束了!");        };        Thread thread1 = new Thread(target, "李四");        Thread thread2 = new Thread(target, "王五");        System.out.println("先让李四开始工作");        thread1.start();        thread1.join();        System.out.println("李四工作结束了,让王五开始工作");        thread2.start();        thread2.join();        System.out.println("王五工作结束了");    }
  当然还有休眠sleep()【上面代码其实已经看到了】;currentThread()获取当前线程引用等方法,我们不一一介绍了,不然太划水了。  线程的状态
  线程安全
  前面的方法做了解,会用,知道,咋回事就好了,但是这个可是重点哟
  我们先来看一段代码: static class Counter {         public int count = 0;        void increase() {             count++;        }    }    public static void main(String[] args) throws InterruptedException {         final Counter counter = new Counter();        Thread t1 = new Thread(() -> {             for (int i = 0; i < 50000; i++) {                 counter.increase();            }        });        Thread t2 = new Thread(() -> {             for (int i = 0; i < 50000; i++) {                 counter.increase();            }        });        t1.start();        t2.start();        t1.join();        t2.join();        System.out.println(counter.count);    }
  很明显结果应该是100000,但是下面是我们的运行结果:
  那么为什么会这样?
  接下来我们正式了解线程安全:  线程安全定义
  如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。  线程不安全的原因jvm内存
  通过这两张图,我们很明显能看出来哪些东西线程之间共享,哪些是有。  内存区域:
  共享:堆、方法区、运行时常量池;
  私有:pc、栈  表现在代码中的话:
  共享:对象、类对象、静态属性;私有:局部变量  线程不安全原因开发者角度:  共享数据:多个线程之间操作同一块数据  并且这两个线程至少有一个线程在修改这块共享数据  系统角度:  注:  高级语言的一条语句,可能对应多条指令; 线程调度可能发生在任一时刻(不会切割指令); cpu为了提高数据获取速度会设置缓存(Cache):因为指令的执行速度远远大于内存读写速度 jvm对内存进行了模拟,内存:jvm主内存;缓存:jvm:工作内存 我们写的代码在编译阶段会进行代码重排序(jvm要求无论如何优化,当线程角度结果不应该变化,所以多线程环境可能出问题) 原子性被破坏  内存可见性问题,导致某些线程读取到脏数据  代码重排序,导致线程之间数据配合出现问题  解决线程不安全方法:
  回顾三个阶段我们知道破环线程安全主要是如下三个条件:  原子性被破坏  内存可见性问题,导致某些线程读取到脏数据  代码重排序,导致线程之间数据配合出现问题
  那么嘿嘿嘿…解决方案不就来了嘛:  synchronized 关键字-监视器锁
  保证原子性,当然他也能保证内存可见性
  注意:  上一个线程解锁之后, 下一个线程并不是立即就能获取到锁. 而是要靠操作系统来 "唤醒". 这也就是操作系统线程调度的一部分工作.  假设有 A B C 三个线程, 线程 A 先获取到锁, 然后 B 尝试获取锁, 然后 C 再尝试获取锁, 此时 B和 C 都在阻塞队列中排队等待. 但是当 A 释放锁之后, 虽然 B 比 C 先来的, 但是 B 不一定就能获取到锁, 而是和 C 重新竞争, 并不遵守先来后到的规则.
  当然关于锁地理解这一点肯定是不够的,我们下一篇将详细介绍这个东西  volatile 关键字
  保证内存可见性,无法保证原子性
  上面是jvm的内存工作,下面是加入volatile关键字时的操作;  代码在写入 volatile 修饰的变量的时候:
  改变线程工作内存中volatile变量副本的值
  将改变后的副本的值从工作内存刷新到主内存  代码在读取 volatile 修饰的变量的时候:
  从主内存中读取volatile变量的最新值到线程的工作内存中
  从工作内存中读取volatile变量的副本
  那么是什么意思?还记得我们之前说过:直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存), 速度非常快, 但是可能出现数据不一致的情况,而加上 volatile , 强制读写内存. 速度是慢了, 但是数据变得更准确了.
  理论是不是很烦我们来看一段代码理解一下:  static class Counter {         public int flag = 0;    }    public static void main(String[] args) {         Counter counter = new Counter();        Thread t1 = new Thread(() -> {             while (counter.flag == 0) {                 // do nothing            }            System.out.println("循环结束!");        });        Thread t2 = new Thread(() -> {             Scanner scanner = new Scanner(System.in);            System.out.println("输入一个整数:");            counter.flag = scanner.nextInt();        });        t1.start();        t2.start();    }
  很明显当我们输入0时上面的代码应该停止运行,然而并不会那样,因为有缓存,所以我们给flag加上volatile关键字,这样,就会从内存读写,解决了。  static class Counter {         public volatile int flag = 0;    }
  如下图所示:
  当然这个只是基本知识,其他的后续将出,当然推荐大家自己动手实验一下;  wait 和 notify
  由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理地协调多个线程之间的执行先后顺序.  wait()方法wait 做的事情:  使当前执行代码的线程进行等待. (把线程放到等待队列中)  释放当前的锁  满足一定条件时被唤醒, 重新尝试获取这个锁.  wait 要搭配 synchronized 来使用, 脱离 synchronized 使用 wait 会直接抛出异常.  wait 结束等待的条件:  其他线程调用该对象的 notify 方法.  wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).  其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常  notify()方法notify 方法是唤醒等待的线程.  方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。  如果有多个线程等待,则有线程调度器随机挑选出一个呈现 wait 状态的线程。(并没有 "先来后到")  在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
  我们来举个例子看一下:  static class WaitTask implements Runnable {         private Object locker;        public WaitTask(Object locker) {             this.locker = locker;        }        @Override        public void run() {             synchronized (locker) {                 while (true) {                     try {                         System.out.println("wait 开始");                        locker.wait();                        System.out.println("wait 结束");                    } catch (InterruptedException e) {                         e.printStackTrace();                    }                }            }        }    }    static class NotifyTask implements Runnable {         private Object locker; public NotifyTask(Object locker) {             this.locker = locker;        }        @Override        public void run() {             synchronized (locker) {                 System.out.println("notify 开始");                locker.notify();                System.out.println("notify 结束");            }        }    }    public static void main(String[] args) throws InterruptedException {         Object locker = new Object();        Thread t1 = new Thread(new WaitTask(locker));        Thread t2 = new Thread(new NotifyTask(locker));        t1.start();        Thread.sleep(5000);        t2.start();    }
  结果很明显了吧,嗯…就是这样等5秒后会notify介入,会让等待的东西继续进入,当然,这个一次只能唤醒一个线程,所以notifyAll()方法故名思义,唤醒所有的等待线程。不过当然如上代码,因为如果有其他线程竞争同一个资源,一起唤醒(notify()),其唤醒过程也是有顺序的。如下图所示:
  wait 和 sleep 的对比wait 需要搭配 synchronized 使用. sleep 不需要.  wait 是 Object 的方法 sleep 是 Thread 的静态方法.  sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。  sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()或者notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。  sleep()方法必须捕获异常InterruptedException,而wait() otify()以及notifyAll()不需要捕获异常。
  这俩本来关系基本接近于0,就像final和finally一样,一个用法像,一个长得像,但是本质没关系,就像现在很多奇怪的的社会现象,孩子是你在养、却和你一点关系都没有、扎心了吧老铁…

前置影像再次升级,开学季购机选华为nova10系列准没错相信很多的学生党们已经陆续拿到了自己的录取通知书,作为新晋的大学生,要给自己一个焕然一新的面貌,准备一部超高性价比的手机是不可或缺的。今天我们就来聊聊前不久开售的华为nova10系国际关注!北京教授团队获石墨烯研究新成果,2天公开发表2篇硬核论文!近日,北京师范大学物理学系何林研究组在小转角双层石墨烯中实现可调控的大面积电子态Kagome晶格,相关成果于8月11日以TunableSampleWideElectronicKag华为Mate50或与iPhone14同期发布苹果有望年底推出台积电3nm芯片2022年8月18星期四闪回每日资讯1新浪科技消息,华为Mate50系列将9月上旬发布,预计日期是9月7日,与此同时彭博社透露,iPhone14系列也将美国时间9月7日发布,9月1千方科技上半年净利润下降118。28,原华为空降总经理潘璠已离职从加入到离开,潘璠在千方科技待了不到两年。作者秀松编辑余快日前,智慧交通企业千方科技发布半年报,报告期内营收31。16亿元,同比下降24。44净利润0。93亿元,同比下降118。2画质为先万元价位内值得入手的拍照手机微单和单反有哪些?如果以画质为优先考虑因素,那么万元价位内最值得入手的拍照手机微单和单反有哪几款呢?这是很多喜欢摄影的小伙伴非常关心的问题。首先我们来看万元之内拍照性能最强悍,画质最好的手机。如果要台积电创始人台积电创始人张忠谋提到台积电赴美建厂是在美国政府的敦促下这样做的。尽管美国政府提出了数百亿美元的补贴,但还是远低于提振本土芯片制造所需金额。而美国想重新在本土建立完整的半导体供应链很现实的生活经典文案,送给不清醒的人1。其实心给出去的时候,就该明白不可能毫发无损地收回来。如果坚强的人落泪了,一定是撑不住了,如果执着的心放弃了,一定是伤得彻底了。2。你知道吗?其实你人再好,也有人讨厌你,你人再真向内求向外生向内求就能建立起守护本心的精神家园吗?朋友推荐瓦尔登湖一个人最高级的修行,是向内走一文,感触颇深。梭罗所处的环境与我们当今时代有些类似,物欲横流,内心浮躁,一切向钱看,守护内心的精特别惊艳的文案1。hr世人慌慌张张,不过是图碎银几两,可区区几两银子却能压断世人,世间熙熙攘攘,皆为利来利往,我也一样,无一例外。2。hr再来一万次,我也愿意去捧水中的岁月,一切都是虚的,但是没三星新品折叠屏GalaxyZFold4手机,真的值得购入么?三星作为折叠屏领域的佼佼者,已经先后推出多款折叠屏手机,全都是各有各的优点,对于喜欢折叠屏手机的用户,应该格外关注这次三星全新升级的两款手机吧。据说这两款手机都是在原有的系列机型上从2498跌至1650元,骁龙870后置四摄,屏下摄像头手机加速退场目前市面上在售的全面屏手机,主要有两种主流的设计方式,一种是以安卓为主的挖孔屏,一种则是以苹果为主的刘海屏设计。无论是挖孔还是刘海屏都是不好看的,会影响屏幕的一体性,所以手机厂商们
她是邓小平的第二任妻子,后来嫁给李维汉,生下的儿子成了副国级在我国革命时期,定海地区曾经有一位女英雄,她就是金维映。她出生于上世纪初期,家境贫寒,父亲金荣贵靠着一家米行勉强维持家庭生活。01金维映出生时,父母给她取名为爱卿。她在幼年的时候,免交智商税!我的购买物品使用分享(二)小米杂货铺头条上看了这么多热心人发的日常用品评测避坑指南,我觉得也不能敝帚自珍,也聊聊用过的一些东西,这次同样挑一些用得不好的,和一些我觉得还不错的。先谈几个感觉不太好用或者说设计有问题的18848开始走下坡路,8256G降7000!从12999放到5999从数量上来看,中国的手机品牌应该是世界上最多的。销量也是全球最多的,因为在世界排名前五的手机品牌中,中国就占了三个。不过因为主流的手机品牌就是华米OV荣耀等,所以除了这些大品牌,很此6A非彼6A,不同厂家线材混用还能快充吗?前言一直以来,大家都有个疑惑不同品牌的6A数据线到底能不能通用?尤其是近来,越来越多的手机支持百瓦以上的充电功率,6A线开始成为各种手机的标配,打开手机官网配件页面,一根原装的6A赌狗阵容推荐在11。24版本更新以后,版本也趋近平衡而今天推荐的是四个个赌狗体系阵容1。堵EZ拼多多3发明家3炼金2精密发条阵容EZ时光老头风女炼金卡密尔冰女狼人2。堵狼人7炼金2挑战(奎因或41年,毛主席在延安交际处食堂遇暴徒,金城舍身护住主席好险前言1937年至1947年,金城历任陕甘宁边区政府交际科长处长,在毛主席的直接领导下,他从事统战外交情报等工作。图金城(二排左一)与美军观察组的合影与毛主席相处的十年间,金城与主席1996年,刘华清得知一山西老太还活着,激动万分她丈夫救了红25军1996年5月初,中央军委为纪念红军长征胜利60周年,决定拍摄大型历史文献片北上先锋。其中有一段镜头要在河南省卢氏县拍摄,摄制组在拍摄之前向时任中央军委副主席刘华清上将汇报拍摄概要世界末日理论真相1。外星人入侵自1898年韦尔斯出版了世界大战一书以来,外星人入侵就成为科幻著作的主题。对于来自例如火星的攻击银河系之外的物种或其他空间生物的恐惧始终伴随着对地球的担忧而此起彼伏。小米12Pro外观再曝6。4英寸直屏饼干后摄,支持屏下前摄不得不说,今年小米12系列的保密工作做得比往年都要好,尽管临近发布了,但始终没有关于该系列的谍照流出,只陆续流出几版渲染图给大家捕风捉影。而近日小米12Pro外观再次传来消息。据多通讯Plus5G全千兆来一起薅羊毛!和彩云网盘免费领2GB流量,每月都可领视频加载中看到精彩时刻就没网?明明10M却要下载半个小时?好不容易等了几个小时却跳出下载失败拳头揣在怀里硬是捏了又捏佛系青年都要被这神操作给整懵还在担忧网盘限速给你整不会?还在战战十铨推出DDR5工业服务器内存单条128GB,支持ECCIT之家12月15日消息,今日十铨科技TEAMGROUP宣布推出多款工业服务器用的DDR5内存条。该系列产品提供ECCDIMM版本,不仅支持片上ECC,还支持对信号进行纠错。此外还