多线程与多进程
进程一般是对操作系统而言的;线程一般是对某程序而言的。
比如,打开word的同时打开outlook接收邮件,我们说这是两个进程;而打开outlook收取新邮件的同时查看已下载的邮件,我们就说这是两个进程。
线程的状态
Thread类
Java的线程是通过类Thread来实现的,生成一个Thread类的对象之后,就产生了一个线程;Thread类对象内部的run()方法会执行代码,被称为线程体。
新建—就绪—运行—阻塞—死亡
- 新建状态:使用new关键字创建一个Thread线程对象,此时线程已有自己的内存空间,但是并不是活着的。
- 就绪状态:调用start()方法
- 运行状态:执行run()方法
- 阻塞状态:调用join()、sleep()、wait()方法。
- 死亡状态
创建线程
一般有两种方法创建线程:
- 继承Thread方法
- 实现Runnable接口
class ThreadInterfaceImpl implements Runnable {
public void run() {
// 线程实现体
}
}
// 创建接口实现类的实例对象
Runnable target = new ThreadInterfaceImpl();
// 创建一个Thread类的对象,并将上步中创建的对象作为参数传递给Thread类的构造函数
Thread threadObject = new Thread(target);
/*
或者简写为:
Thread threadObject = new Thread(new ThreadInterfaceImpl())
*/
// 用start()方法启动线程
threadObject.start();
线程调度策略
Java采用抢占式策略,高优先级的线程抢占CPU。
void setPriority(int newPriority); // 重置线程优先级
int getPriority();
static void yield(); // 给其他同等优先级的线程一个运行机会
void sleep(); // 使线程休眠一段时间
void join(); // 等待某个线程结束后再开始执行另一个线程
线程的基本控制
join()方法
关于join()方法,它的使用举一个栗子:
设置三个线程,线程1用于生成一群随机数,线程2用于计算随机数之和,线程3用于输出这个和,要求线程1、线程2、线程3依次运行。
package hitwh.hitwh.myThread;
//线程三,输出结果
public class PrintTask implements Runnable{
Thread thread;
public PrintTask(Thread thread) {
this.thread = thread;
}
public void run() {
try {
// 让接收的线程对象执行完,本线程才运行
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sum="+JoinDemo.sum);
}
}
package hitwh.hitwh.myThread;
//线程1,用于产生随机数
public class Producer implements Runnable{
int[] arr;
public Producer(int []arr) {
this.arr = arr;
}
// 线程体
public void run() {
System.out.println("初始化...");
for(int i=0; i<arr.length;i++) {
arr[i]=(int)(Math.random()*100);
System.out.print(arr[i]+",");
if((i+1)%10==0)
System.out.println();
}
}
}
package hitwh.hitwh.myThread;
//线程2,计算线程一产生的随机数的和
public class Worker implements Runnable{
int[] arr;
Thread thread;
// 构造方法,接收一个数组和一个线程对象
public Worker(int[] arr, Thread thread) {
this.arr = arr;
this.thread = thread;
}
// 线程体
public void run() {
try {
// 让接收的线程执行完,本线程才会执行
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始计算...");
// 计算数组中所有元素之和
int sum = 0;
for(int i=0; i<arr.length; i++) {
sum+=arr[i];
}
JoinDemo.sum=sum;
}
}
package hitwh.hitwh.myThread;
public class JoinDemo {
static int sum = 0;
public static void main(String[] args) throws InterruptedException {
int[] arr = new int[10];
// 先启动线程一,生成一群随机数
Thread producer = new Thread(new Producer(arr));
producer.start();
// 后启动线程二,计算线程一生成的随机数之和
Thread worker = new Thread(new Worker(arr, producer));
worker.start();
// 最后启动线程三,输出结果
Thread printTask = new Thread(new PrintTask(worker));
printTask.start();
// 等待printTask线程对象执行完,程序才继续运行
printTask.join();
int sum1 = 0;
for(int i=0; i<arr.length; i++) {
sum1+=arr[i];
}
if(sum == sum1) {
System.out.println("验证通过!");
} else {
System.out.println("验证失败!"+" sum="+sum+", sum1="+sum1);
}
}
}
结束线程与中断线程
run()线程体运行结束时,该线程自然死亡;
stop()方法可以强制停止线程执行(不推荐使用)
使用interrupt()方法可以使线程中断执行。
一般地,在程序中调用interrupt()方法后,通常需要在run()方法中使用isInterrupt()进行判断,根据判断结果执行相应操作。
线程间的通信
设置三个线程类,通信的双方线程作为辅类线程,都将数据流连接到主类中的管道流中。
package hitwh.hitwh.commuThread;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.PipedInputStream;
public class MyReader1 extends Thread{
// 创建管道输入流
private PipedInputStream pis;
// 创建数据输入流
private DataInputStream dis;
// 接收数据
private String messages[];
public MyReader1(PipedInputStream pis) {
this.pis = pis;
}
public void run() {
// 将数据输入流与管道输入流联系起来
dis = new DataInputStream(pis);
try {
int size = dis.read();
messages = new String[size];
// 读数据
for(int i=0; i<messages.length; i++) {
messages[i] = dis.readUTF();
System.out.println("Read:"+messages[i]);
try {
sleep(400);
} catch (InterruptedException e) {}
}
} catch (IOException e) {}
}
}
package hitwh.hitwh.commuThread;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.PipedOutputStream;
//写线程
public class MyWriter1 extends Thread{
// 创建管道输出流
private PipedOutputStream pos;
// 创建数据输出流
private DataOutputStream dos;
private int size = 7;
// 要写的数据源
private String messages[] = {"Mon", "Tues", "Wednes", "Thur", "Fri", "Satur", "Sun"};
public MyWriter1(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
// 用 管道输出流 和 写线程的数据输出流 联系起来
dos = new DataOutputStream(pos);
try {
dos.write(size);
for(int i=0; i<messages.length; i++) {
dos.writeUTF(messages[i]);
System.out.println("Writte:"+messages[i]);
try {
sleep(300);
} catch (InterruptedException e) {}
}
dos.close();
} catch (IOException e) {}
}
}
package hitwh.hitwh.commuThread;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class PipeThread {
public static void main(String[] args) {
PipeThread thisPipe = new PipeThread();
thisPipe.process();
}
public void process() {
PipedInputStream inputStream;
PipedOutputStream outputStream;
try {
// 建立管道输入流与管道输出流之间的连接
outputStream = new PipedOutputStream();
inputStream = new PipedInputStream(outputStream);
// 读线程与写线程一起运行
new MyWriter1(outputStream).start();
new MyReader1(inputStream).start();
} catch (IOException e) {}
}
}
线程的同步
线程间的资源互斥
在多个线程共享同一资源的情况下,如果不加以任何控制,就有可能产生访问冲突和数据不一致。
为了解决这样的问题,就需要给多个线程共享的资源加锁。当一个线程访问共享资源时,就会被共享资源加锁,其他线程就不能访问被加了锁的资源,直到共享资源的锁被释放。这就是线程同步。
在Java中,用关键字synchronize来保证线程同步。当它作用于方法时,称方法同步;作用于语句块时,称为语句块同步。
线程交互
等待集合
每一个类的对象实例都有一个等待集合,当在实例上调用方法wait后,线程都会进入到该实例的等待集合中,除非发生下列情况:
- 其他线程调用了方法notify()或者notifyAll();
- 其他线程调用了方法interrupt()中断该线程
- 方法wait()的等待时间结束
- wait()
wait()方法是类Object中的方法。当线程调用了方法wait()后,当前线程就会进入休眠状态,并且释放对象同步锁的控制权。 - notify()
线程不能一直停留在等待集合中,notify可以从将某个对象的等待集合中选择一个等待的线程进行唤醒。 - notifyAll()
唤醒所有等待的线程。