前端技术
HTML
CSS
Javascript
前端框架和UI库
VUE
ReactJS
AngularJS
JQuery
NodeJS
JSON
Element-UI
Bootstrap
Material UI
服务端和客户端
Java
Python
PHP
Golang
Scala
Kotlin
Groovy
Ruby
Lua
.net
c#
c++
后端WEB和工程框架
SpringBoot
SpringCloud
Struts2
MyBatis
Hibernate
Tornado
Beego
Go-Spring
Go Gin
Go Iris
Dubbo
HessianRPC
Maven
Gradle
数据库
MySQL
Oracle
Mongo
中间件与web容器
Redis
MemCache
Etcd
Cassandra
Kafka
RabbitMQ
RocketMQ
ActiveMQ
Nacos
Consul
Tomcat
Nginx
Netty
大数据技术
Hive
Impala
ClickHouse
DorisDB
Greenplum
PostgreSQL
HBase
Kylin
Hadoop
Apache Pig
ZooKeeper
SeaTunnel
Sqoop
Datax
Flink
Spark
Mahout
数据搜索与日志
ElasticSearch
Apache Lucene
Apache Solr
Kibana
Logstash
数据可视化与OLAP
Apache Atlas
Superset
Saiku
Tesseract
系统与容器
Linux
Shell
Docker
Kubernetes
[布尔变量逻辑判断]的搜索结果
这里是文章列表。热门标签的颜色随机变换,标签颜色没有特殊含义。
点击某个标签可搜索标签相关的文章。
点击某个标签可搜索标签相关的文章。
转载文章
...语言中的条件表达式和逻辑控制的多样性和复杂性。近期,Lua 5.4版本发布,其中对布尔逻辑运算符进行了优化,使得程序员在处理可能为nil的变量时拥有更多选择。例如,新增的andthen与orelse运算符可以更直观地实现“短路求值”,避免了类似原始三目运算符可能导致的问题。 此外,对于条件分支的处理,不仅限于Lua,其他如JavaScript、Python等流行语言也有各自的独特机制和最佳实践。比如,JavaScript引入了Optional Chaining(可选链)语法,允许开发者在访问深层嵌套属性时无需担心因中间路径值为null或undefined而导致程序崩溃。 而在编程范式上,函数式编程强调纯函数和不可变数据结构,其条件表达式的处理方式与命令式编程有所不同,例如Haskell中的Maybe Monad或Elixir中的Pattern Matching机制,都为nil或空值提供了优雅且安全的处理方式。 综上所述,深入研究各种编程语言中条件表达式的设计理念与实现机制,不仅可以帮助我们更好地运用Lua中的特殊写法,还能拓宽视野,提升跨语言编程能力,并在实际项目中实现更为健壮高效的代码逻辑。
2023-12-29 14:47:09
241
转载
Java
逻辑运算符 , 在编程语言中,逻辑运算符是用来连接或组合布尔表达式的符号,以确定最终的布尔值(真或假)。在Java中,\ or\ (||)和\ and\ (&&)就是这样的逻辑运算符。它们根据预设的逻辑规则对两个或多个布尔条件进行判断,并返回一个布尔结果。例如,\ or\ 运算符要求只要有一个条件为true,整个表达式就为true;而\ and\ 运算符则需要所有条件均为true,表达式才为true。 短路求值(Short-Circuit Evaluation) , 这是一种编程中的优化策略,主要应用于逻辑运算符处理布尔表达式时。在Java中,对于\ or\ (||)运算符,如果左侧条件为true,那么右侧条件将不再评估,因为无论右侧条件如何,整个表达式的结果已经是true。同样地,对于\ and\ (&&)运算符,若左侧条件为false,则不再评估右侧条件,因为在这种情况下,整个表达式无论如何都将得到false的结果。这种特性可以节省计算资源,提高程序执行效率,并且在某些情况下避免不必要的错误发生。 布尔变量 , 布尔变量是一种特殊的变量类型,在Java中用boolean关键字声明,其值只能是true或false。在本文的上下文中,布尔变量condition1和condition2被用来存储特定条件的状态,通过逻辑运算符与这些布尔变量结合使用,可以构建复杂的条件判断结构,以决定程序流程的走向和执行相应的代码块。
2024-02-21 16:05:44
275
码农
Shell
...的返回值进行进一步的逻辑判断。这种操作是我们为了应对业务需求或者解决问题而采取的一种策略,它能够帮助我们更顺溜地处理数据,更灵活地掌控程序流程,让一切变得更有条不紊。就像是给我们的工作装上了加速器,让数据处理和程序运行更加得心应手。 二、什么是函数返回值? 在计算机编程中,函数是一段封装了特定功能的可重用代码块。当我们调用一个函数时,它会执行一些特定的操作,并返回一个结果。这个结果通常被称为函数的返回值。返回值是我们根据函数的功能期望得到的结果。 三、如何实现在函数返回值的基础上进行逻辑判断? 假设我们有一个名为is_even()的函数,它的功能是判断输入的数字是否为偶数。该函数的实现如下: bash function is_even { local number=$1 if [ $((number % 2)) -eq 0 ]; then echo "$number 是偶数" else echo "$number 不是偶数" fi } 我们可以使用这个函数并获取其返回值: bash result=$(is_even 5) echo "函数返回值:$result" 在这个例子中,我们通过将函数的返回值赋给变量result,然后打印出这个变量的值来查看函数的输出。 接下来,我们可以基于这个返回值来进行逻辑判断。例如,如果我们想要检查一个数字是否为偶数,我们可以这样做: bash if [ $(is_even $num) == "数字是偶数" ]; then echo "数字$num是偶数" else echo "数字$num不是偶数" fi 在这个例子中,我们首先调用了is_even()函数,并将结果赋给了变量result。接着,咱们把result这个家伙的数值,跟一句“数字是偶数”对对碰一下。如果两者相等,我们就认为数字是偶数,否则就认为数字不是偶数。 四、结论 在Shell编程中,我们可以通过获取函数的返回值,并基于这些返回值进行逻辑判断,来实现更复杂的任务。这需要我们理解函数的工作原理,以及如何正确地使用和操作返回值。总的来说,这个技能真的是超级实用,它能够实实在在地帮我们把代码编写得更溜,管理起来也更加得心应手。
2023-12-12 21:33:31
114
冬日暖阳-t
Golang
...换通常涉及到更复杂的逻辑判断和错误处理机制。
2023-03-08 13:29:34
722
幽谷听泉-t
Golang
...断言:理解与应对代码逻辑错误导致的断言失败 1. 引言 --- 在Golang的世界里,断言是一种强大的工具,它允许我们在程序运行时确认某个条件为真。其实,很多时候断言失败,那都是因为代码逻辑出了岔子导致的。这就像是我们的期望和实际情况闹了别扭,让程序在跑起来的时候“摔了个跟头”。本文将通过深入探讨和丰富的实例,帮助你理解Golang中的断言机制以及如何处理因代码逻辑错误导致的断言失败。 2. 什么是断言? --- 在Golang中,assertion(断言)主要用于在非测试代码中验证程序内部状态。assert函数并不是Golang标准库的一部分,但我们可以自定义实现。例如: go func assert(condition bool, message string) { if !condition { panic(message) } } // 使用示例 i := 10 assert(i == 10, "预期值应为10,但实际上不是") 当assert函数接收到的条件不满足时,会触发panic异常,抛出一个错误信息。这就是对代码状态的一种“健康检查”——就像是我们在心里默念,希望某个状况能按预期出现。如果没出现,那好比医生告诉你,“哎呀,有个小问题需要处理一下了”。 3. 断言失败的原因 代码逻辑错误 --- 断言失败通常是由于我们的编程逻辑与实际执行结果不符导致的。下面是一个简单的例子来说明这个问题: go func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("除数不能为零") } result := a / b // 这里忽略了可能的整数溢出问题 assert(result b == a, "除法运算结果有误") // 断言可能会失败,因为存在整数溢出的情况 return result, nil } result, err := divide(1<<63 - 1, -1) // 此处a为int的最大值,b为-1,预期结果应为-1,但由于溢出问题,实际结果并非如此 上述代码中,我们在进行除法操作后添加了一个断言,期望result b等于原始的a。然而,有个情况要敲小黑板强调一下,就是当整数超出它的承受范围时,这个断言就可能扑街,这就无意间揭露出咱们代码逻辑里的一些小bug。 4. 解决断言失败 深度排查与修复逻辑错误 --- 面对断言失败,首先要做的是定位引发问题的具体逻辑,然后修复它。对于上述divide函数的例子,我们可以调整代码以避免整数溢出,并修正断言: go func divide(a, b int) (int, error) { if b == 0 { return 0, errors.New("除数不能为零") } // 添加对溢出的检查 if a > 0 && b < 0 || a < 0 && b > 0 { if a > math.MinInt64/b { return 0, errors.New("运算结果超出int范围") } } result := a / b assert(resultb == a || (a != math.MinInt64 && a != math.MaxInt64), "除法运算结果或边界条件有误") return result, nil } 这里我们不仅修正了断言表达式,还引入了对潜在溢出问题的判断,从而确保断言反映的是正确的程序逻辑。 5. 结语 --- 断言失败如同一面镜子,反映出代码中隐藏的逻辑瑕疵。在使用Golang编程的时候,如果我们能灵活巧妙地运用断言这个小工具,就能像侦探一样揪出那些藏在代码深处的逻辑bug,让它们无处遁形。这样一来,咱们不仅能提高代码的质量,还能让整个程序稳如磐石,运行起来更顺畅、更可靠。记住,断言不是银弹,但它是我们确保代码正确性的重要手段之一。让我们善用断言,洞察代码背后的逻辑世界,共同编织出更健壮、可靠的程序吧!
2023-04-24 17:22:37
491
凌波微步
Impala
...ESTAMP。 - 布尔型:BOOLEAN。 每种数据类型都有其适用场景,选择合适的类型就像是为你的数据穿上最合身的衣服。 3. 如何选择合适的数据类型 3.1 整型的选择 示例代码: sql CREATE TABLE numbers ( id TINYINT, value SMALLINT, count INT, total BIGINT ); 在这个例子中,id 可能只需要一个非常小的范围,所以 TINYINT 是一个不错的选择。而 value 和 count 则可以根据实际需求选择 SMALLINT 或 INT。要是你得对付那些超级大的数字,比如说计算网站的点击量,那 BIGINT 可就派上用场了。 3.2 浮点型的选择 示例代码: sql CREATE TABLE prices ( product_id INT, price FLOAT, discount_rate DOUBLE ); 在处理价格和折扣率这类数据时,FLOAT 足够满足大部分需求。不过,如果是要做金融计算这种得特别精确的事情,还是用 DOUBLE 类型吧,这样数据才靠谱。 3.3 字符串的选择 示例代码: sql CREATE TABLE users ( user_id INT, name STRING, email VARCHAR(255) ); 对于用户名称和电子邮件地址这种信息,我们可以使用 STRING 类型。如果知道字段的最大长度,推荐使用 VARCHAR,这样可以节省一些存储空间。 3.4 日期时间的选择 示例代码: sql CREATE TABLE orders ( order_id INT, order_date TIMESTAMP, delivery_date TIMESTAMP ); 在处理订单日期和交货日期这样的信息时,TIMESTAMP 类型是最直接的选择。这个不仅能存日期,还能带上具体的时间,特别适合用来做时间上的研究和分析。 3.5 布尔型的选择 示例代码: sql CREATE TABLE active_users ( user_id INT, is_active BOOLEAN ); 如果你有一个字段需要表示某种状态是否开启(如用户账户是否激活),那么 BOOLEAN 类型就是最佳选择。它只有两种取值:TRUE 和 FALSE,非常适合用来简化逻辑判断。 4. 性能优化技巧 4.1 减少数据冗余 尽量避免不必要的数据冗余。例如,在多个表中重复存储相同的字符串数据(如用户姓名)。可以考虑使用外键或者创建一个独立的字符串存储表来减少重复数据。 4.2 使用分区表 分区表可以帮助我们更好地管理和优化大型数据集。把数据按时间戳之类的东西分个区,查询起来会快很多,特别是当你 dealing with 时间序列数据的时候。 示例代码: sql CREATE TABLE sales ( year INT, month INT, day INT, amount DECIMAL(10,2) ) PARTITION BY (year, month); 在这个例子中,我们将 sales 表按年份和月份进行了分区,这样查询某个特定时间段的数据就会变得非常高效。 4.3 使用索引 合理利用索引可以大大提高查询速度。不过,在建索引的时候得好好想想,毕竟索引会吃掉一部分存储空间,而且在往里面添加或修改数据时,还得额外花工夫去维护。 示例代码: sql CREATE INDEX idx_user_email ON users(email); 通过在 email 字段上创建索引,我们可以快速查找特定邮箱的用户记录。 5. 结论 通过本文的学习,我们了解了如何在Impala中选择合适的数据类型以及如何通过这些选择来优化查询性能。希望这些知识能够帮助你在实际工作中做出更好的决策。记住啊,选数据类型和搞性能优化这事儿,就跟学骑自行车一样,得不停地练。别害怕摔跤,每次跌倒都是长经验的好机会!祝你在这个过程中找到乐趣,享受数据带来的无限可能!
2025-01-15 15:57:58
35
夜色朦胧
转载文章
...面的,因为它做了各种判断和CAS,但是差距不是特别大 读取效率很高,100个线程同时访问,每个线程读取一百万次实测 Hashtable 39s ,SynchronizedHashMap 38s ,ConcurrentHashMap 1.7s 前两个将近40秒,ConcurrentHashMap只需要不到2s,由此可见此容器读取效率极高 2、为什么推荐使用Queue来做高并发 为什么推荐Queue(队列) Queue接口提供了很多针对多线程非常友好的API(offer ,peek和poll,其中BlockingQueue还添加了put和take可以阻塞),可以说专门为多线程高并发而创造的接口,所以一般我们使用Queue而不用List 以下代码分别使用链表LinkList和ConcurrentQueue,对比一下速度 LinkList用了5s多,ConcurrentQueue几乎瞬间完成 Concurrent接口就是专为多线程设计,多线程设计要多考虑Queue(高并发用)的使用,少使用List / 有N张火车票,每张票都有一个编号 同时有10个窗口对外售票 请写一个模拟程序 分析下面的程序可能会产生哪些问题? 重复销售?超量销售? 使用Vector或者Collections.synchronizedXXX 分析一下,这样能解决问题吗? 就算操作A和B都是同步的,但A和B组成的复合操作也未必是同步的,仍然需要自己进行同步 就像这个程序,判断size和进行remove必须是一整个的原子操作 @author 马士兵/import java.util.LinkedList;import java.util.List;import java.util.concurrent.TimeUnit;public class TicketSeller3 {static List<String> tickets = new LinkedList<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {synchronized(tickets) {if(tickets.size() <= 0) break;try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("销售了--" + tickets.remove(0));} }}).start();} }} 队列 import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class TicketSeller4 {static Queue<String> tickets = new ConcurrentLinkedQueue<>();static {for(int i=0; i<1000; i++) tickets.add("票 编号:" + i);}public static void main(String[] args) {for(int i=0; i<10; i++) {new Thread(()->{while(true) {String s = tickets.poll();if(s == null) break;else System.out.println("销售了--" + s);} }).start();} }} 3、多线程常用容器 1、ConcurrentHashMap(无序)和ConcurrentSkipListMap(有序,链表,使用跳表数据结构,让查询更快) 跳表:http://blog.csdn.net/sunxianghuang/article/details/52221913 import java.util.;import java.util.concurrent.ConcurrentHashMap;import java.util.concurrent.ConcurrentSkipListMap;import java.util.concurrent.CountDownLatch;public class T01_ConcurrentMap {public static void main(String[] args) {Map<String, String> map = new ConcurrentHashMap<>();//Map<String, String> map = new ConcurrentSkipListMap<>(); //高并发并且排序//Map<String, String> map = new Hashtable<>();//Map<String, String> map = new HashMap<>(); //Collections.synchronizedXXX//TreeMapRandom r = new Random();Thread[] ths = new Thread[100];CountDownLatch latch = new CountDownLatch(ths.length);long start = System.currentTimeMillis();for(int i=0; i<ths.length; i++) {ths[i] = new Thread(()->{for(int j=0; j<10000; j++) map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000));latch.countDown();});}Arrays.asList(ths).forEach(t->t.start());try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println(end - start);System.out.println(map.size());} } 2、CopyOnWriteList(写时复制)和CopyOnWriteSet 适用于,高并发是,读的多,写的少的情况 当我们写的时候,将容器复制,让写线程去复制的线程写(写的时候加锁) 而读线程依旧去读旧的(读的时候不加锁) 当写完,将对象指向复制后的已经写完的容器,原来容器销毁 大大提高读的效率 / 写时复制容器 copy on write 多线程环境下,写时效率低,读时效率高 适合写少读多的环境 @author 马士兵/import java.util.ArrayList;import java.util.Arrays;import java.util.List;import java.util.Random;import java.util.Vector;import java.util.concurrent.CopyOnWriteArrayList;public class T02_CopyOnWriteList {public static void main(String[] args) {List<String> lists = //new ArrayList<>(); //这个会出并发问题!//new Vector();new CopyOnWriteArrayList<>();Random r = new Random();Thread[] ths = new Thread[100];for(int i=0; i<ths.length; i++) {Runnable task = new Runnable() {@Overridepublic void run() {for(int i=0; i<1000; i++) lists.add("a" + r.nextInt(10000));} };ths[i] = new Thread(task);}runAndComputeTime(ths);System.out.println(lists.size());}static void runAndComputeTime(Thread[] ths) {long s1 = System.currentTimeMillis();Arrays.asList(ths).forEach(t->t.start());Arrays.asList(ths).forEach(t->{try {t.join();} catch (InterruptedException e) {e.printStackTrace();} });long s2 = System.currentTimeMillis();System.out.println(s2 - s1);} } 3、synchronizedList和ConcurrentLinkedQueue package com.mashibing.juc.c_025;import java.util.ArrayList;import java.util.Collections;import java.util.List;import java.util.Queue;import java.util.concurrent.ConcurrentLinkedQueue;public class T04_ConcurrentQueue {public static void main(String[] args) {List<String> strsList = new ArrayList<>();List<String> strsSync = Collections.synchronizedList(strsList);//加锁ListQueue<String> strs = new ConcurrentLinkedQueue<>();//Concurrent链表队列,就是读快for(int i=0; i<10; i++) {strs.offer("a" + i); //add添加,但是不同点是,此方法会返回一个布尔值}System.out.println(strs);System.out.println(strs.size());System.out.println(strs.poll());//取出,取完后将元素去除System.out.println(strs.size());System.out.println(strs.peek());//取出,但是不会将元素从队列删除System.out.println(strs.size());//双端队列Deque} } 4、LinkedBlockingQueue 链表阻塞队列(无界链表,可以一直装东西,直到内存满(其实,也不是无限,其长度Integer.MaxValue就是上限,毕竟最大就这么大)) 主要体现在put和take方法,put添加的时候,如果队列满了,就阻塞当前线程,直到队列有空位,继续插入。take方法取的时候,如果没有值,就阻塞,等有值了,立马去取 import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.TimeUnit;public class T05_LinkedBlockingQueue {static BlockingQueue<String> strs = new LinkedBlockingQueue<>();static Random r = new Random();public static void main(String[] args) {new Thread(() -> {for (int i = 0; i < 100; i++) {try {strs.put("a" + i); //如果满了,当前线程就会等待(实现阻塞),等多会有空位,将值插入TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();} }}, "p1").start();for (int i = 0; i < 5; i++) {new Thread(() -> {for (;;) {try {System.out.println(Thread.currentThread().getName() + " take -" + strs.take()); //取内容,如果空了,当前线程就会等待(实现阻塞)} catch (InterruptedException e) {e.printStackTrace();} }}, "c" + i).start();} }} 5、ArrayBlockingQueue 有界阻塞队列(因为Array需要指定长度) import java.util.Random;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue;import java.util.concurrent.TimeUnit;public class T06_ArrayBlockingQueue {static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);static Random r = new Random();public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) {strs.put("a" + i);}//strs.put("aaa"); //满了就会等待,程序阻塞//strs.add("aaa");//strs.offer("aaa");strs.offer("aaa", 1, TimeUnit.SECONDS);System.out.println(strs);} } 6、特殊的阻塞队列1:DelayQueue 延时队列(按时间进行调度,就是隔多长时间运行,谁隔的少,谁先) 以下例子中,我们添加线程到队列顺序为t12345,正常情况下,会按照顺序运行,但是这里有了延时时间,也就是时间越短,越先执行 步骤很简单,拿到延时队列 指定构造方法 继承 implements Delayed 重写 compareTo和getDelay import java.util.Calendar;import java.util.Random;import java.util.concurrent.BlockingQueue;import java.util.concurrent.DelayQueue;import java.util.concurrent.Delayed;import java.util.concurrent.TimeUnit;public class T07_DelayQueue {static BlockingQueue<MyTask> tasks = new DelayQueue<>();static Random r = new Random();static class MyTask implements Delayed {String name;long runningTime;MyTask(String name, long rt) {this.name = name;this.runningTime = rt;}@Overridepublic int compareTo(Delayed o) {if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS))return -1;else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) return 1;else return 0;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic String toString() {return name + " " + runningTime;} }public static void main(String[] args) throws InterruptedException {long now = System.currentTimeMillis();MyTask t1 = new MyTask("t1", now + 1000);MyTask t2 = new MyTask("t2", now + 2000);MyTask t3 = new MyTask("t3", now + 1500);MyTask t4 = new MyTask("t4", now + 2500);MyTask t5 = new MyTask("t5", now + 500);tasks.put(t1);tasks.put(t2);tasks.put(t3);tasks.put(t4);tasks.put(t5);System.out.println(tasks);for(int i=0; i<5; i++) {System.out.println(tasks.take());//获取的是toString方法返回值} }} 7、特殊的阻塞队列2:PriorityQueque 优先队列(二叉树算法,就是排序) import java.util.PriorityQueue;public class T07_01_PriorityQueque {public static void main(String[] args) {PriorityQueue<String> q = new PriorityQueue<>();q.add("c");q.add("e");q.add("a");q.add("d");q.add("z");for (int i = 0; i < 5; i++) {System.out.println(q.poll());} }} 8、特殊的阻塞队列3:SynchronusQueue 同步队列(线程池用处非常大) 此队列容量为0,当插入元素时,必须同时有个线程往外取 就是说,当你往这个队列里面插入一个元素,它就拿着这个元素站着(阻塞),直到有个取元素的线程来,它就把元素交给它 就是用来同步数据的,也就是线程间交互数据用的一个特殊队列 package com.mashibing.juc.c_025;import java.util.concurrent.BlockingQueue;import java.util.concurrent.SynchronousQueue;public class T08_SynchronusQueue { //容量为0public static void main(String[] args) throws InterruptedException {BlockingQueue<String> strs = new SynchronousQueue<>();new Thread(()->{//这个线程就是消费者,来取值try {System.out.println(strs.take());//和同步队列要值} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.put("aaa"); //阻塞等待消费者消费,就拿着aaa站着,等线程来取//strs.put("bbb");//strs.add("aaa");System.out.println(strs.size());} } 9、特殊的阻塞队列4:TransferQueue 传递队列 此队列加入了一个方法transfer()用来向队列添加元素 但是和put()方法不同的是,put添加完元素就走了 而这个方法,添加完自己就阻塞了,直到有人将这个元素取走,它才继续工作(省去我们手动阻塞) import java.util.concurrent.LinkedTransferQueue;public class T09_TransferQueue {public static void main(String[] args) throws InterruptedException {LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();strs.transfer("aaa");//放东西到队列,同时阻塞等待消费者线程,取走元素//strs.put("aaa");//如果用put就和普通队列一样,放完东西就走了/new Thread(() -> {try {System.out.println(strs.take());} catch (InterruptedException e) {e.printStackTrace();} }).start();/} } 3、线程池 线程池 由于单独创建线程,十分影响效率,而且无法对线程集中管理,一旦疏落,可能线程无限执行,浪费资源 线程池就是一个存储线程的游泳池,而每个线程就是池子里面的赛道 池子里的线程不执行任何任务,只是提供一个资源 而谁提交了任务,比如我想来游泳,那么池子就给你一个赛道,让你游泳 比如它想练憋气,那么给它一个赛道练憋气 当他们用完,走了,那么后面其它人再过来继续用 这就是线程池,始终只有这几个线程,不做实现,而是借用这几个线程的用户,自己掌控用这些线程资源做什么(提交任务给线程,线程空闲就帮他们完成任务) 线程池的两种类型(两类,不是两个) ThreadPoolExecutor(简称TPE) ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 1、常用类 Executor ExecutorService 扩展了execute方法,具有一个返回值 规定了异步执行机制,提供了一些执行器方法,比如shutdown()关闭等 但是它不知道执行器中的线程何时执行完 Callable 对Runnable进行了扩展,实现Callable的调用,可以有返回值,表示线程的状态 但是无法返回线程执行结果 Future 获得未来线程执行结果 由此,我们可以得知线程池基本的一个使用步骤 其中service.submit():为异步提交,也就是说,主线程该干嘛干嘛,我是异步执行的,和同步不一样(当前线程执行完,主线程才能继续执行,叫同步) futuer.get():获取结果集结果,此时因为异步,主线程执行到这里,结果集可能还没封装好,所以此时如果没有值,就阻塞,直到结果集出来 public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<String> c = new Callable() {@Overridepublic String call() throws Exception {return "Hello Callable";} };ExecutorService service = Executors.newCachedThreadPool();Future<String> future = service.submit(c); //异步System.out.println(future.get());//阻塞service.shutdown();} 2、FutureTask 可充当任务的结果集 上面我们介绍Future是用来得到任务的执行结果的 而FutureTask,可以当做一个任务用,并且返回任务的结果,也就是可以跑线程,然后还可以得到线程结果 public static void main(String[] args) throws InterruptedException, ExecutionException {FutureTask<Integer> task = new FutureTask<>(()->{TimeUnit.MILLISECONDS.sleep(500);return 1000;}); //new Callable () { Integer call();}new Thread(task).start();System.out.println(task.get()); //阻塞} 3、CompletableFuture 非常灵活的任务结果集 一个非常灵活的结果集 他可以将很多执行不同任务的线程的结果进行汇总 比如一个网站,它可以启动多个线程去各大电商网站,比如淘宝,京东,收集某些或某一个商品的价格 最后,将获取的数据进行整合封装 最终,客户就可以通过此网站,获取某类商品在各网站的价格信息 / 假设你能够提供一个服务 这个服务查询各大电商网站同一类产品的价格并汇总展示 @author 马士兵 http://mashibing.com/import java.io.IOException;import java.util.Random;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;public class T06_01_CompletableFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {long start, end;/start = System.currentTimeMillis();priceOfTM();priceOfTB();priceOfJD();end = System.currentTimeMillis();System.out.println("use serial method call! " + (end - start));/start = System.currentTimeMillis();CompletableFuture<Double> futureTM = CompletableFuture.supplyAsync(()->priceOfTM());CompletableFuture<Double> futureTB = CompletableFuture.supplyAsync(()->priceOfTB());CompletableFuture<Double> futureJD = CompletableFuture.supplyAsync(()->priceOfJD());CompletableFuture.allOf(futureTM, futureTB, futureJD).join();//当所有结果集都获取到,才汇总阻塞CompletableFuture.supplyAsync(()->priceOfTM()).thenApply(String::valueOf).thenApply(str-> "price " + str).thenAccept(System.out::println);end = System.currentTimeMillis();System.out.println("use completable future! " + (end - start));try {System.in.read();} catch (IOException e) {e.printStackTrace();} }private static double priceOfTM() {delay();return 1.00;}private static double priceOfTB() {delay();return 2.00;}private static double priceOfJD() {delay();return 3.00;}/private static double priceOfAmazon() {delay();throw new RuntimeException("product not exist!");}/private static void delay() {int time = new Random().nextInt(500);try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.printf("After %s sleep!\n", time);} } 4、TPE型线程池1:ThreadPoolExecutor 原理及其参数 线程池由两个集合组成,一个集合存储线程,一个集合存储任务 存储线程:可以规定大小,最多可以有多少个,以及指定核心线程数量(不会被回收) 任务队列:存储任务 细节:初始线程池没有线程,当有一个任务来,线程池起一个线程,又有一个任务来,再起一个线程,直到达到核心线程数量 核心线程数量达到时,新来的任务将存储到任务队列中等待核心线程处理完成,直到任务队列也满了 当任务队列满了,此时再次启动一个线程(非核心线程,一旦空闲,达到指定时间将会消失),直到达到线程最大数量 当线程容器和任务容器都满了,又来了线程,将会执行拒绝策略 上面的细节涉及的所有步骤内容,均由创建线程池的参数执行 下面是ThreadPoolExecutor构造方法参数的源码注释 / 用给定的初始值,创建一个新的线程池 @param corePoolSize 核心线程数量 @param maximumPoolSize 最大线程数量 @param keepAliveTime 当线程数大于核心线程数量时,空闲的线程可生存的时间 @param unit 时间单位 @param workQueue 任务队列,只能包含由execute提交的Runnable任务 @param threadFactory 工厂,用于创建线程给线程池调度的工厂,可以自定义 @param handler 拒绝策略(可以自定义,JDK默认提供4种),当线程边界和队列容量已经满了,新来线程被阻塞时使用的处理程序/public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) JDK提供的4种拒绝策略,不常用,一般都是自己定义拒绝策略 Abort:抛异常 Discard:扔掉,不抛异常 DiscardOldest:扔掉排队时间最久的(将队列中排队时间最久的扔掉,然后让新来的进来) CallerRuns:调用者处理任务(谁通过execute方法提交任务,谁处理) ThreadPoolExecutor继承关系 继承关系:ThreadPoolExecutor->AbstractExectorService类->ExectorService接口->Exector接口 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面创建线程池,哪里用到了它 使用实例 import java.io.IOException;import java.util.concurrent.;public class T05_00_HelloThreadPool {static class Task implements Runnable {private int i;public Task(int i) {this.i = i;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + " Task " + i);try {System.in.read();} catch (IOException e) {e.printStackTrace();} }@Overridepublic String toString() {return "Task{" +"i=" + i +'}';} }public static void main(String[] args) {ThreadPoolExecutor tpe = new ThreadPoolExecutor(2, 4,60, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());//创建线程池,核心2个,最大4个,空闲线程存活时间60s,任务队列容量4,使用默认线程工程,创建线程。拒绝策略是JDK提供的for (int i = 0; i < 8; i++) {tpe.execute(new Task(i));//供提交8次任务}System.out.println(tpe.getQueue());//查看任务队列tpe.execute(new Task(100));//提交新的任务System.out.println(tpe.getQueue());tpe.shutdown();//关闭线程池} } 5、TPE型线程池2:SingleThreadPool 单例线程池(只有一个线程) 为什么有单例线程池 有任务队列,有线程池管理机制 Executors(注意这后面有s) 它可以说是线程池工厂类,我们一般通过它创建线程池,并且它为我们封装了线程 看看下面哪里用到了它 /创建单例线程池,扔5个任务进去,查看输出结果,看看有几个线程执行任务/import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();for(int i=0; i<5; i++) {final int j = i;service.execute(()->{System.out.println(j + " " + Thread.currentThread().getName());});} }} 6、TPE型线程池3:CachedPool 缓存,存储线程池 此线程池没有核心线程,来一个任务启动一个线程(最多Integer.MaxValue,不会放在任务队列,因为任务队列容量为0),每个线程空闲后,只能活60s 实例 import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class T07_SingleThreadPool {public static void main(String[] args) {ExecutorService service = Executors.newSingleThreadExecutor();//通过Executors获取池子for(int i=0; i<5; i++) {final int j = i;service.execute(()->{//提交任务System.out.println(j + " " + Thread.currentThread().getName());});}service.shutdown();} } 7、TPE型线程池4:FixedThreadPool 固定线程池 此线次池,用于创建一个固定线程数量的线程池,不会回收 实例 import java.util.ArrayList;import java.util.List;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class T09_FixedThreadPool {public static void main(String[] args) throws InterruptedException, ExecutionException {//并发执行long start = System.currentTimeMillis();getPrime(1, 200000); long end = System.currentTimeMillis();System.out.println(end - start);//输出并发执行耗费时间final int cpuCoreNum = 4;//并行执行ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum);MyTask t1 = new MyTask(1, 80000); //1-5 5-10 10-15 15-20MyTask t2 = new MyTask(80001, 130000);MyTask t3 = new MyTask(130001, 170000);MyTask t4 = new MyTask(170001, 200000);Future<List<Integer>> f1 = service.submit(t1);Future<List<Integer>> f2 = service.submit(t2);Future<List<Integer>> f3 = service.submit(t3);Future<List<Integer>> f4 = service.submit(t4);start = System.currentTimeMillis();f1.get();f2.get();f3.get();f4.get();end = System.currentTimeMillis();System.out.println(end - start);//输出并行耗费时间}static class MyTask implements Callable<List<Integer>> {int startPos, endPos;MyTask(int s, int e) {this.startPos = s;this.endPos = e;}@Overridepublic List<Integer> call() throws Exception {List<Integer> r = getPrime(startPos, endPos);return r;} }static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;}static List<Integer> getPrime(int start, int end) {List<Integer> results = new ArrayList<>();for(int i=start; i<=end; i++) {if(isPrime(i)) results.add(i);}return results;} } 8、TPE型线程池5:ScheduledPool 预定,延时线程池 根据延时时间(隔多长时间后运行),排序,哪个线程先执行,用户只需要指定核心线程数量 此线程池返回的池对象,和提交任务方法都不一样,比较涉及到时间 import java.util.Random;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.TimeUnit;public class T10_ScheduledPool {public static void main(String[] args) {ScheduledExecutorService service = Executors.newScheduledThreadPool(4);service.scheduleAtFixedRate(()->{//提交延时任务try {TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000));} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName());}, 0, 500, TimeUnit.MILLISECONDS);//指定延时时间和单位,第一个任务延时0毫秒,之后的任务,延时500毫秒} } 9、手写拒绝策略小例子 import java.util.concurrent.;public class T14_MyRejectedHandler {public static void main(String[] args) {ExecutorService service = new ThreadPoolExecutor(4, 4,0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(6),Executors.defaultThreadFactory(),new MyHandler());//将手写拒绝策略传入}static class MyHandler implements RejectedExecutionHandler {//1、继承RejectedExecutionHandler@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {//2、重写方法//log("r rejected")//伪代码,表示通过log4j.log()报一下日志,拒绝的时间,线程名//save r kafka mysql redis//可以尝试保存队列//try 3 times //可以尝试几次,比如3次,重新去抢队列,3次还不行就丢弃if(executor.getQueue().size() < 10000) {//尝试条件,如果size>10000了,就执行拒绝策略//try put again();//如果小于10000,尝试将其放到队列中} }} } 10、ForkJoinPool线程池1:ForkJoinPool 前面我们讲过线程分为两大类,TPE和FJP ForkJoinPool(分解汇总任务(将任务细化,最后汇总结果),少量线程执行多个任务(子任务,TPE做不到先执行子任务),CPU密集型) 适合将大任务切分成多个小任务运行 两个方法,fork():分子任务,将子任务分配到线程池中 join():当前任务的计算结果,如果有子任务,等子任务结果返回后再汇总 下面实例实现,一百万个随机数求和,由两种方法实现,一种ForkJoinPool分任务并行,一种使用单线程做 import java.io.IOException;import java.util.Arrays;import java.util.Random;import java.util.concurrent.ForkJoinPool;import java.util.concurrent.RecursiveAction;import java.util.concurrent.RecursiveTask;public class T12_ForkJoinPool {//1000000个随机数求和static int[] nums = new int[1000000];//一堆数static final int MAX_NUM = 50000;//分任务时,每个任务的操作量不能多于50000个,否则就继续细分static Random r = new Random();//使用随机数将数组初始化static {for(int i=0; i<nums.length; i++) {nums[i] = r.nextInt(100);}System.out.println("---" + Arrays.stream(nums).sum()); //stream api 单线程就这么做,一个一个加}//分任务,需要继承,可以继承RecursiveAction(不需要返回值,一般用在不需要返回值的场景)或//RecursiveTask(需要返回值,我们用这个,因为我们需要最后获取求和结果)两个更好实现的类,//他俩继承与ForkJoinTaskstatic class AddTaskRet extends RecursiveTask<Long> {private static final long serialVersionUID = 1L;int start, end;AddTaskRet(int s, int e) {start = s;end = e;}@Overrideprotected Long compute() {if(end-start <= MAX_NUM) {//如果任务操作数小于规定的最大操作数,就进行运算,long sum = 0L;for(int i=start; i<end; i++) sum += nums[i];return sum;//返回结果} //如果分配的操作数大于规定,就继续细分(简单的重中点分,两半)int middle = start + (end-start)/2;//获取中间值AddTaskRet subTask1 = new AddTaskRet(start, middle);//传入起始值和中间值,表示一个子任务AddTaskRet subTask2 = new AddTaskRet(middle, end);//中间值和结尾值,表示一个子任务subTask1.fork();//分任务subTask2.fork();//分任务return subTask1.join() + subTask2.join();//最后返回结果汇总} }public static void main(String[] args) throws IOException {/ForkJoinPool fjp = new ForkJoinPool();AddTask task = new AddTask(0, nums.length);fjp.execute(task);/ForkJoinPool fjp = new ForkJoinPool();//创建线程池AddTaskRet task = new AddTaskRet(0, nums.length);//创建任务fjp.execute(task);//传入任务long result = task.join();//返回汇总结果System.out.println(result);//System.in.read();} } 11、ForkJoinPool线程池2:WorkStealingPool 任务偷取线程池 原来的线程池,都是有一个任务队列,而这个不同,它给每个线程都分配了一个任务队列 当某一个线程的任务队列没有任务,并且自己空闲,它就去其它线程的任务队列中偷任务,所以叫任务偷取线程池 细节:当线程自己从自己的任务队列拿任务时,不需要加锁,但是偷任务时,因为有两个线程,可能发生同步问题,需要加锁 此线程继承FJP 实例 import java.io.IOException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class T11_WorkStealingPool {public static void main(String[] args) throws IOException {ExecutorService service = Executors.newWorkStealingPool();System.out.println(Runtime.getRuntime().availableProcessors());service.execute(new R(1000));service.execute(new R(2000));service.execute(new R(2000));service.execute(new R(2000)); //daemonservice.execute(new R(2000));//由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出System.in.read(); }static class R implements Runnable {int time;R(int t) {this.time = t;}@Overridepublic void run() {try {TimeUnit.MILLISECONDS.sleep(time);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(time + " " + Thread.currentThread().getName());} }} 12、流式API:ParallelStreamAPI 不懂的请参考:https://blog.csdn.net/grd_java/article/details/110265219 实例 import java.util.ArrayList;import java.util.List;import java.util.Random;public class T13_ParallelStreamAPI {public static void main(String[] args) {List<Integer> nums = new ArrayList<>();Random r = new Random();for(int i=0; i<10000; i++) nums.add(1000000 + r.nextInt(1000000));//System.out.println(nums);long start = System.currentTimeMillis();nums.forEach(v->isPrime(v));long end = System.currentTimeMillis();System.out.println(end - start);//使用parallel stream apistart = System.currentTimeMillis();nums.parallelStream().forEach(T13_ParallelStreamAPI::isPrime);//并行流,将任务切分成子任务执行end = System.currentTimeMillis();System.out.println(end - start);}static boolean isPrime(int num) {for(int i=2; i<=num/2; i++) {if(num % i == 0) return false;}return true;} } 13、总结 总结 Callable相当于一Runnable但是它有返回值 Future:存储执行完产生的结果 FutureTask 相当于Future+Runnable,既可以执行任务,又能获取任务执行的Future结果 CompletableFuture 可以多任务异步,并对多任务控制,整合任务结果,细化完美,比如可以一个任务完成就可以整合结果,也可以所有任务完成才整合结果 4、ThreadPoolExecutor源码解析 依然只讲重点,实际还需要大家按照上篇博客中看源码的方式来看 1、常用变量的解释 // 1. ctl,可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));// 2. COUNT_BITS,Integer.SIZE为32,所以COUNT_BITS为29private static final int COUNT_BITS = Integer.SIZE - 3;// 3. CAPACITY,线程池允许的最大线程数。1左移29位,然后减1,即为 2^29 - 1private static final int CAPACITY = (1 << COUNT_BITS) - 1;// runState is stored in the high-order bits// 4. 线程池有5种状态,按大小排序如下:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATEDprivate static final int RUNNING = -1 << COUNT_BITS;private static final int SHUTDOWN = 0 << COUNT_BITS;private static final int STOP = 1 << COUNT_BITS;private static final int TIDYING = 2 << COUNT_BITS;private static final int TERMINATED = 3 << COUNT_BITS;// Packing and unpacking ctl// 5. runStateOf(),获取线程池状态,通过按位与操作,低29位将全部变成0private static int runStateOf(int c) { return c & ~CAPACITY; }// 6. workerCountOf(),获取线程池worker数量,通过按位与操作,高3位将全部变成0private static int workerCountOf(int c) { return c & CAPACITY; }// 7. ctlOf(),根据线程池状态和线程池worker数量,生成ctl值private static int ctlOf(int rs, int wc) { return rs | wc; }/ Bit field accessors that don't require unpacking ctl. These depend on the bit layout and on workerCount being never negative./// 8. runStateLessThan(),线程池状态小于xxprivate static boolean runStateLessThan(int c, int s) {return c < s;}// 9. runStateAtLeast(),线程池状态大于等于xxprivate static boolean runStateAtLeast(int c, int s) {return c >= s;} 2、构造方法 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {// 基本类型参数校验if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();// 空指针校验if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;// 根据传入参数unit和keepAliveTime,将存活时间转换为纳秒存到变量keepAliveTime 中this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;} 3、提交执行task的过程 public void execute(Runnable command) {if (command == null)throw new NullPointerException();/ Proceed in 3 steps: 1. If fewer than corePoolSize threads are running, try to start a new thread with the given command as its first task. The call to addWorker atomically checks runState and workerCount, and so prevents false alarms that would add threads when it shouldn't, by returning false. 2. If a task can be successfully queued, then we still need to double-check whether we should have added a thread (because existing ones died since last checking) or that the pool shut down since entry into this method. So we recheck state and if necessary roll back the enqueuing if stopped, or start a new thread if there are none. 3. If we cannot queue task, then we try to add a new thread. If it fails, we know we are shut down or saturated and so reject the task./int c = ctl.get();// worker数量比核心线程数小,直接创建worker执行任务if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// worker数量超过核心线程数,任务直接进入队列if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();// 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。// 这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。if (! isRunning(recheck) && remove(command))reject(command);// 这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。// 这儿有3点需要注意:// 1. 线程池不是运行状态时,addWorker内部会判断线程池状态// 2. addWorker第2个参数表示是否创建核心线程// 3. addWorker返回false,则说明任务执行失败,需要执行reject操作else if (!addWorker(command, false))reject(command);} 4、addworker源码解析 private boolean addWorker(Runnable firstTask, boolean core) {retry:// 外层自旋for (;;) {int c = ctl.get();int rs = runStateOf(c);// 这个条件写得比较难懂,我对其进行了调整,和下面的条件等价// (rs > SHUTDOWN) || // (rs == SHUTDOWN && firstTask != null) || // (rs == SHUTDOWN && workQueue.isEmpty())// 1. 线程池状态大于SHUTDOWN时,直接返回false// 2. 线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false// 3. 线程池状态等于SHUTDOWN,且队列为空,直接返回false// Check if queue empty only if necessary.if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 内层自旋for (;;) {int wc = workerCountOf(c);// worker数量超过容量,直接返回falseif (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// 使用CAS的方式增加worker数量。// 若增加成功,则直接跳出外层循环进入到第二部分if (compareAndIncrementWorkerCount(c))break retry;c = ctl.get(); // Re-read ctl// 线程池状态发生变化,对外层循环进行自旋if (runStateOf(c) != rs)continue retry;// 其他情况,直接内层循环进行自旋即可// else CAS failed due to workerCount change; retry inner loop} }boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {final ReentrantLock mainLock = this.mainLock;// worker的添加必须是串行的,因此需要加锁mainLock.lock();try {// Recheck while holding lock.// Back out on ThreadFactory failure or if// shut down before lock acquired.// 这儿需要重新检查线程池状态int rs = runStateOf(ctl.get());if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {// worker已经调用过了start()方法,则不再创建workerif (t.isAlive()) // precheck that t is startablethrow new IllegalThreadStateException();// worker创建并添加到workers成功workers.add(w);// 更新largestPoolSize变量int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;} } finally {mainLock.unlock();}// 启动worker线程if (workerAdded) {t.start();workerStarted = true;} }} finally {// worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作if (! workerStarted)addWorkerFailed(w);}return workerStarted;} 5、线程池worker任务单元 private final class Workerextends AbstractQueuedSynchronizerimplements Runnable{/ This class will never be serialized, but we provide a serialVersionUID to suppress a javac warning./private static final long serialVersionUID = 6138294804551838833L;/ Thread this worker is running in. Null if factory fails. /final Thread thread;/ Initial task to run. Possibly null. /Runnable firstTask;/ Per-thread task counter /volatile long completedTasks;/ Creates with given first task and thread from ThreadFactory. @param firstTask the first task (null if none)/Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;// 这儿是Worker的关键所在,使用了线程工厂创建了一个线程。传入的参数为当前workerthis.thread = getThreadFactory().newThread(this);}/ Delegates main run loop to outer runWorker /public void run() {runWorker(this);}// 省略代码...} 6、核心线程执行逻辑-runworker final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;// 调用unlock()是为了让外部可以中断w.unlock(); // allow interrupts// 这个变量用于判断是否进入过自旋(while循环)boolean completedAbruptly = true;try {// 这儿是自旋// 1. 如果firstTask不为null,则执行firstTask;// 2. 如果firstTask为null,则调用getTask()从队列获取任务。// 3. 阻塞队列的特性就是:当队列为空时,当前线程会被阻塞等待while (task != null || (task = getTask()) != null) {// 这儿对worker进行加锁,是为了达到下面的目的// 1. 降低锁范围,提升性能// 2. 保证每个worker执行的任务是串行的w.lock();// If pool is stopping, ensure thread is interrupted;// if not, ensure thread is not interrupted. This// requires a recheck in second case to deal with// shutdownNow race while clearing interrupt// 如果线程池正在停止,则对当前线程进行中断操作if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();// 执行任务,且在执行前后通过beforeExecute()和afterExecute()来扩展其功能。// 这两个方法在当前类里面为空实现。try {beforeExecute(wt, task);Throwable thrown = null;try {task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {afterExecute(task, thrown);} } finally {// 帮助gctask = null;// 已完成任务数加一 w.completedTasks++;w.unlock();} }completedAbruptly = false;} finally {// 自旋操作被退出,说明线程池正在结束processWorkerExit(w, completedAbruptly);} } 本篇文章为转载内容。原文链接:https://blog.csdn.net/grd_java/article/details/113116244。 该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。 作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。 如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。
2023-07-21 16:19:45
328
转载
Java
...象结构复杂也能准确地判断内容是否相等,从而避免因属性遗漏而导致的比较错误。 总之,理解并有效运用Java中的对象比较方式是编程过程中的基石之一,它不仅关乎程序逻辑的准确性,也在很大程度上影响着应用程序的性能与健壮性。紧跟技术发展趋势,结合实际项目需求,灵活选择和定制合适的比较策略,是每个Java开发者不断提升技能的重要环节。
2023-06-27 10:13:01
314
键盘勇士
Python
...。运用文中介绍的条件判断循环结构,结合Python强大的pandas库进行数据清洗和计算,使得复杂的财务数据分析变得更为高效且精准。 更进一步,Python内置函数的强大性和灵活性,不仅体现在abs()这样的数学运算上,还表现在众多其他场景中。例如,Python 3.9版本引入了新特性——":= walrus operator",它可以简化if条件语句内部的赋值操作,使代码更加简洁易读。这一更新对于解决类似本文所述问题的程序编写具有重要意义,让开发者能够更好地应对实际编程挑战,提升代码质量及执行效率。 综上所述,无论是基础的正数求和问题,还是前沿的数据科学、金融分析等领域,Python以其丰富全面的功能和不断优化的语法设计,持续赋能广大开发者实现高效、准确的数据处理与业务逻辑构建。
2023-04-28 23:59:16
1590
软件工程师
Element-UI
... 方法中加入更多的逻辑判断和状态管理。 此外,为了提升用户体验,还可以为按钮添加相应的样式和提示信息,例如当日期未选择时禁用“确认”按钮,同时在按钮上显示合适的提示文字。 总结 通过以上步骤,我们成功地在 Element UI 的日期选择器中添加了清空和确认按钮,并实现了预期功能。这一过程不仅体现了 Vue.js 和 Element UI 在组件化开发中的灵活性,也展示了我们在面对特定需求时,如何结合现有工具进行创新扩展的能力。在实际工作中,咱们得学会灵活思考、动手实践,不断琢磨怎么让用户跟产品的交流更顺畅、体验更美妙,让技术实实在在地为我们的产品和用户效力,做到贴心又实用。
2023-07-02 11:30:26
496
秋水共长天一色_
JQuery
...s){ //整合输入变量和预设选项 var settings = $.extend({}, defaults, options); //循环每个节点 this.each(function(){ //编写扩展功能 //... }); //回馈当前的jQuery对象以便连续调用 return this; } //预设选项 var defaults = { option1: value1, option2: value2, //... } 代码中使用$.fn来增加jQuery集合,其中myPlugin是插件名。options参数接收用户传入的参数,并与预设选项进行整合。this代表当前的选中的节点,使用each方法循环每个节点,为每个节点编写扩展功能。最后,回馈当前的jQuery对象以便连续调用。 以上是一个简单的框架,我们可以根据实际需要进行修改和增加。下面是一个实现点击按钮使文本加粗的例子: //声明插件 $.fn.bold = function(){ //循环每个节点 this.each(function(){ //获取当前的节点 var $this = $(this); //添加点击事件 $this.on('click', function(){ //判断当前的状态 if($this.css('font-weight') == "bold"){ $this.css('font-weight', 'normal'); }else{ $this.css('font-weight', 'bold'); } }); }); //回馈当前的jQuery对象以便连续调用 return this; } 代码中声明了一个名为bold的插件,使用on方法为节点添加了点击事件。点击事件里判断当前的节点是否加粗,然后切换加粗状态。最后,回馈当前的jQuery对象以便连续调用。
2023-12-24 23:53:36
419
程序媛
转载文章
...件临时路径信息的数组变量。当用户通过选择文件或拍照等方式上传图片后,调用相关接口(如uni.uploadFile)成功返回的结果中会包含此属性。tempFilePaths 数组中的每个元素代表一个临时文件的路径,这些临时文件通常仅在一定时间内有效,可用于后续的预览、上传至服务器等操作。 TypeError , 在JavaScript编程中,TypeError是一种标准内置错误类型,表示由于变量类型不匹配或者无效的操作导致的错误。在uni-app图片上传问题的场景下,出现“TypeError: e.split is not a function”错误提示意味着开发者试图对非字符串类型的对象执行split方法,而split是专门用于字符串处理的方法,无法应用于非字符串类型的变量(在这里是数组res.tempFilePaths)。因此,该错误提示表明了代码逻辑存在类型判断和处理上的问题,需要调整以适应实际的数据类型。
2023-03-05 15:38:13
59
转载
Java
...,对象引用是指存储在变量中的内存地址,该地址指向堆内存中实际的对象实例。例如,在文章中提到的\ ==操作符比较的是两个对象的引用是否相同\ ,即检查两个变量是否都指向同一个内存位置上的对象。 equals方法重写 , 在面向对象编程中,equals方法是Object类的一个基本方法,用于判断两个对象是否相等。当我们在自定义类中覆盖(或重写)这个方法时,可以根据类的业务逻辑和数据成员来决定什么样的情况下两个对象应该被视为相等。如文中所述,“如果重写了equals方法,我们可以根据自己的需求来定制如何比较两个对象的值是否相等”。 String池 , String池是Java虚拟机内部的一种优化机制,它用来存储字符串字面量。每当创建一个字符串字面量时,JVM会首先在String池中查找是否存在相同的字符串,如果存在,则不会创建新的对象,而是返回池中已存在的对象引用。这也就是为什么在某些情况下,即使使用\ ==\ 也能正确比较两个字符串内容是否相等的原因所在,因为它们可能指向了String池中的同一个字符串实例。
2023-08-26 12:21:44
298
月影清风_t
Javascript
...尤其是对于循环条件和逻辑判断部分,要格外谨慎。 此外,Stack Overflow社区也针对此问题进行了深入讨论,众多资深开发者分享了他们在实际工作中遇到的类似案例,以及如何通过自动化测试和代码审查机制来减少这类错误的发生。他们强调,虽然现代IDE具备强大的语法检测功能,但在复杂的项目中,人工复核仍然是不可或缺的一环。 因此,除了依赖工具和技术手段外,开发者还需要不断提高自身的编程素养,培养良好的编码习惯。只有这样,才能在复杂多变的开发环境中,有效避免诸如SyntaxError: Unexpected token这样的低级错误,确保软件系统的稳定运行。
2025-01-19 16:04:29
100
繁华落尽
Groovy
...可以访问其外部作用域变量的匿名函数。它不仅包含了函数体,还包含了一个引用到外部作用域的环境。这种特性让闭包能记住并访问创建时周围环境里的变量,哪怕这个函数已经跑到了别的地方。 代码示例: groovy def createMultiplier(x) { return { y -> x y } } def double = createMultiplier(2) def triple = createMultiplier(3) println(double(5)) // 输出: 10 println(triple(5)) // 输出: 15 在这个例子中,我们定义了一个createMultiplier函数,它接受一个参数x,并返回一个新的闭包。这个闭包接收一个参数y,然后计算x y的结果。这样,我们就能轻松地创建用于乘以不同倍数的函数。 2. 为什么要在函数中返回闭包? 闭包作为返回值的主要好处之一就是它允许我们在函数调用之间共享状态。这就意味着我们可以设计一些可以根据实际情况灵活调整的动态功能,让一切变得更聪明、更顺手!这种方式非常适合于那些需要高度灵活性的应用场景。 代码示例: groovy def createCounter() { def count = 0 return { count++ "Count is now $count" } } def counter = createCounter() println(counter()) // 输出: Count is now 1 println(counter()) // 输出: Count is now 2 println(counter()) // 输出: Count is now 3 在这个例子中,createCounter函数返回了一个闭包,这个闭包每次被调用时都会递增一个内部计数器,并返回当前计数器的值。这种方法让我们可以在不修改全局状态的情况下,实现计数功能。 3. 实战 使用闭包返回值优化代码 有时候,直接在代码中硬编码逻辑可能会导致代码变得复杂且难以维护。这时候,使用闭包作为返回值就可以大大简化我们的代码结构。比如,我们可以通过返回不同的闭包来处理不同的业务逻辑分支。 代码示例: groovy def getOperation(operationType) { switch (operationType) { case 'add': return { a, b -> a + b } case 'subtract': return { a, b -> a - b } default: return { a, b -> a b } // 默认为乘法操作 } } def add = getOperation('add') def subtract = getOperation('subtract') def multiply = getOperation('multiply') // 注意这里会触发默认情况 println(add(5, 3)) // 输出: 8 println(subtract(5, 3)) // 输出: 2 println(multiply(5, 3)) // 输出: 15 在这个例子中,我们定义了一个getOperation函数,它根据传入的操作类型返回不同的闭包。这样,我们就可以动态地选择执行哪种操作,而无需通过if-else语句来判断了。这种方法不仅使代码更简洁,也更容易扩展。 4. 小结与思考 通过以上几个例子,相信你已经对如何在Groovy中使用闭包作为返回值有了一个基本的理解。闭包作为一种强大的工具,不仅可以帮助我们封装逻辑,还能让我们以一种更灵活的方式组织代码。嘿,话说回来,闭包这玩意儿确实挺强大的,但你要是用得太多,就会搞得代码一团乱,别人看着也头疼,自己以后再看可能也会懵圈。所以啊,在用闭包的时候,咱们得好好想想,确保它们真的能让代码变好,而不是捣乱。 希望今天的分享对你有所帮助!如果你有任何疑问或者想了解更多关于Groovy的知识,请随时留言交流。让我们一起探索更多编程的乐趣吧! --- 这篇文章旨在通过具体的例子和口语化的表达方式,帮助读者更好地理解和应用Groovy中的闭包作为返回值的概念。希望这样的内容能让学习过程更加生动有趣!
2024-12-16 15:43:22
148
人生如戏
Javascript
...以直接在字符串里塞进变量和各种表达式,简直不要太方便!你可能已经见过这种东西了,它们看起来就像这样: javascript const name = "Alice"; console.log(Hello, ${name}!); 这段代码会输出 Hello, Alice!。这里的关键在于反引号( )和花括号({}),它们让一切变得不一样。 2. 简单的嵌入 变量和表达式 现在,让我们深入一点。模板字面量不仅限于插入简单的变量。你还可以插入任何有效的JavaScript表达式。比如,我们想输出两个数字相加的结果: javascript const num1 = 5; const num2 = 7; console.log(The sum is ${num1 + num2}.); 这里,${num1 + num2} 就是一个表达式,它的值会被计算并插入到最终的字符串中。 3. 复杂表达式的嵌入 函数调用和条件判断 但真正的乐趣在于处理更复杂的场景。想象一下,你现在正忙着设计一个用户界面,得让它能根据用户的输入,自个儿变出点新东西来。这时候,模板字面量就能大显身手了。 假设我们需要根据年龄来显示不同的欢迎消息: javascript function getGreeting(age) { if (age < 18) { return 'young'; } else if (age < 65) { return 'adult'; } else { return 'senior'; } } const age = 25; console.log(Welcome, you are a ${getGreeting(age)}.); 这段代码中,我们通过调用getGreeting()函数来决定输出哪个词。这不仅仅简化了代码结构,也让逻辑更加清晰易读。 4. 多行字符串与标签模板 模板字面量还有更多玩法,比如多行字符串和标签模板。先来看看多行字符串,这是非常实用的功能,特别是在编写HTML片段或长文本时: javascript const html = This is a multi-line string. ; console.log(html); 再来看看标签模板。这是一种高级用法,允许你在字符串被解析之前对其进行处理。虽然有点复杂,但非常适合做模板引擎或数据绑定等场景: javascript function tag(strings, ...values) { let result = ''; strings.forEach((str, i) => { result += str + (values[i] || ''); }); return result; } const name = 'Alice'; const greeting = tagHello, ${name}!; console.log(greeting); // 输出: Hello, Alice! 这里的tag函数接收两个参数:一个是原始字符串数组,另一个是所有插入表达式的值。通过这种方式,我们可以对最终的字符串进行任意处理。 5. 结论 模板字面量的价值 总之,模板字面量是现代JavaScript开发中不可或缺的一部分。不管是简化日常生活的小事,还是搞定那些繁琐的业务流程,它们都能让你省心不少。希望今天的分享能帮助你在未来的项目中更好地利用这一强大的工具! --- 希望这篇教程对你有所帮助,如果你有任何疑问或想要了解更多细节,别犹豫,直接留言告诉我吧!让我们一起在编程的世界里不断探索前进!
2024-12-10 15:48:06
97
秋水共长天一色
MyBatis
...is动态SQL:根据变量值执行不同SQL语句的艺术 在Java开发领域,MyBatis作为一款优秀的持久层框架,以其高度灵活和强大的SQL映射功能深受开发者喜爱。特别是在应对那些复杂的业务难题时,MyBatis的XML配置文件有个超赞的功能——动态SQL。它就像个聪明的小助手,能够根据我们传递的不同参数值,灵活地现场“编写”并执行不同的SQL语句,真可谓是个省心又给力的好帮手!本文将通过详细的代码示例及通俗易懂的解释,带你一起揭秘这个实用且强大的功能。 1. 动态SQL简介 想象一下这样的场景:你正在设计一个用户查询接口,需要根据请求中传递的不同条件组合来筛选用户数据。如果使用硬编码SQL,这将导致大量冗余或难以维护的SQL语句。而MyBatis提供的动态SQL就为我们提供了一个优雅的解决方案,它允许我们在XML映射文件中编写条件分支、循环等逻辑,以便根据实际需求拼接SQL。 2. 核心标签与使用 在MyBatis的XML映射文件中,有多个用于实现动态SQL的关键标签: - :用于判断条件是否满足,满足则包含其中的SQL片段。 - / / :类似于Java中的switch-case结构,根据不同的条件执行相应的SQL片段。 - :智能地添加WHERE关键字,避免无谓的空格或多余的AND。 - :动态构建UPDATE语句的SET部分。 - :遍历集合,适用于in查询或者批量插入、更新操作。 示例一:条件查询 xml SELECT FROM user AND name LIKE CONCAT('%', {name}, '%') AND age = {age} 在这个例子中,只有当传入的name或age不为null时,对应的SQL条件才会被加入到最终的查询语句中。 示例二:多条件选择 xml SELECT FROM user SELECT FROM user WHERE is_active = 1 SELECT FROM user WHERE name IS NOT NULL 在这个示例中,根据传入的type参数,会选择执行不同的查询语句。 3. 深度探索与思考 使用MyBatis的动态SQL不仅极大地简化了我们的工作,而且提升了代码的可读性和可维护性。瞧,我们能像看故事书一样,直接从那个映射文件里瞅明白SQL是怎么根据输入的参数灵活变动的,这可真是团队一起干活儿和后面维护工作的大宝贝啊! 此外,值得注意的是,虽然动态SQL强大而灵活,但过度使用可能导致SQL解析性能下降。所以,在我们追求代码的“随心所欲”时,也别忘了给性能这块儿上点心。就拿减少那些频繁变动的元素数量、提前把SQL语句好好编译一下这些招数来说,都是能让程序跑得更溜的好方法。 总结来说,MyBatis的动态SQL是我们在应对复杂查询场景时的一把利器。这些动态元素就像是我们的法宝,即使需求七十二变,我们也能轻松写出既简洁又高效的数据库访问代码。这样一来,程序就能更好地模拟现实世界的各种复杂情况,不仅读起来更容易理解,修改起来也更加方便,就像在现实生活中调整家具布局一样简单自然。让我们在实践中不断探索和挖掘MyBatis动态SQL的魅力吧!
2024-02-16 11:34:53
133
风轻云淡_
Shell
...何在Shell中查找变量是否已定义? 在我们的日常Shell编程实践中,我们经常会遇到需要检查某个变量是否已经定义过的情况。这篇内容,咱们要一起深入地“挖掘”一下,在Shell脚本这个神奇世界里,怎么去判断一个变量是否存在。我不仅会给你讲理论知识,还会带着你手把手过一遍实例代码,让你像了解老朋友一样,彻底搞明白Shell中那些变量的状态,可别说我不够接地气! 1. 基础概念 什么是Shell变量 在Shell编程的世界里,变量是存储数据的重要工具,它们可以保存文本、数值等各种类型的数据。在编写Shell脚本时,每个变量都有自己的小名儿。就像每个人都有自己的名字一样,你可以随时给这些变量“朋友”分配一个值,或者在脚本运行的过程中,只要叫出它们的名字,就能获取到它们当前的数值啦。如果试图访问一个未定义的变量,Shell通常会返回一个空字符串或触发错误。 2. 初级方法 测试变量是否为空 首先,我们可以尝试直接引用变量并检查其值是否为空来判断变量是否已定义。不过呢,这种方法并不是百分百合心意,因为就算你定义了变量这个小家伙,可要是从始至终都没给它喂过值,那在系统眼里,它就相当于个“空壳子”啦。 bash 定义一个变量,但不赋值 my_var= 检查变量是否为空 if [ -z "$my_var" ]; then echo "Variable 'my_var' is either undefined or empty." else echo "Variable 'my_var' is defined and has a value." fi 然而,这个方法并不能区分变量是否真的未定义还是仅仅被赋予了空值。所以,这就引出了更精确的方法。 3. 高级技巧 使用declare命令 在Shell中,declare命令可以用来查看和操作变量,其中包括检查变量是否已定义的功能。如果你想查看某个特定变量的具体信息,我们可以灵活运用那个 -v 参数。比方说,你敲入命令带上 -v 选项去查询一个变量,要是这个变量还没被定义过,系统就会俏皮地蹦出一条错误提示告诉你:“嘿,这个变量我还不认识呢!” bash 尝试查询一个可能未定义的变量 if declare -v my_maybe_undefined_var > /dev/null; then echo "Variable 'my_maybe_undefined_var' is defined." else echo "Variable 'my_maybe_undefined_var' is not defined." fi 这个方法的优点在于,无论变量值是否为空,只要它已被声明,都会认为是已定义。 4. 更进一步 使用set命令 另一种方式是使用set命令配合管道与grep命令查找变量名是否存在。尽管这种方法略显复杂,但在某些场景下也十分有用: bash 使用set命令输出所有环境变量列表,然后通过grep搜索特定变量名 if set | grep -q "^my_special_var="; then echo "Variable 'my_special_var' is defined." else echo "Variable 'my_special_var' is not defined." fi 这里,-q选项使得grep命令在匹配成功时不打印任何内容,仅根据匹配结果返回退出状态。如果找到匹配项(即变量已定义),则返回0,否则返回非零值。 结语 在Shell编程中,理解并熟练掌握如何判断变量是否已定义是一项基本且重要的技能。不同的方法适用于不同的情境,有时我们需要根据实际需求灵活运用。整个探索过程的核心,就是我们对Shell编程逻辑那股子钻劲儿和死磕精神,一边不断加深理解,一边持续优化实践,铆足了劲儿,下定决心一路通关到底。希望本文能帮助你更好地驾驭Shell变量,让每一次与Shell的对话都充满智慧与乐趣!
2023-07-08 20:17:42
34
繁华落尽
MyBatis
...XML映射文件中编写逻辑判断,从而实现SQL语句的灵活构建,解决SQL间的依赖关系问题。 事务管理 , 事务管理是一种确保数据库操作原子性和一致性的机制。在本文中,Spring框架提供的@Transactional注解被用来控制多个数据库操作在一个事务内的执行顺序和回滚策略。当一系列数据库更新需要按照特定顺序完成,并且任何一步失败都需要全部回滚时,事务管理就显得尤为重要。 ResultHandler , ResultHandler是MyBatis中的一个接口,允许用户自定义处理结果集的方式。在文章示例中,DeleteResultHandler实现了ResultHandler接口,用于在执行SQL后处理结果,如根据DELETE操作影响的行数决定是否执行后续的SQL更新操作,这样可以有效地处理SQL之间的依赖关系。
2023-07-04 14:47:40
149
凌波微步
Struts2
...设计模式,用于将业务逻辑、数据和用户界面分离。在Struts2框架中,模型(Model)负责处理数据和业务逻辑;视图(View)负责显示数据及接收用户输入,这里即JSP页面;控制器(Controller)则作为中间层,负责接收请求、调用模型进行业务处理,并选择合适的视图返回给用户。 Struts2标签库 , Struts2提供的一系列定制标签,如s:iterator等,这些标签可以嵌入到JSP页面中,帮助开发者更方便地与Action类中的数据进行交互,实现数据的显示、表单元素生成、迭代遍历集合等功能,从而降低Java代码在JSP中的嵌入程度,提高程序可读性和维护性。 迭代状态变量 , 在Struts2中,s:iterator标签支持定义一个迭代状态变量,例如status=rowstatus。这个变量在遍历集合过程中,会自动更新并记录当前迭代的状态信息,如索引值、是否为奇数行/偶数行等。开发者可以利用这个状态变量动态调整输出样式或执行其他条件判断,增强了前端展示效果的灵活性。
2023-01-03 18:14:02
44
追梦人
Python
...该精通掌握的。这涵盖变量值、函数、判断语句、循环体和读写操作等语法。以下是一个例子: message = 'Hello, world!' print(message) 2. 学习数据种类 Python提供多种数据种类,涵盖数值、字符串和列表等。你需要学习如何创建这些数据种类,并了解如何对它们进行操作。 3. 学习函数和模块 函数是Python编程中的重要组成部分,帮助你将代码段封装为可重复使用的块。这相当于一个独立的子程序,它可以被其他的程序调用。另外,学习Python模块也是必须的。Python拥有非常多的模块,帮助你快速地扩展功能。 4. 学习调试技巧 调试是Python编程中不可避免的一部分。你应该学习如何使用Python调试器和其他调试工具,以确保你的代码不会发生意外错误。 5. 学习文件操作 文件操作在Python编程中非常重要。你需要了解如何打开、读取、写入和关闭文件。 6. 学习错误处理 错误处理是Python编程中非常重要的一部分。你需要了解如何使用try、except语句来捕获错误,并对它们进行处理。 Python是一门强大的程序设计语言,你不需要在一天内掌握所有知识,但是掌握上述基本知识是非常重要的。不断地练习和探索,相信你会变为一名杰出的Python开发者。
2023-06-06 20:35:24
123
键盘勇士
Shell
...状况——就是那个用来判断循环该不该继续的条件突然不灵了。本文将深入探讨这种问题,并提供一些解决方案。 二、While循环的基本原理与语法 首先,让我们回顾一下while循环的基本原理和语法。你知道吗,while循环就像是一个超级有耐心的小助手,它会一直重复做同一组任务,直到达到某个特定的要求才肯罢休。说白了,就是在条件没满足之前,它就一直在那儿坚守岗位,一遍又一遍地执行那组语句,可真是个执着的小家伙呢!其基本语法如下: bash while condition; do command1; command2; ... done 在这里,condition是一个布尔表达式,如果为真,则执行do后面的所有命令。 三、while循环条件判断失效的原因分析 那么,为什么我们在使用while循环时会遇到条件判断失效的问题呢?这通常是因为以下几个原因: 1. 条件表达式的错误 条件表达式可能包含语法错误或者逻辑错误,导致条件始终无法得到正确的评估。 2. 无限递归 如果while循环内部调用了其他while循环,而这些循环没有正确地退出,就会形成无限递归,最终导致条件判断失效。 3. 命令执行失败 如果while循环中的命令执行失败(例如,返回非零状态),那么下次循环时,条件表达式的结果就可能被误判为真,导致循环无限制地进行下去。 四、解决while循环条件判断失效的方法 对于以上提到的问题,我们可以采取以下几种方法来解决: 1. 检查并修复条件表达式 首先,我们需要检查while循环的条件表达式是否正确。如果发现有语法错误或逻辑错误,我们就需要对其进行修复。例如,下面的代码中,echo命令输出了非零状态,因此while循环条件判断始终为真: bash num=5 while [ "$num" -gt 0 ]; do echo "Hello World" num=$((num-1)) done 我们应该修复这个错误,确保条件表达式能够正确地评估: bash num=5 while [ "$num" -gt 0 ]; do echo "Hello World" num=$((num-1)) if [ "$num" -le 0 ]; then break fi done 2. 避免无限递归 如果while循环内部调用了其他while循环,我们应该确保这些循环能够在适当的时候退出。例如,下面的代码中,两个while循环相互调用,形成了无限递归: bash i=0 j=0 while [ $i -lt 10 ]; do j=$((j+1)) while [ $j -lt 10 ]; do i=$((i+1)) done done 我们应该调整逻辑,避免无限递归: bash i=0 j=0 while [ $i -lt 10 ]; do j=$((j+1)) while [ $j -lt 10 ]; do i=$((i+1)) j=$((j+1)) done j=0 done 3. 检查命令执行结果 如果我们发现while循环中的命令执行失败,我们就需要找出原因,并修复这个问题。例如,下面的代码中,sleep命令返回了非零状态,导致while循环条件判断始终为真: bash num=5 while true; do sleep 1 num=$((num-1)) if [ "$num" -eq 0 ]; then break fi done 我们应该修复这个错误,确保命令执行成功: bash num=5 while true; do sleep 1 num=$((num-1)) if [ "$num" -eq 0 ]; then break fi if ! some_command; then continue fi done 五、总结 通过本文的学习,我们应该对while循环条件判断失效有了更深刻的理解。无论是排查并搞定条件表达式的bug,防止程序陷入无限循环的漩涡,还是仔细审查命令执行的结果反馈,我们都能运用这些小妙招,手到病除地解决各类问题,让咱们的shell编程稳如磐石,靠得住得很。同时呢,咱们也得养成棒棒的编程习惯了,就像定期给车子做保养一样,时不时地给咱的代码做个“体检”和“调试”,这样一来,就能有效地防止这类问题再冒出来捣乱啦。
2023-07-15 08:53:29
71
蝶舞花间_t
站内搜索
用于搜索本网站内部文章,支持栏目切换。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
journalctl --since "yyyy-mm-dd HH:MM:SS"
- 查看指定时间之后的日志条目。
推荐内容
推荐本栏目内的其它文章,看看还有哪些文章让你感兴趣。
2023-04-28
2023-08-09
2023-06-18
2023-04-14
2023-02-18
2023-04-17
2024-01-11
2023-10-03
2023-09-09
2023-06-13
2023-08-07
2023-03-11
历史内容
快速导航到对应月份的历史文章列表。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"