线程是什么
线程为什么有用
怎么开始编写使用线程的简单程序
如何在线程之间交换数据
如何控制线程以及线程如何相互通信
没有多线程时,完成毫不相关的任务需要顺序完成;
有多线程时,可以增加资源利用率;
我在写信的同时可能打发我的儿子去邮局买邮票。用软件术语来说,这称为多个控制(或执行)线程。
可以用两种不同的方式获得多个控制线程
当一个程序启动时,它可以为即将开始的每项任务创建一个进程,并允许它们同时运行。当一个程序因等待网络访问或用户输入而被阻塞时,另一个程序还可以运行,这样就增加了资源利用率。
代价是设置一个进程要占用相当一部分处理器时间和内存资源;
线程运行协作和数据交换
一个事务被另一个事务覆盖。这种情况将是灾难性的。但是,Java 编程语言提供了一种简单的机制来防止发生这种覆盖。每个对象在运行时都有一个关联的锁。这个锁可通过为方法添加关键字 synchronized 来获得。这样,修订过的 Account 对象(如下所示)将不会遭受像数据损坏这样的错误:
synchronized 同步
作用:当一个函数运行时,另一个函数就被阻塞
读函数不用进行同步处理
线程组:
管理线程,当有大量线程时
线程间发信
Object 类为此提供了三个函数:wait()、notify() 和 notifyAll()。
在每个循环中各个线程都必须等待所有线程完成各自的任务以后才能进入下一个循环。这个模型称为 屏蔽同步,
当对一个线程调用 wait() 时,该线程就被有效阻塞,只到另一个线程对同一个对象调用 notify() 或 notifyAll() 为止。
不同的线程在完成它们的工作以后将调用 waitForAll() 函数,最后一个线程将触发 notifyAll() 函数,该函数将释放所有的线程。
第三个函数 notify() 只通知一个正在等待的线程,当对每次只能由一个线程使用的资源进行访问限制时,这个函数很有用。但是,不可能预知哪个线程会获得这个通知,因为这取决于 Java 虚拟机 (JVM) 调度算法。
守护线程
有两类线程:用户线程和守护线程
用户线程是那些wan’c
什么时候使用多线程?
当应用程序必须等待缓慢的资源(如网络连接或数据库连接)时,或者当应用程序是非交互式的时,多线程通常是有利的
编写多线程的java应用程序
和进程不同的是,线程可以共享地址空间,也就是说,多线程能够读写相同的变量或数据结构。
操作系统可以将线程从处理器移到准备就绪队列或阻塞队列中,这种情况可以认为是处理器 挂起了该线程。
JVM也可以控制线程的移动
协作式线程模型中,
协作式线程 模型允许线程自己决定什么时候放弃处理器来等待其他的线程。程序开发员可以精确地决定某个线程何时会被其他线程挂起,允许它们与对方有效地合作。缺点在于某些恶意或是写得不好的线程会消耗所有可获得的 CPU 时间,导致其他线程“饥饿”。
在 抢占式线程 模型中,操作系统可以在任何时候打断线程。通常会在它运行了一段时间(就是所谓的一个时间片)后才打断它。
抢占式线程模型要求线程正确共享资源,协作式模型却要求线程共享执行时间。
由于 JVM 规范并没有特别规定线程模型,Java 开发员必须编写可在两种模型上正确运行的程序。
线程和java语言
为了使用java语言创建线程,生成一个Thread类(或其子类)的对象,并给这个对象发送start()消息。
线程的动作定义在包含该线程对象的run()方法中;
run方法就相当于传统程序中的main方法
线程会持续运行,直到run()返回为止,此时该线程便死了。
上锁:
java中最简单实现同步的方法。
为了防止同时访问共享资源,线程在使用资源的前后可以给该资源上锁和开锁;
在 Java 编程中,所有的对象都有锁。线程可以使用 synchronized 关键字来获得锁。
任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。
Fine-grain 锁
对象级使用锁是一种非常粗糙的方式;
因为如果一个对象拥有多个资源,就不需要只为了让一个线程使用其中一部分资源,将所有线程都锁在外面;
由于每个对象都有锁,可以如下所示使用虚拟对象来上锁:
对应的是方法级上同步;
若为了在方法级上同步,不能将整个方法声明为 synchronized 关键字。它们使用的是成员锁,而不是 synchronized 方法能够获得的对象级锁。
信号量
多个线程需要访问数目很少的资源;
这些线程需要连接到同一数据库;
但任一时刻只能获得一定数目的数据库连接;
问题:怎么样才能有效地将这些固定数目的数据库连接 分配给大量的线程?
一种控制访问一组资源的方法(除了简单上锁之外),就是使用众所周知的信号量计数;
信号量计数:
将一组可获得资源的管理封装起来。
信号量是在简单上锁的基础上实现的,相当于能令线程安全执行,并初始化为可用资源个数的计数器。
例如我们可以将一个信号量初始化为可获得的数据库连接个数
。一旦某个线程获得了信号量,可获得的数据库连接数减一。
线程消耗完资源并释放该资源时,计数器就会加一。
当信号量控制的所有资源都已被占用时,若有线程试图访问此信号量,则会进入阻塞状态,直到有可用资源被释放。
信号量最常见的用法是解决“消费者-生产者问题”
消费者线程只能在生产者线程完成生产后才能够访问数据。
常见的上锁问题:
使用上锁会带来问题;
死锁
死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成
例子:
假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。假如线程 “A” 获得了刀,而线程 “B” 获得了叉。线程 A 就会进入阻塞状态来等待获得叉,而线程 B 则阻塞来等待 A 所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。
按照下面几条规则去设计系统,就能够避免死锁问题:
让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。
按照顺序获得一组锁;
2、将多个锁
解决死锁问题的策略:
将多个锁组成一组并放到同一个锁下。前面死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁
将那些不会阻塞的可获得资源用变量标志出来。
当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。
volatile 关键字是 Java 语言为优化编译器设计的
用 volatile 关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。
无法访问的线程:
有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。
在java编程中IO就是这类问题最好的例子。
当线程因为对象内的IO调用而阻塞时,此对象应当仍能被其他线程访问。
该对象有责任取消这个阻塞的IO操作。
造成阻塞调用的线程常常会令同步任务失败;
如果对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了;
其他的线程由于不能获得对象的锁,就不能给此对象发消息(例如,取消IO操作)。
必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。
尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。
为不同的线程模型进行设计
判断是抢占式
Java 开发员必须编写那些能够在两种模型上工作的程序。
抢占式线程模型:
使用锁来正确同步共享资源的访问,就足以保证一个多线程程序在抢占式模型下正确工作。
协作式线程模型:
调用 yield() 方法能够将当前的线程从处理器中移出到准备就绪队列中;
另一个方法则是调用 sleep() 方法,使线程放弃处理器,并且在 sleep 方法中指定的时间间隔内睡眠。
如果线程正拥有一个锁(因为它在一个同步方法或代码块中),则当它调用yield() 时不能够释放这个锁。
这就意味着即使这个线程已经被挂起,等待这个锁释放的其他线程依然不能继续运行。
为了缓解这个问题,最好不在同步方法中调用yield方法。
将那些需要同步的代码包在一个同步块中,里面不含有非同步的方法,并且在这些同步代码块之外才调用 yield
同步信息:被立即处理,直到信息处理完成才返回消息句柄;
异步信息:异步信息收到后将在后台处理一段时间,而早在信息处理结束前就返回消息句柄)
Java 编程语言中的 Toolkit.getImage() 方法就是异步信息的一个好例子。 getImage() 的消息句柄将被立即返回,而不必等到整个图像被后台线程取回。
Java 的线程模型是非面向对象的。
一个java编程语言线程实际上只是一个 run()过程,它调用了其它的过程