提纲:
一.开篇废话
二.再看多线程
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编程中,多线程是并发编程的重要组成部分,它允许程序同时执行多个任务,从而提高了系统的效率和响应性。...通过熟练掌握上述知识点,开发者可以更好地驾驭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线程:线程的调度-...
多线程相关知识源码-----多线程案例源码
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线程、线程池资料----下载不扣分,回帖加1分,欢迎下载,童叟无欺JAVA线程、线程池资料----下载不扣分,回帖加1分,欢迎下载,童叟无欺JAVA线程、线程池资料----下载不...
练习GUI编程和多线程,包括多线程的睡眠、唤醒,界面的简单编程,实现字幕滚动的功能
计算机后端-Java-Java核心基础-第20章 多线程 11. 线程安全的单例模式之懒汉式.avi