1. 线程与多线程的概念
线程(thread),又称轻量级进程(LightWeight Process,LWP),是进程的一个实体,是被系统独立调度和分派的基本单位(进程作为系统分配资源的基本单位)。多个线程可以共享数据空间和程序代码,每个线程有自己的执行堆栈和程序计数器为其执行上下文;多线程是为了节约CPU时间。
实现多线程的方式:多个硬件处理器同时支持多线程、单个硬件处理器以时间分片方式支持多线程、多个硬件处理器以时间分片方式支持多线程。
多线程的优点:提高交互式应用程序的响应性、线程的上下文切换较之进程的开销低、实现计算机任务之间的资源共享。
多线程的缺点:设计复杂存在同步、死锁、公平等问题;多线程应用程序的行为通常与特定的平台有关,可移植性降低。
2. 线程的状态及生命周期
java线程的状态切换详图
线程生命周期的五个状态:New(新建)、Runnable(就绪)、Running(运行)、Dead(终止、死亡)、Blocked(堵塞)
线程的创建方式有两种:实现Runnable接口或者继承Thread类 并实现其中的run()方法。
3. 线程中常用方法详解
- setPriority(intnew Priority)设置线程优先级(1-10),基数越大优先级越高、越先执行。JVM的线程调度机制会根据优先级大小决定哪个线程先执行,优先级高的线程会抢占低优先级线程的CPU时间片。注意:调度机制不会保证优先级高的线程就一定先执行,故不要将算法语义的正确性建立在线程优先级的基础上。
- join( ) 该方法做线程的串行处理。 如果一个线程执行过程中要用到另一个线程的运行结果、则可进行线程的串行处理。(调用该方法的线程将立即进入阻塞状态,直到加入的线程执行结束,当前线程才能进入到就绪状态,等待被调度执行)
- yield( ) 线程让步。 让运行中的线程主动放弃当前获得CPU的处理机会,从而转入就绪状态;让其他与该线程具有相同优先级的线程有机会运行。(主要用在:占用数据库连接资源的线程、占用计算机端口资源的线程。 尽快释放这些紧缺资源,以供其他任务使用)
- wait( )/notify( ) notifyAll( ) 这些方法只能用在synchronized机制中,否则会抛出异常。 这三个方法都是Object类中的方法,能在所有类的对象中使用。
- sleep( ) 使当前线程睡眠指定的时间,可以用interrupt( )方法终止。
4. 守护线程和同步、锁问题
守护线程也称后台线程,为其他线程提供服务。守护线程不作为应用线程的核心,所有非守护线程(用户线程)终止运行后,守护线程还可以继续运行一段时间。
isDaemon()方法判断一个线程是否为守护线程。
setDaemon(boolean) 方法讲一个线程设置为守护线程。
synchronized 能给方法和代码块加锁,给方法加锁只能锁定调用该方法的对象、给代码块加锁可以锁定任意指定的对象如:synchronized( 对象名){ 代码块 }。
注意:
- synchronized不能同步类和变量
- 线程睡眠时,它产生的任何锁都不会被释放
- synchronized同步损害并发性,应该尽可能缩小同步范围
- 注意静态方法的同步锁,锁定的是类对象
5. 经典问题解析(生产者、消费者问题)
package oracle.hl.threads.consumerandproducer2;
/**
*
* 生产者和消费者共享的资源栈
* 1、问题在于多个线程可以同时地访问共享资源,
* 并且某一时刻只允许有单个线程更新资源。
* @author admin
*
*/
public class SynStack {
private int index = 0;
private char [] data = new char[3];
private boolean available = false;
public synchronized void push(char c){
//当栈中资源已满,停止生产线程。
// 控制在同一时刻只能有单一线程向共享栈中写入数据;
while(index==data.length||available){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//如果栈中资源未满,向栈中添加资源,同时唤醒消费线程
available = true; //设置为true表示有线程正在访问共享资源
this.notifyAll();
data[index] = c;
index++;
System.out.println("Produced:"+c);
}
public synchronized char pop(){
//当栈中没有资源可以消费,则停止消费者线程。
//控制每一时刻只能有一个消费者对栈中资源消费(即:更改资源)
while(index == 0||!available){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//当共享栈中存在可消费资源,减少栈中资源,同时唤醒生产者线程。
available = false;
this.notifyAll();
index--;
System.out.println("consumer:"+data[index]);
return data[index];
}
}
//生产者
package oracle.hl.threads.consumerandproducer2;
public class Producer implements Runnable{
private SynStack stack;
private String producer;
public Producer(SynStack stack,String producer){
this.stack = stack;
this.producer = producer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
char c = (char)(Math.random()*26+'A');
stack.push(c);
System.out.println(producer+" 生产--->"+c);
try {
//休眠指定时间,测试时能够看清生产者消费者的距离逻辑
Thread.currentThread().sleep((int)(Math.random()*100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package oracle.hl.threads.consumerandproducer2;
//消费者
public class Consumer implements Runnable{
private SynStack stack;
private String consumer;
public Consumer(SynStack stack,String consumer){
this.stack = stack;
this.consumer = consumer;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
char c = stack.pop();
System.out.println(consumer+" 消费----> "+c);
try {
//设置休眠时间,能够清晰看出生产者消费者的具体逻辑
Thread.currentThread().sleep((int)(Math.random()*10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package oracle.hl.threads.consumerandproducer2;
//主测试函数
public class ProducerCousumerProblem {
public static void main(String args[]){
SynStack stack = new SynStack();
Runnable p = new Producer(stack,"p1");
Runnable c = new Consumer(stack,"c1");
Runnable c2 = new Consumer(stack,"c2");
Thread pThread = new Thread(p);
Thread cThread = new Thread(c);
Thread c2Thread = new Thread(c2);
pThread.start();
cThread.start();
c2Thread.start();
}
}
6. 总结
使用线程解决问题时主要得考虑:线程同步问题(死锁、互斥),要防止程序出现死锁现象。要准确地使用synchronized解决同步问题。