java 多线程
程序:指令和数据的集合,通常,我们用程序表示在磁盘或其他存储设备中的可执行文件。
进程:进程表示的是程序的执行状态。在单道操作系统中,一次只能有一个程序就执行,他可以获得所有的内存和CPU资源,但是在多道操作系统中,允许同时又多个程序在执行,操作系统为了对MEM 和CPU资源进行管理,以进程为单位来分配资源。
线程:在一个进程中,我们可能希望它能够同时提供多方面的服务。比如,一边响应用户的操作,一边进行后来的数据处理或者网络通信。这时,就需要多个执行流程同时运行。每一个执行的流程就叫做一个线程。线程也叫做程序的执行路径。
java 中 线程的使用方式
- 定义一个继承自Thread 的类,然后重写run方法, 用它创建对象然后调用start() 方法即可创建一个线程。
- 定义一个实现Runnable 的类,重写run()方法,然后用它的对象作为Thread 构造函数的参数,创建新的Thread对象 , 调用start方法创建新线程。
示例代码:
// 通过Runnable 接口创建线程。
class NewThread implements Runnable {
@Override
public void run(){
try{
for (int i = 5; i> 0; i--){
System.out.println("in the "+ Thread.currentThread().getName() + " Thread:" + i);
Thread.sleep(50);
}
}catch(InterruptedException e){
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread.");
}
}
使用:
NewThread nt = new NewThread();
Thread t1 = new Thread(nt);
t1.start();
//通过Thread 类创建。
class NewThread1 extends Thread{
public NewThread1(String name) {
super(name);
//System.out.println("Thread NO." + name );
start();
}
public void run(){
try {
for (int i = 5; i > 0; i--) {
System.out.println("Child Thread:" + Thread.currentThread().getName() + i);
Thread.sleep(50);
}
} catch (Exception e) {
System.out.println("Child interrupted.");
}
System.out.println("Exiting child thread." + Thread.currentThread().getName());
}
}
使用:
NewThread1 nt1 = new NewThread1();
nt1.start();
线程的状态
就绪 –> 运行 –> 终止
运行 –> 阻塞 –> 解除阻塞 –> 就绪
线程可以处于下列状态之一:
NEW:至今尚未启动的线程处于这种状态。
RUNNABLE:正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED:受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING:无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING:等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED:已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
java Thread 类常用方法
public final void setName(String name);
设置当前线程的名字
public static Thread currentThread();
返回当前线程的引用
public final String getName();
获取当前线程的名字。
isAlive(): 从就绪到终止之间的状态
getPriority(); 获取优先级
setPriority(); 设置优先级
Thread.sleep(); 睡眠,暂停
join(); a.join(),将调用join方法的线程暂停,转到a线程执行,当a线程终止后才会继续执行调用者线程。线程串行化
yield():让出cpu,当前线程进入就绪队列,等待调度
wait(); 当前线程进入对象的wait pool;
notify()/ notifyAll 唤醒对象的wait pool中的一个、所有等待线程。进入就绪状态。
线程控制
优先级:线程调度器监控程序中启动后进入就绪状态的所有线程。线程调度器参考线程优先级把线程假如到线程队列中。
优先级用1–10 表示,默认为5,1最低
主线程会等待其他线程都结束后再结束。
- a.join()\ a.join(timeout)\a.join(timeout,nanos) 等待a线程终止,也就是当前线程等待,a线程进入就绪状态,直到a线程执行完退出后,当前线程才会执行。参数timeout表示的是等待的时间,无参 或 timeout=0 表示的是一直等待。
- sleep:执行sleep 时,当前线程会进入休眠状态,即timed_waiting 状态。
- yield:暂停当前正在执行的线程,并执行其他线程。
- setDaemon:设置一个线程为守护线程,如果正在运行的线程都是守护线程,虚拟机将推出。 需要在start之前调用。
线程的同步
java 中线程的同步相对比较简单, 统一采用synchronized来进行同步。
基本思想,对于共享资源的操作放在同步块中,同步块用同一个监视器对象来锁定,如果某一个同步块正在执行,执行其他的同步块的线程就进入等待状态。每个线程只有在占有监视器对象时,才可以执行同步块中的代码, 一个监视器对象在某一时刻只能被一个线程占用。
用法:
- synchronized(监视器对象) { 同步块 } 一个对象可以用多个监视器对象。
- synchronized 普通成员方法,以当前对象为监视器对象,也就是一个对象用一个监视器对象。
- synchronized 静态成员方法,以Class 对象为监视器对象,也就是这个类的所有对象共用一个监视器对象。
线程的等待与唤醒
等待:wait(timeout)
此方法导致当前线程将自身引用放在对象的等待集中,然后放弃此对象上的所有同步要求。wait 之后线程将处于休眠状态,只能用下面三种方法解除休眠:
* 其他某个线程调用此对象的notify方法,并且休眠线程被唤醒
* 其他某个线程调用此对象的notifyAll方法。
* 其他某个线程中断wait的线程
* 到达指定的时间后自动恢复。
线程唤醒后,从对象的等待集中删除线程T,并重新进行线程调度。然后,该线程以常规方式与其他线程竞争,以获得在该对象上同步的权利。
如果wait(0)时,线程会一直等待,直到被唤醒, 如果wait(1) ,线程在1ms后自动唤醒。
另外,线程wait 时,还有可能发生虚假唤醒(spurious wakeup),所以等待总是发生在循环中,
synchronized(obj){
while (condition){
obj.wait(timeout);
}
由于wait方法将当前线程放入了某个对象的等待集中,所以它只能解除此对象的锁定(注意代码的obj监视器),如果当前线程因为其他监视器对象而处于锁定状态时,并不会因为wait方法而使得其他同步块解除锁定。
此方法只能由作为此对象监视器的所有者的线程来调用。
wait() == wait(0)
wait(timeout, nanos) === wait(timeout * 10 ^6 + nanos)
唤醒 notify
唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会任意选择唤醒其中一个线程。
此方法只能由作为此对象监视器的对象的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
- 通过执行此对象的同步实例方法。
- 通过执行在此对象上进行同步的synchronized语句块
- 对于Class类型的对象,可以通过执行该类的同步静态方法
一次只能有一个线程拥有对象的监视器。
一个经典的生产者和消费者的例子:
// TestPCThread.java
class SyncStack {
private char[] data; // 数据首地址
private int count; // 当前数据的个数
private final int capa; // 数据容量
SyncStack(){
capa = 6;
data = new char[capa];
count = 0;
}
SyncStack(int capa){
this.capa = capa;
data = new char[capa];
count = 0;
}
//同步的压栈方法
public synchronized void push(char ch){ // 以当前对象为监视器对象。 如果方法中操作比较简单,可以直接把整个方法设为synchronized,否则,应该只把关键的代码段设为synchronized。
data[count++] = ch;
}
// 同步的出栈方法
public synchronized char pop(){
char ch;
ch = data[--count];
return ch;
}
public synchronized int getCount() {
return count;
}
public synchronized int getCapa() {
return capa;
}
}
class Productor implements Runnable{
private SyncStack ssStack;
public Productor(SyncStack ss) {
ssStack = ss;
}
@Override
public void run() {
for(int i = 0; i< 26; i++){
synchronized (ssStack){ // 统一以栈对象为监视器对象
while(ssStack.getCount() == ssStack.getCapa()){
try {
ssStack.wait(); // 栈满时,wait
} catch (InterruptedException e) {
e.printStackTrace();
}
}
ssStack.push((char)('a' + i));
System.out.println("生产元素:" + (char)('a' + i));
ssStack.notify(); // 生产之后notify 其他线程
}
}
}
}
class Consumer implements Runnable{
private SyncStack ssStack;
public Consumer(SyncStack ss){
ssStack = ss;
}
@Override
public void run() {
for(int i=0;i< 26; i++){
synchronized (ssStack) { // wait应该在一个同步的代码块中。
while(ssStack.getCount() == 0){ // while循环是为了防止发生虚假唤醒
try {
ssStack.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
char ch = ssStack.pop();
System.out.println("消费元素:" + ch);
ssStack.notify(); // 消费之后唤醒其他线程。
}
}
}
}
public class TestPCThread {
public static void main(String[] args) {
SyncStack ssStack = new SyncStack();
Productor productor = new Productor(ssStack);
Consumer consumer = new Consumer(ssStack);
Thread t1 = new Thread(productor);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
关于上面代码中把特别的数据结构及操作封装为独立的类,需要说明的是这是一种常用的操作,通过把数据结构对象作为属性,降低数据、数据操作、逻辑操作的耦合性,设计的层次更加鲜明。