提纲:
一.开篇废话
二.再看多线程
1)线程的状态(这一部分是总结前面所学的线程基础知识)
2)线程的同步问题(这一部分是最近的一些收获)
一.开篇废话
Where又回来了,貌似有快一个学期没写技术博客了,虽然期间做过一些总结但也没发上博客,又到了假期可以安下心好好写点东西了,废话不多说,希望自己在寒假能安心下来好好学点东西,写点东西,那么让我们从线程开始。
二.再看线程
1.线程的几个状态
一个线程可以有四种状态:
(1) 新(New):线程对象已经创建,但尚未启动,所以不可运行。
问题一:如何新建线程?
答:让一个类成为线程类的方式有两种:一个是实现java.lang.Runnable接口,另一个是继承java.lang.Thread类。
范例代码如下:
public class TestThread extends Thread { //继承Thread类的线程类 public void run(){ System.out.println("线程开始运行"); do... } } public class TestThread1 implements Runnable{ //实现Runable接口的线程类 public void run(){ System.out.println("线程开始运行"); do... } }
问题二:比较这两种方式的异同?
答:
相同点:两者都需要根据具体要求实现run()方法。
区别:(1)线程类继承自Thread类则不能继承其他类,而Runnable可以。(因为JAVA的类只能继承自一个类,却可以实现多个接口,这个在之前的博客有提到)。
(2)线程类继承Thread类相对于Runnable来说,使用线程的方法更方便些。(因为Thread类提供了很多关于线程的方法,比如:获取线程名,线程状态等)。
(3) 实现Runable接口的线程类的多个线程,可以更方便的访问同一变量可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的
情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。,而Thread类则需要内部类替换。
如果发现第三点不好理解那就看一下我下面的例子:
现有火车票10张(1-10号),现有X和Y两个代售点要把这十张票卖出我们用Thread和Runnable来试验一下这个过程。
package 比较多线程实现方法; /** * 用Runnable的方式模拟场景 * @author where * */ public class TestRunnable implements Runnable{ private static int MAX=10;//共有MAX张票 public void run() { while(MAX>0){ System.out.println("-"+Thread.currentThread().getName()+"-卖掉第 : "+MAX--+" 张"); } } public static void main(String[] args) { TestRunnable tt0=new TestRunnable(); new Thread(tt0,"X").start(); TestRunnable tt1=new TestRunnable(); new Thread(tt1,"Y").start(); System.out.println("-------"); } }
运行结果:
-X-卖掉第 : 9 张
-X-卖掉第 : 8 张
-X-卖掉第 : 7 张
-Y-卖掉第 : 10 张
-Y-卖掉第 : 5 张
-X-卖掉第 : 6 张
-Y-卖掉第 : 4 张
-X-卖掉第 : 3 张
-Y-卖掉第 : 2 张
-X-卖掉第 : 1 张
可已看出用这种方式的结果是对的,那么我们再来看看另一种。
package 比较多线程实现方法; /** * 用Thread方式来模拟场景 * @author where * */ public class TestThread extends Thread{ private static int MAX=10;//共有MAX张票 public void run() { while(MAX>0){ System.out.println("-"+Thread.currentThread().getName()+"-卖掉第 : "+MAX--+" 张"); } } public static void main(String[] args) { TestRunnable tt0=new TestRunnable(); new Thread(tt0,"X").start(); TestRunnable tt1=new TestRunnable(); new Thread(tt1,"Y").start(); System.out.println("-------"); } }
运行结果(这只是结果的一种情况):
-Y-卖掉第 : 10 张
-X-卖掉第 : 10 张
-Y-卖掉第 : 9 张
-Y-卖掉第 : 7 张
-X-卖掉第 : 8 张
-Y-卖掉第 : 6 张
-Y-卖掉第 : 4 张
-X-卖掉第 : 5 张
- X-卖掉第 : 2 张
-X-卖掉第 : 1 张
-Y-卖掉第 : 3 张
这次就有问题了,Y售票站已经将10号票售出为什么X又买出了一张10号票,这就是上面我们讲的那个问题。貌似到这里你该明白第三点了吧。
(2) 可运行(Runnable ):意味着一旦时间分片机制有空闲的CPU 周期提供给一个线程,那个线程便可立即
开始运行。因此,线程可能在、也可能不在运行当中,但一旦条件许可,没有什么能阻止它的运行——它既
没有“死”掉,也未被“堵塞”。
(3) 死(Dead):从自己的run()方法中返回后,一个线程便已“死”掉。亦可调用stop()令其死掉,但会
产生一个违例——属于Error 的一个子类(也就是说,我们通常不捕获它)。记住一个违例的“掷”出应当
是一个特殊事件,而不是正常程序运行的一部分。所以不建议你使用stop()(在Java 1.2 则是坚决反
对)。另外还有一个destroy()方法(它永远不会实现),应该尽可能地避免调用它,因为它非常武断,根
本不会解除对象的锁定。
(4) 堵塞(Blocked):线程可以运行,但有某种东西阻碍了它。若线程处于堵塞状态,调度机制可以简单地
跳过它,不给它分配任何CPU 时间。除非线程再次进入“可运行”状态,否则不会采取任何操作。
问题三:为何会堵塞?
答:堵塞状态是前述四种状态中最有趣的,值得我们作进一步的探讨。线程被堵塞可能是由下述五方面的原因造成的:
1.调用sleep(毫秒数),使线程进入“睡眠”状态。在规定的时间内,这个线程是不会运行的。
2. 用suspend()暂停了线程的执行。除非线程收到resume()消息,否则不会返回“可运行”状态。
3. 用wait()暂停了线程的执行。除非线程收到nofify()或者notifyAll()消息,否则不会变成“可运行”
(是的,这看起来同原因2 非常相象,但有一个明显的区别是我们马上要揭示的)。
4. 线程正在等候一些IO(输入输出)操作完成。
5. 线程试图调用另一个对象的“同步”方法,但那个对象处于锁定状态,暂时无法使用。
2.线程的同步问题(这一部分是最近的一些收获)
多线程一旦操作同一内存的数据就容易造成数据的混乱,也就是常说的线程安全问题。针对这个问题我们一般有四种解决方式:
- 采用synchronized关键字锁定要同步的方法
- 采用synchronized关键字锁定要同步的代码块
- 采用lock锁对象锁定同步代码,注意这里的所对象必须要为同一个否则加锁没意义。
- 采用共享的代码操作为原子操作
-
在这一部分我用生产者消费者模型为例说明:场景模拟:有一个最大存货量为5的仓库,生产者往里放,消费者取,当放满了则生产者wait,当取空了则消费者wait.
package 生产消费者模型; //用wait/notify public class Store { public static final int MAXSIZE = 5;//表示仓库里的最大存货量 int n;//表示仓库里当时的存货量 public Store(int n){ this.n= n; } /** * 放入货物的方法 */ public synchronized void addThing(){ while(n>=MAXSIZE){ try { System.out.println(Thread.currentThread().getName()+"当前仓库已经存放到最大限度,需等待才能在放"); //notifyAll(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } n++; System.out.println(Thread.currentThread().getName()+"当前仓库放入了"+n+"个或物"); notify(); } /** * 移除货物的方法 */ public synchronized void removeThing(){ while(n<=0){ try { System.out.println(Thread.currentThread().getName()+"当前仓库已没有货物要取需等待"); //notifyAll(); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } n--; System.out.println(Thread.currentThread().getName()+"仓库被取走货物后剩下的货物为:"+n+"个"); notify(); } }
package 生产消费者模型; public class Customer extends Thread{ Store s; public Customer(Store s){ this.s = s; } public void run(){ while(true){ s.removeThing(); try { Thread.sleep(1500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package 生产消费者模型; public class Producer extends Thread{ private Store s; public Producer(Store s){ this.s = s; } public void run(){ while(true){ s.addThing(); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }
package 生产消费者模型; public class Test { /** * 测试入口 */ public static void main(String[] args) { Store s = new Store(0); Thread pro1 = new Producer(s); Thread pro2 = new Producer(s); //Thread pro3 = new Producer(s); Thread cus1 = new Customer(s); Thread cus2 = new Customer(s); Thread cus3 = new Customer(s); pro1.start(); pro2.start(); //pro3.start(); cus1.start(); cus2.start(); cus3.start(); } }
运行结果(由于是while(true)所以只选取了部分结果来说明问题):
Thread-0当前仓库放入了1个或物
Thread-2仓库被取走货物后剩下的货物为:0个
Thread-3当前仓库已没有货物要取需等待
Thread-1当前仓库放入了1个或物
Thread-3仓库被取走货物后剩下的货物为:0个
Thread-4当前仓库已没有货物要取需等待
Thread-0当前仓库放入了1个或物
Thread-4仓库被取走货物后剩下的货物为:0个
Thread-1当前仓库放入了1个或物
Thread-0当前仓库放入了2个或物
Thread-1当前仓库放入了3个或物
Thread-0当前仓库放入了4个或物
Thread-2仓库被取走货物后剩下的货物为:3个
Thread-3仓库被取走货物后剩下的货物为:2个
Thread-1当前仓库放入了3个或物
Thread-0当前仓库放入了4个或物
Thread-4仓库被取走货物后剩下的货物为:3个
Thread-1当前仓库放入了4个或物
Thread-0当前仓库放入了5个或物
Thread-1当前仓库已经存放到最大限度,需等待才能在放
Thread-0当前仓库已经存放到最大限度,需等待才能在放
Thread-2仓库被取走货物后剩下的货物为:4个
Thread-1当前仓库放入了5个或物
Thread-0当前仓库已经存放到最大限度,需等待才能在放
Thread-3仓库被取走货物后剩下的货物为:4个
Thread-0当前仓库放入了5个或物
...
结果分析:
(1)线程并不是按它们创建时的顺序运行的
(2)当执行到后面,有多个wait(),那么notify该激活哪个wait或者说它有什么调度机制?根据结果(标红的部分)我们可以做此大胆猜想:这里有一个wait队列,先进先出,即notify相应最先进队列的wait.
相关推荐
这份"java-java面试题库整理-基础-JVM-线程并发-框架等.zip"文件提供了一个全面的复习资源,帮助求职者准备Java相关的面试。 1. **Java基础知识** - 类与对象:Java是一种面向对象的语言,了解类的定义、构造器、...
Java开发案例-springboot-61-整合asyncTool京东多线程编排工具-源代码+文档.rar Java开发案例-springboot-61-整合asyncTool京东多线程编排工具-源代码+文档.rar Java开发案例-springboot-61-整合asyncTool京东多线程...
以上只是Java基础知识的一部分,实际的Java基础教程会更深入地讲解每个概念,并配有实例来帮助理解。这份"java基础教程----精华版"应该涵盖了这些主题,并且可能还有更多的实践指导和示例代码,对于学习和巩固Java...
基于java的开发源码-超简单Java多线程填表源码.zip 基于java的开发源码-超简单Java多线程填表源码.zip 基于java的开发源码-超简单Java多线程填表源码.zip 基于java的开发源码-超简单Java多线程填表源码.zip 基于java...
javaweb毕业设计-Java多线程与线程安全实践-基于Http协议的断点续传(可做课程设计).rarjavaweb毕业设计-Java多线程与线程安全实践-基于Http协议的断点续传(可做课程设计).rarjavaweb毕业设计-Java多线程与线程安全...
Java基础[01-Java概述].pdf Java基础[02-Java基础语法1].pdf Java基础[02-Java基础语法2].pdf Java基础[03-面向对象].pdf Java基础[04-继承上].pdf Java基础[04-继承下].pdf Java基础[05-多线程].pdf Java基础[06-...
Java线程:概念与原理 Java线程:创建与启动 Java线程:线程栈模型与线程的变量 Java线程:线程状态的转换 Java线程:线程的同步与锁 Java线程:线程的交互 Java线程:线程的调度-休眠 Java线程:线程的调度-...
基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多线程与线程安全实践编程.zip 基于Http协议的断点续传-Java多...
多线程相关知识源码-----多线程案例源码
Java线程亲和性(Thread Affinity)是一个高级并发编程概念,主要涉及到操作系统调度和硬件资源的优化。在多核处理器系统中,线程亲和性允许开发者指定某个线程应该运行在哪个特定的处理器核心上,从而提高性能、...
Java技术基础知识是编程领域中的重要组成部分,尤其对于初学者来说,掌握这些基础知识是成为Java开发者的必经之路。本课件旨在系统性地介绍Java语言...深入学习并掌握这些知识点,将为你的Java编程之旅打下坚实的基础。
Java多线程是Java编程中的核心...以上就是关于Java多线程的主要知识点,理解并熟练掌握这些概念对于编写高效的并发程序至关重要。在实际开发中,应根据需求选择合适的线程模型和同步机制,以保证程序的正确性和性能。
在Java编程领域,线程是并发处理的核心概念,它允许程序在同一时间...理解并熟练掌握这些知识点,对于解决Java并发问题和优化多线程应用至关重要。在实际工作中,还需要关注线程安全、性能优化和代码可维护性等问题。
总的来说,Java Socket的单线程阻塞模式是一个基础且重要的概念,它涉及网络通信的基本原理,以及如何在Java中实现这一模式。通过学习和实践,开发者能够构建出简单的客户端-服务器应用,并为进一步学习多线程、异步...
Java 第二阶段提升编程能力【线程(基础)】---- 代码 Java 第二阶段提升编程能力【线程(基础)】---- 代码 Java 第二阶段提升编程能力【线程(基础)】---- 代码 Java 第二阶段提升编程能力【线程(基础)】---- ...
多线程注意:wait()方法的调用要有判定条件常用 while () obj.wait(timeout, nanos); ... // Perform action appropriate to condition } synchronized会影响共享数据,但对其他语句的执行不会有规律了!
java线程的状态3---马克-to-win java视频的详细描述与介绍
计算机后端-Java-Java核心基础-第20章 多线程 15. 线程通信的例题.avi
计算机后端-Java-Java核心基础-第20章 多线程 04. 线程的生命周期.avi