首页 文章 精选 留言 我的

精选列表

搜索[AI编程],共10000篇文章
优秀的个人博客,低调大师

Java并发编程基础-线程间通信

章节目录 volatile 与 synchronized 关键字 等待/通知机制 等待/通知经典范式 管道输入/输出流 Thread.join() 的 使用 1. volatile 与 synchronized 关键字 线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一行一行的执行,直到终止。如果每个运行中的线程,仅仅是孤立的运行,那么没有价值,或者说价值很少,如果多个线程能够 相互配合 完成工作,这将带来巨大的价值。 1.1 Java 线程操作的共享变量是对共享内存变量的一份拷贝 Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个共享变量的一份拷贝 (虽然对象以及成员变量分配的内存是在共享内存中,但是每个执行的线程还是可以拥有一份拷贝,这样做的目的是 加速程序的执行)。这是现代多核处理器的一个显著特性, 所以在程序执行过程中,(未同步的程序代码块),一个线程看到的变量并不一定是最新的。 1.2 volatile 关键字-线程间通信 关键字volatile可以用来修饰字段(成员变量),就是告知任何对该变量的访问均 需要从共享内存中获取,而对它的改变必须同步刷新到共享内存, 它能保证虽有线程对共享变量的可见性。 举个例子,定义一个程序是否运行的成员变量,boolean on = true; 那么另一个 线程可能对它执行关闭动作(on = false),这涉及多个线程对变量的访问,因此 需要将其定义为 volatile boolean on = true,这样其他线程对他进行改变时,可 以让所有线程感知到变化,因为所有对共享变量的访问(load)和修改(store)都需 要以共享内存为准。但是过多的使用volatile是不必要的,因为它会降低程序执行的效率。 1.3 synchronized 关键字-线程间通信 关键字 synchronized 可以修饰方法 或者以同步块的形来进行使用,它主要确 保多个线程在同一时刻,只能有一个线程执行同步方法或同步块,它保证了线 程对变量访问的可见性、排他性。 如下所示,类中使用了同步块和同步方法,通过使用javap 工具查看生成的class文件信息来分析synchronized关键字实现细节,示例如下: package org.seckill.Thread; public class Synchronized { public static void ls(String[] args) { synchronized (Synchronized.class) { }//静态同步方法,对Synchronized Class对象进行加锁 m(); } public static synchronized void m(){ } } 执行 javap -v Synchronized.class 输出如下所示: public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc #2 // class org/seckill/Thread/Synchronized 2: dup 3: astore_1 4: monitorenter 5: aload_1 6: monitorexit 7: goto 15 10: astore_2 11: aload_1 12: monitorexit 13: aload_2 14: athrow 15: invokestatic #3 // Method m:()V 18: return public static synchronized void m(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=0, locals=0, args_size=0 0: return 对上述汇编指令进行解读 对于同步代码块(临界区)的实现使用了monitorenter 和 monitorexit 指令。 同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED。 另种同步方式的原理是 对一个充当锁的对象的monitor 进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由syntronized 所保护的对象的监视器。 任何一个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取到对象的监视器才能进入到同步块或者同步方法中,那么没有获取到监视器(执行改方法)的线程将会被阻塞在同步块和同步方法的入口处,进入blocked 状态。 如下是对上述解读过程的图示: 对象、监视器、同步队列、执行线程之间的关系 2.等待/通知机制 等待通知相关方法 方法名称 描述 wait() 调用lock.wait()(lock是充当锁的对象)的线程将进入waiting状态,只有等待另外线程的通知或者线程对象.interrupted()才能返回,wait()调用后,会释放对象的锁 wait(long) 超时一段时间,这里的参数是毫秒,也就是等待n毫秒,如果没有通知就超时返回 wait(long,int) 对于超时间的更细粒度控制,可以达到纳秒级别 notify() 通知一个在锁对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁(其实是线程获取到了该对象的monitor对象的控制权) notifyAll() 通知所有等待在充当锁的对象上的线程 对等待通知机制的解释 等待通知机制,是指一个线程A调用了充当锁的对象的wait()方法进入等 waiting 状态 另一个线程B调用了对象的O的 notify() 或者 notifyAll() 方法,线程A接收到通知后从充当锁的对象上的wait()方法返回,进而执行后续操作,最近一次操作是线程从等待队列进入到同步阻塞队列。 上述两个线程通过充当锁的对象 lock 来完成交互,而lock对象上的wait()/notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方的交互工作 如下代码清单所示,创建两个线程 WaitThread & NotifyThread,前者检查flag是否为false,如果符合要求,进行后续操作,否则在lock上wait,后者在睡眠一段时间后对lock进行通知。 package org.seckill.Thread; public class WaitNotify { static boolean flag = true; static Object lock = new Object();//充当锁的对象 public static void main(String[] args) { //新建wait线程 Thread waitThread = new Thread(new WaitThread(),"waitThread"); Thread notifyThread = new Thread(new NotifyThread(),"notifyThread"); waitThread.start();//等待线程开始运行 Interrupted.SleepUnit.second(5);//主线程sleep 5s notifyThread.start(); } //wait线程 static class WaitThread implements Runnable { public void run() { synchronized (lock) { //判定flag while (flag) { try { System.out.println(Thread.currentThread().getName() + "获取flag 信息" + flag); //判定为true 直接wait lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag); } } } static class NotifyThread implements Runnable { public void run() { synchronized (lock) { while (flag) { System.out.println(Thread.currentThread().getName() + "获取flag 信息 为" + flag+"可以运行"); lock.notify();//唤醒wait在lock上的线程,此时wait线程只能能从waiting队列进入阻塞队列,但还没有开始重新进行monitorenter的动作 // 因为锁没有释放 flag = false; Interrupted.SleepUnit.second(5); } } synchronized (lock){//有可能获取到lock对象monitor,获取到锁 System.out.println(Thread.currentThread().getName()+" hold lock again"); Interrupted.SleepUnit.second(5); } } } } 运行结果如下所示: 运行结果 对如上程序运行流程的解释如下所示: 上图中"hold lock again 与 最后一行输出"的位置可能互换,上述例子说明调用wait()、notify()、notifyAll需要注意的细节 使用wait()、notify() 和 notifyAll() 时需要在同步代码块或同步方法中使用,且需要先对调用的锁对象进行加锁(获取充当锁的对象的monitor对象) 调用wait() 方法后,线程状态由running 变为 waiting,并将当前线程放置到等待队列中 notify()、notifyAll() 方法调用后,等待线程依旧不会从wait()方法返回,需要调用notify()、notifyAll()的线程释放锁之后,等待线程才有机会从wait()方法返回 notify() 方法将waiting队列中的一个等待线程从waiting队列 移动到同步队列中,而notifyAll() 则是将等待队列中所有的线程全部移动到同步队列,被移动的线程状态由waiting status change to blocked状态 从wait() 方法返回的前提是获得了调用对象的锁等待/通知机制依托于同步机制,其目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量做出的修改 3.等待/通知经典范式 等待/通知经典范式 该范式分为两部分,分别针对等待方(消费方)、和通知方(生产方)等待方遵循如下原则: 获取对象的锁 如果条件不满足,则调用对象的wait() 方法,被通知后仍要检查条件 条件满足则执行对应的逻辑 对应伪代码 syntronized (lock) { while( !条件满足 ){ lock.wait(); } //对应的处理逻辑 } 通知方遵循如下原则: 获取对象锁 改变条件 通知所有等待在锁对象的线程 syntronized(lock) { //1.执行逻辑 //2.更新条件 lock.notify(); } 4.管道输入输出流 管道输入 / 输出流和普通的文件输入/输出流 或者网络输入/输出流的不同之处在于它主要用于线程之间的数据传输,而传输的媒介为内存。 管道输入 / 输出流主要包括如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader 、PipedWriter 前两种面向字节,后两种面向字符 对于Piped类型的流,必须先进行绑定,也就是调用connect()方法,如果没有输入/输出流绑定起来,对于该流的访问将抛出异常。 5.Thread.join() 的 使用 如果使用了一个线程A执行了thread.join ,其含义是线程A等待thread线程终止之后才从thread.join()返回。 如下笔试题: 有A、B、C、D四个线程,在main线程中运行,要求 执行顺序是A->B->C->D->mian 变种->main等待A、B、C、D四个线程顺序执行,且进行sum,之后main线程打印sum解法1-join() 其实就是插队 package org.seckill.Thread; public class InOrderThread { static int num = 0; public static void main(String[] args) throws InterruptedException { Thread previous = null; for (int i = 0; i < 4; i++) { char threadName = (char) (i + 65); Thread thread = new Thread(new RunnerThread(previous), String.valueOf(threadName)); previous = thread; thread.start(); } previous.join(); System.out.println("total num=" + num); System.out.println(Thread.currentThread().getName() + "terminal"); } static class RunnerThread implements Runnable { Thread previous;//持有前一个线程引用 public RunnerThread(Thread previous) { this.previous = previous; } public void run() { if (this.previous == null) { // num += 25; System.out.println(Thread.currentThread().getName() + " terminate "); } else { try { previous.join(); } catch (InterruptedException e) { e.printStackTrace(); } // num += 25; System.out.println(Thread.currentThread().getName() + " terminate "); } } } } 解法2-wait/notify package org.seckill.Thread; //wait/notify public class InOrderThread2 { // static int state = 0;//运行标志 // static Object lock = new Object(); public static void main(String[] args) { // RunnerThread runnerThreadA = new RunnerThread(); // RunnerThread runnerThreadB = new RunnerThread(); // RunnerThread runnerThreadC = new RunnerThread(); // RunnerThread runnerThreadD = new RunnerThread(); // Thread threadA = new Thread(runnerThreadA, "A"); // Thread threadB = new Thread(runnerThreadB, "B"); // Thread threadC = new Thread(runnerThreadC, "C"); // Thread threadD = new Thread(runnerThreadD, "D"); RunnerThread runnerThread = new RunnerThread(); Thread threadA = new Thread(runnerThread, "A"); Thread threadB = new Thread(runnerThread, "B"); Thread threadC = new Thread(runnerThread, "C"); Thread threadD = new Thread(runnerThread, "D"); threadD.start(); threadA.start(); threadB.start(); threadC.start(); } static class RunnerThread implements Runnable { // private boolean flag = true; static int state = 0;//运行标志 static Object lock = new Object(); public void run() { String threadName = Thread.currentThread().getName(); // while (flag) { // synchronized (lock) { // if (state % 4 == threadName.charAt(0) - 65) { // state++; // flag = false; // System.out.println(threadName + " run over"); // } // } // } synchronized (lock) { while (state % 4 != threadName.charAt(0) - 65) { try { lock.wait(); }catch (InterruptedException e){ e.printStackTrace(); } } state++; System.out.println(threadName+" run over "); lock.notifyAll(); } } } } 等待/通知范式做线程同步 是非常方便的。 解法3-循环获取锁 package org.seckill.Thread; //wait/notify public class InOrderThread2 { static int state = 0;//运行标志 static Object lock = new Object(); public static void main(String[] args) { RunnerThread runnerThreadA = new RunnerThread(); RunnerThread runnerThreadB = new RunnerThread(); RunnerThread runnerThreadC = new RunnerThread(); RunnerThread runnerThreadD = new RunnerThread(); Thread threadA = new Thread(runnerThreadA, "A"); Thread threadB = new Thread(runnerThreadB, "B"); Thread threadC = new Thread(runnerThreadC, "C"); Thread threadD = new Thread(runnerThreadD, "D"); // RunnerThread runnerThread = new RunnerThread(); // Thread threadA = new Thread(runnerThread, "A"); // Thread threadB = new Thread(runnerThread, "B"); // Thread threadC = new Thread(runnerThread, "C"); // Thread threadD = new Thread(runnerThread, "D"); threadD.start(); threadA.start(); threadB.start(); threadC.start(); } static class RunnerThread implements Runnable { private boolean flag = true;//每个线程的私有变量 // static int state = 0;//运行标志 // static Object lock = new Object(); public void run() { String threadName = Thread.currentThread().getName(); while (flag) {//主动循环加锁 synchronized (lock) { if (state % 4 == threadName.charAt(0) - 65) { state++; flag = false; System.out.println(threadName + " run over"); } } } // // synchronized (lock) { // while (state % 4 != threadName.charAt(0) - 65) { // try { // lock.wait(); // }catch (InterruptedException e){ // e.printStackTrace(); // } // } // state++; // System.out.println(threadName+" run over "); // lock.notifyAll(); // } } } } 开销是极大的、难以确保及时性 解法4-CountDownLatch package org.seckill.Thread; import java.util.concurrent.CountDownLatch; public class InOrderThread3 { // static int state = 0;//运行标志 // static Object lock = new Object(); public static void main(String[] args) throws InterruptedException{ CountDownLatch countDownLatchA = new CountDownLatch(1); CountDownLatch countDownLatchB = new CountDownLatch(1); CountDownLatch countDownLatchC = new CountDownLatch(1); CountDownLatch countDownLatchD = new CountDownLatch(1); RunnerThread runnerThreadA = new RunnerThread(countDownLatchA); RunnerThread runnerThreadB = new RunnerThread(countDownLatchB); RunnerThread runnerThreadC = new RunnerThread(countDownLatchC); RunnerThread runnerThreadD = new RunnerThread(countDownLatchD); Thread threadA = new Thread(runnerThreadA, "A"); Thread threadB = new Thread(runnerThreadB, "B"); Thread threadC = new Thread(runnerThreadC, "C"); Thread threadD = new Thread(runnerThreadD, "D"); // RunnerThread runnerThread = new RunnerThread(); // Thread threadA = new Thread(runnerThread, "A"); // Thread threadB = new Thread(runnerThread, "B"); // Thread threadC = new Thread(runnerThread, "C"); // Thread threadD = new Thread(runnerThread, "D"); threadA.start(); countDownLatchA.await();//主线程阻塞,待countDownLatch 减为0即可继续向下运行 threadB.start(); countDownLatchB.await(); threadC.start(); countDownLatchC.await(); threadD.start(); countDownLatchD.await(); System.out.println(Thread.currentThread().getName()+" run over "); } static class RunnerThread implements Runnable { // private boolean flag = true; // static int state = 0;//运行标志 // static Object lock = new Object(); CountDownLatch countDownLatch; RunnerThread(CountDownLatch countDownLatch){ this.countDownLatch = countDownLatch; } public void run() { String threadName = Thread.currentThread().getName(); System.out.println(threadName+" run over"); countDownLatch.countDown(); // while (flag) { // synchronized (lock) { // if (state % 4 == threadName.charAt(0) - 65) { // state++; // flag = false; // System.out.println(threadName + " run over"); // } // } // } // // synchronized (lock) { // while (state % 4 != threadName.charAt(0) - 65) { // try { // lock.wait(); // }catch (InterruptedException e){ // e.printStackTrace(); // } // } // state++; // System.out.println(threadName+" run over "); // lock.notifyAll(); // } } } } countDownLatch 的使用场景 :比如系统完全开启需要等待系统软件全部运行之后才能开启。最终的结果一定是发生在子(部分)结果完成之后的。也可作为线程同步的一种方式Thread join() 源码 public final synchronized void join() throws InterruptedException { while (isAlive) { wait(0); } } 当被调用thread.join() 的线程(thread)终止运行时,会调用自身的notifyAll()方法,会通知所有等待该线程对象上完成运行的线程,可以看到join方法的逻辑结构与等待/通知经典范式一致,即加锁、循环、处理逻辑3个步骤。

优秀的个人博客,低调大师

11.OkHttp之java网络编程

1.TCP/IP协议家族 HTTP,FTP,DNS,TCP,UDP,IP OSI七层模型:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层 五层协议: TCP三次握手(建立连接)和四次挥手(断开连接) HTTP请求:以报文形式通信 客户端请求Request:请求报文=请求头+空行+请求数据 Accept:客户端可以处理的数据格式 Cookie:服务端发送给客户端的cookie 服务端响应Response:响应报文=响应头+空行+响应数据 Content-Type:响应返回的类型(text/html;charset=utf-8) Set-Cookie:服务端给我们的cookie,要保存起来下次带过去 2.接口测试 搭建服务器: 1.下载Eclipse+Tomcat Eclipse Version: Luna Service Release 2 (4.4.2) Tomcat 7.0 2.配置Tomcat 配置tomcat.png 可能会存在的错误 Could not load the Tomcat server configuration at \Servers\Tomcat v5.5 Server at localhost-config. The configuration may be corrupt or incomplete. tomcat配置.png 其实主要是路径问题。解决方法就是将原来的server删掉,在添加一个server,设置server runtime ,tomcat安装目录设对 3.新建一个web工程 右键->new project选择web工程 新建web工程.png 4.将工程发布到tomcat,Add之后,start或者restart tomcat 部署项目到服务器.png 5.用浏览器测试项目工程 在web工程的WebContent目录下创建index.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> Hello OkHttp! </body> </html> 在浏览器中输入http://192.168.1.107:8080/OkHttpServer,如果浏览器打印出Hello OkHttp!则证明成功 6.创建测试接口 import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 为什么eclipse不识别HttpServlet,因为没有servlet架包,要在项目中导入这个架包。 * 工程上右键,选择properties,然后选择java build path,library选项卡,add external jars, * 在tomcat的lib目录下找到servlet-api.jar,加载进来就可以了 * @author renzhenming */ @WebServlet("/TestServlet")//TestServlet前边这里如果不加"/"服务器启动不了(start failed) public class TestServlet extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { //super.doPost(req, resp); PrintWriter writer = resp.getWriter(); writer.write("server data is coming"); writer.flush(); writer.close(); } } 这时候重新执行上一步骤,如果打印出server data is coming则证明测试接口成功,我们来详细看一下当这个请求发出时,两端的交互情况。在浏览器输入http://localhost:8080/OkHttpServer/TestServlet,右键 -->查看元素-->选择网络,此时我们回车浏览器会发送一次请求到服务端,也就是我们的本地服务器,看看此时网络中显示的,这些字段就不一一解释了,可以自己百度 网络请求头响应头.png 我们切换到cookie,可以看到此请求无cookie,那么cookie是什么,如果一个请求有cookie会怎样显示,这是接下来要说的内容 3.Cookie cookie是存放在客户端的信息,来自服务器,当客户端发送请求到服务器时,会携带cookie过去,如果用户离开网站,cookie会在一定时间内过期(相对危险) Session是存放在服务器上面的客户端的临时信息,用户离开网站会被清除(相对安全,但是会耗费资源) Token(令牌):用户身份验证,有点类似Cookie,相对来说更安全,一般流程: 1)客户端向服务端申请token 2)服务端收到请求,验证用户信息,签发一个token给客户端,自己保存token 3)客户端收到token会保存起来,每次请求带上token 4)服务器收到其他请求,会去验证客户端token,成功返回数据 创建一个用户登录servlet,模拟登录过程,当等成功之后,设置cookie返回给客户端,给cookie设置过期时间 import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.alibaba.fastjson.JSON; import bean.ResponseEntity; import bean.UserInfoEntity; @WebServlet("/LoginServlet") public class LoginServlet extends BaseJsonServlet{ private static final long serialVersionUID = 1L; @Override protected ResponseEntity onHandle(HttpServletRequest req, HttpServletResponse resp) throws Exception { ResponseEntity entity = new ResponseEntity(); entity.code = "0022"; entity.msg = "用户名或密码错误"; String userName = req.getParameter("userName"); if ("rzm".equals(userName)) { entity.code = "0000"; entity.msg = "登录成功"; UserInfoEntity usEntity = new UserInfoEntity(); usEntity.userName = userName; usEntity.userSex = "男"; entity.data = JSON.toJSONString(usEntity); Cookie cookie = new Cookie("userName", "rzm"); //设置cookie过期时间为一小时 cookie.setMaxAge(60*60); resp.addCookie(cookie); } return entity; } } 重新部署服务器,在浏览器中请求登录 http://localhost:8080/OkHttpServer/LoginServlet?userName=rzm 注意,这样设置的话,登录成功的提示会是乱码,我们需要设置content-type给响应头,这样就可以正常显示中文了 resp.setContentType("text/html;charset=utf-8"); 我们打开网页元素,看看此时的请求头和响应头有什么变化,第一次登录后服务器会返回一个cookie到客户端,此时由于本地没有cookie,所以请求头中不含有cookie,当第一次请求之后再次请求会携带cookie到服务器,前提是这个cookie没有过期,如果过期则不携带 cookie.png 模拟cookie过期的情况 创建一个用户评论servlet,当cookie存在且未过期时,评论成功,如果不存在或过期,提示用户未登录 import javax.servlet.annotation.WebServlet; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import bean.ResponseEntity; @WebServlet("/UserCommentServlet") public class UserCommentServlet extends BaseJsonServlet{ private static final long serialVersionUID = 1L; @Override protected ResponseEntity onHandle(HttpServletRequest req, HttpServletResponse resp) throws Exception { //判断该用户有没有登录,没有或cookie过期都不让评论 ResponseEntity entity = new ResponseEntity(); entity.code = "0033"; entity.msg = "评论失败,用户未登录"; Cookie cookies[] = req.getCookies(); if (cookies != null) { for(Cookie cookie:cookies){ String userName = cookie.getName(); if (userName != null) { entity.code = "0000"; entity.msg = "评论成功"; } } } return entity; } } 当用户登录之后由于本地已经保存了cookie,所以当调用评论接口的时候会检查cookie是否过期(过期则为null),如果过期则需要重新登录,我们把登录servlet中cookie过期的时间设置为10秒,登录之后10秒内调用评论接口 http://localhost:8080/OkHttpServer/UserCommentServlet 会告诉我们评论成功,但是如果超过十秒,那么就会返回用户未登录的提示

优秀的个人博客,低调大师

Java并发编程的艺术(五)——中断

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34173549/article/details/79612424 什么是中断? 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupted方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位;如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。 中断的相关方法 public void interrupt()将调用者线程的中断状态设为true。 public boolean isInterrupted()判断调用者线程的中断状态。 public static boolean interrupted只能通过Thread.interrupted()调用。它会做两步操作: 返回当前线程的中断状态; 将当前线程的中断状态设为false; 暂停、继续、停止线程(已过时) 以下三个方法都是通过线程对象去调用。 suspend()暂停调用者线程,只释放CPU执行权,不释放锁。由于在不释放资源的情况下进入睡眠状态,容易产生死锁。因此已过时! resume()恢复调用者线程,让他处于就绪状态。 stop()调用stop后,并不会保证资源被正确地释放,它会使程序处于不正确的状态下。 PS:stop和interrupt的区别? 中断的使用 要使用中断,首先需要在可能会发生中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理代码。当需要中断线程时,调用该线程对象的interrupt函数即可。 设置中断监听 Thread t1 = new Thread( new Runnable(){ public void run(){ // 若未发生中断,就正常执行任务 while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断的处理代码…… doSomething(); } } ).start(); 1 2 3 4 5 6 7 8 9 10 11 正常的任务代码被封装在while循环中,每次执行完一遍任务代码就检查一下中断状态;一旦发生中断,则跳过while循环,直接执行后面的中断处理代码。 触发中断 t1.interrupt(); 1 上述代码执行后会将t1对象的中断状态设为true,此时t1线程的正常任务代码执行完成后,进入下一次while循环前Thread.currentThread.isInterrupted()的结果为true,此时退出循环,执行循环后面的中断处理代码。 安全地停止线程 stop函数停止线程过于暴力,它会立即停止线程,不给任何资源释放的余地,下面介绍两种安全停止线程的方法。 循环标记变量 自定义一个共享的boolean类型变量,表示当前线程是否需要中断。 中断标识 volatile boolean interrupted = false; 1 任务执行函数 Thread t1 = new Thread( new Runnable(){ public void run(){ while(!interrupted){ // 正常任务代码…… } // 中断处理代码…… // 可以在这里进行资源的释放等操作…… } } ); 1 2 3 4 5 6 7 8 9 中断函数 Thread t2 = new Thread( new Runnable(){ public void run(){ interrupted = true; } } ); 1 2 3 4 5 循环中断状态 中断标识由线程对象提供,无需自己定义。 任务执行函数 Thread t1 = new Thread( new Runnable(){ public void run(){ while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断处理代码…… // 可以在这里进行资源的释放等操作…… } } ); 1 2 3 4 5 6 7 8 9 中断函数 t1.interrupt(); 1 总结 上述两种方法本质一样,都是通过循环查看一个共享标记为来判断线程是否需要中断,他们的区别在于:第一种方法的标识位是我们自己设定的,而第二种方法的标识位是Java提供的。除此之外,他们的实现方法是一样的。 上述两种方法之所以较为安全,是因为一条线程发出终止信号后,接收线程并不会立即停止,而是将本次循环的任务执行完,再跳出循环停止线程。此外,程序员又可以在跳出循环后添加额外的代码进行收尾工作。 处理中断 上文都在介绍如何获取中断状态,那么当我们捕获到中断状态后,究竟如何处理呢? Java类库中提供的一些可能会发生阻塞的方法都会抛InterruptedException异常,如:BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep。 当你在某一条线程中调用这些方法时,这个方法可能会被阻塞很长时间,你可以在别的线程中调用当前线程对象的interrupt方法触发这些函数抛出InterruptedException异常。 当一个函数抛出InterruptedException异常时,表示这个方法阻塞的时间太久了,别人不想等它执行结束了。 当你的捕获到一个InterruptedException异常后,亦可以处理它,或者向上抛出。 抛出时要注意???:当你捕获到InterruptedException异常后,当前线程的中断状态已经被修改为false(表示线程未被中断);此时你若能够处理中断,则不用理会该值;但如果你继续向上抛InterruptedException异常,你需要再次调用interrupt方法,将当前线程的中断状态设为true。 注意:绝对不能“吞掉中断”!即捕获了InterruptedException而不作任何处理。这样违背了中断机制的规则,别人想让你线程中断,然而你自己不处理,也不将中断请求告诉调用者,调用者一直以为没有中断请求。 QA 为什么catch InterruptedException后会自动清除中断状态?

优秀的个人博客,低调大师

java面试-Java并发编程(五)——中断

什么是中断? 在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的机制——中断。 中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupted方法,该方法也仅仅是将线程对象的中断标识设成true;接着你需要自己写代码不断地检测当前线程的标识位;如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。 每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断; 通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。 中断的相关方法 public void interrupt()将调用者线程的中断状态设为true。 public boolean isInterrupted()判断调用者线程的中断状态。 public static boolean interrupted只能通过Thread.interrupted()调用。它会做两步操作: 返回当前线程的中断状态; 将当前线程的中断状态设为false; 暂停、继续、停止线程(已过时) 以下三个方法都是通过线程对象去调用。 suspend()暂停调用者线程,只释放CPU执行权,不释放锁。由于在不释放资源的情况下进入睡眠状态,容易产生死锁。因此已过时! resume()恢复调用者线程,让他处于就绪状态。 stop()调用stop后,并不会保证资源被正确地释放,它会使程序处于不正确的状态下。 PS:stop和interrupt的区别? 中断的使用 要使用中断,首先需要在可能会发生中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理代码。当需要中断线程时,调用该线程对象的interrupt函数即可。 设置中断监听 Thread t1 = new Thread( new Runnable(){ public void run(){ // 若未发生中断,就正常执行任务 while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断的处理代码…… doSomething(); } } ).start(); 1 2 3 4 5 6 7 8 9 10 11 正常的任务代码被封装在while循环中,每次执行完一遍任务代码就检查一下中断状态;一旦发生中断,则跳过while循环,直接执行后面的中断处理代码。 触发中断 t1.interrupt(); 1 上述代码执行后会将t1对象的中断状态设为true,此时t1线程的正常任务代码执行完成后,进入下一次while循环前Thread.currentThread.isInterrupted()的结果为true,此时退出循环,执行循环后面的中断处理代码。 安全地停止线程 stop函数停止线程过于暴力,它会立即停止线程,不给任何资源释放的余地,下面介绍两种安全停止线程的方法。 循环标记变量 自定义一个共享的boolean类型变量,表示当前线程是否需要中断。 中断标识 volatile boolean interrupted = false; 1 任务执行函数 Thread t1 = new Thread( new Runnable(){ public void run(){ while(!interrupted){ // 正常任务代码…… } // 中断处理代码…… // 可以在这里进行资源的释放等操作…… } } ); 1 2 3 4 5 6 7 8 9 中断函数 Thread t2 = new Thread( new Runnable(){ public void run(){ interrupted = true; } } ); 1 2 3 4 5 循环中断状态 中断标识由线程对象提供,无需自己定义。 任务执行函数 Thread t1 = new Thread( new Runnable(){ public void run(){ while(!Thread.currentThread.isInterrupted()){ // 正常任务代码…… } // 中断处理代码…… // 可以在这里进行资源的释放等操作…… } } ); 1 2 3 4 5 6 7 8 9 中断函数 t1.interrupt(); 1 总结 上述两种方法本质一样,都是通过循环查看一个共享标记为来判断线程是否需要中断,他们的区别在于:第一种方法的标识位是我们自己设定的,而第二种方法的标识位是Java提供的。除此之外,他们的实现方法是一样的。 上述两种方法之所以较为安全,是因为一条线程发出终止信号后,接收线程并不会立即停止,而是将本次循环的任务执行完,再跳出循环停止线程。此外,程序员又可以在跳出循环后添加额外的代码进行收尾工作。 处理中断 上文都在介绍如何获取中断状态,那么当我们捕获到中断状态后,究竟如何处理呢? Java类库中提供的一些可能会发生阻塞的方法都会抛InterruptedException异常,如:BlockingQueue#put、BlockingQueue#take、Object#wait、Thread#sleep。 当你在某一条线程中调用这些方法时,这个方法可能会被阻塞很长时间,你可以在别的线程中调用当前线程对象的interrupt方法触发这些函数抛出InterruptedException异常。 当一个函数抛出InterruptedException异常时,表示这个方法阻塞的时间太久了,别人不想等它执行结束了。 当你的捕获到一个InterruptedException异常后,亦可以处理它,或者向上抛出。 抛出时要注意???:当你捕获到InterruptedException异常后,当前线程的中断状态已经被修改为false(表示线程未被中断);此时你若能够处理中断,则不用理会该值;但如果你继续向上抛InterruptedException异常,你需要再次调用interrupt方法,将当前线程的中断状态设为true。 注意:绝对不能“吞掉中断”!即捕获了InterruptedException而不作任何处理。这样违背了中断机制的规则,别人想让你线程中断,然而你自己不处理,也不将中断请求告诉调用者,调用者一直以为没有中断请求。 QA 为什么catch InterruptedException后会自动清除中断状态?

优秀的个人博客,低调大师

python函数式编程之迭代器

什么是迭代器 顾名思义,就是更新换代的意思 python中的迭代器就是根据上一个结果生成下一个结果,一直循环往复不断重复的过程 迭代器有两个特点: 1.不断重复同一个过程 2.根据上一个结果生成下一个结果 迭代器的定义 先来看两个例子 代码一: while True: cmd = input("input your command: ").strip() print(cmd) 代码二: l1 = ["a", "b", "c", "d"] count = 0 while count < len(l1): print(l1[count]) count += 1 在代码一中,让用户不断输入命令,然后打印用户输入的命令,这是一个不断重复的过程,但是后一个结果跟前一个结果没有什么联系,所以代码一不是迭代器 在代码二中,先定义一个列表l1和count,在count的值小于列表l1的长度中,打印列表中count索引的值,然后count的值加1,这几行代码符合迭代器的定义 代码二的执行过程: 首先,打印列表l1中count索引的值,这是一个不断重复的过程 然后,每循环一次,count的值就在以前的基础上增加1,直到count的值等于列表l1的长度时,循环中止 所以代码一不是迭代器,代码二才是符后迭代器的定义标准 迭代器的作用 python中常见的数据类型包括字符串,列表,元组,字典,集合,文件 我们知道,python中的数据类型又分为有序和无序 对于有序数据类型,可以如上面的代码二所示,使用索引遍历有序数据类型中的元素 而对于无序数据类型,就不可以使用索引来遍历其中的元素了,此时可以使用for循环来遍历其中的元素 现在来看看for循环到底做了哪些事,可以取出无序数据类型中的元素 dic1 = {"a": 1, "b": 2, "c": 3, "d": 4, } for k in dic1: print(k) 执行上面的程序,可以打印出字典dic1中所有的键 实际上,python为了提供一种不依赖于索引的迭代方式,python会为一些对象内置__iter__方法 在python中,如果一个对象有__iter__方法,那么这个对象就是可迭代对象 执行obj.__iter__()方法得到的结果就是迭代器 所以python中的迭代器,都会有__iter__方法和__next__方法 以上面定义的dic1为例 dic1 = {"a": 1, "b": 2, "c": 3, "d": 4, } i = dic1.__iter__() # dic1.__iter__方法执行结果得到一个迭代器i print(i) # 打印迭代器,得到结果:<dict_keyiterator object at 0x0000000002DBD278> print(i.__next__()) # 执行迭代器i中的__next__方法,得到dic1中的一个元素,然后打印迭代出的元素 print(i.__next__()) # 再次执行迭代器的__next__方法,得到dic1的第二个元素,然后打印出来 print(i.__next__()) # 第三次执行迭代器的__next__方法,得到dic1的第三个元素,然后打印出来 由于python的字典是无序的数据类型,所以每次得到的dic1中的元素可能是不相同的 执行三次__next__方法,得到的结果如下 a b c 上面dic1的长度为4,如果执行5次__next__方法,会出错什么结果 dic1 = {"a": 1, "b": 2, "c": 3, "d": 4, } i = dic1.__iter__() print(i.__next__()) print(i.__next__()) print(i.__next__()) print(i.__next__()) print(i.__next__()) 程序执行结果 a File "D:\python3\lib\unittest\loader.py", line 153, in loadTestsFromName b module = __import__(module_name) c File "E:\python_learn\py_code\test.py", line 21, in <module> d print(i.__next__()) StopIteration 可以看到,python解释器在执行第五次__next__方法时,抛出了StopIteration的错误,这说明如果执行__next__方法的次数超出迭代器的长度时,python解释会抛出异常 迭代器的优缺点 迭代器的优点 1.提供一种不依赖于索引的取值方式 2.提供惰性计算,每执行一次__next__方法,只会从迭代器中取出一个值,不管这个数据长度有多大,使用迭代器取值时,每取出一个值,占用的内存空间都一个元素的大小,非常节省内存空间 迭代器的缺点 1.取值比较麻烦。如果想用迭代器的方式取出列表中的第三个值,必须调用三次__next__方法,才能取出第三个值,而前两次取出的值实际上是没有作用的 2.一次性的,只能向后迭代,不能回退。如果运行两次__next__方法后,就不可能回退再去了迭代器的第一个值 3.不能获取到迭代器的长度,只能不停执行__next__方法,直到出现StopIteration的提示,此时才能知道迭代器的长度 for循环的执行过程 定义一个列表,使用for循环遍历取出列表中的值 l1 = ['a', 'b', 'c', 'd'] for i in l1: print(i) 在for循环的执行过程中,实际上for循环是把in后面的对象执行__iter__方法,得到一个迭代器对象,然后不停执行迭代器对象中的__next__方法,得到元素,然后把迭代出来的元素赋值给i,再调用print方法打印元素i,直到for循环捕捉到StopIteration异常,结束循环,程序停止 类似于下面的代码 l1 = ['a', 'b', 'c', 'd'] obj = l1.__iter__() while True: try: i = obj.__next__() print(i) except StopIteration: break 判断一个对象是迭代器对象还是可迭代对象 只要一个对象有__iter__方法,这个对象就是可迭代对象 一个可迭代对象有__next__方法,这个对象就是迭代器对象 判断python中的字符串,列表,元组,字典和文件对象是否是可迭代对象和迭代器对象 示例代码 from collections import Iterable, Iterator s1 = "hello" l1 = ['a', 'b', 'c', 'd'] t1 = ('a', 'b', 'c', 'd') set1 = {'a', 'b', 'c', 'd'} dic1 = {"a": 1, "b": 2, "c": 3, "d": 4, } f1 = open("db.txt", "w", encoding="utf-8") print(isinstance(s1, Iterable)) print(isinstance(l1, Iterable)) print(isinstance(t1, Iterable)) print(isinstance(set1, Iterable)) print(isinstance(dic1, Iterable)) print(isinstance(f1, Iterable)) print("*" * 30) print(isinstance(s1, Iterator)) print(isinstance(l1, Iterator)) print(isinstance(t1, Iterator)) print(isinstance(set1, Iterator)) print(isinstance(dic1, Iterator)) print(isinstance(f1, Iterator)) 执行结果: True True True True True True ****************************** False False False False False True 从上面的例子,可以看出,字符串,列表,元组,集合,字典和文件都是可迭代对象,其中只有文件是迭代器对象 在上面,我们知道,使用for遍历对象中的值时,是把in后面的对象的变为可迭代对象,然后调用__next__方法取出对象中的元素 既然文件本身就是迭代器对象,那使用for循环获取文件的内容时,又是怎么做的呢 先打开一个文件,打印出这个文件对象,再调用文件对象的__iter__方法得到迭代器对象,判断两个对象是否是同一个对象 f1 = open("db.txt", encoding="utf-8") print(f1) obj = f1.__iter__() print(obj) print(obj is f1) 执行结果 <_io.TextIOWrapper name='db.txt' mode='r' encoding='utf-8'> <_io.TextIOWrapper name='db.txt' mode='r' encoding='utf-8'> True 得到结论: 如果一个对象可以被for循环迭代,则这个对象一定是可迭代对象 文件对象被执行__iter__方法,得到的结果仍然是一个迭代器对象

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册