1 线程
多任务:操作系统完成多任务是多线程存在的原因。
线程:进程中单一顺序的执行流。多个线程共享相同地址空间并构成进程。
注意:这里可以参考操作系统一起学习,进程与线程的比较,在面试或者笔试时也经常会出。
多线程:一个程序同时具备完成多个任务的能力。多线程指一个程序包中含有多个执行流,是实现并发的有效手段。
2 线程的状态及转换
- 新建:new Thread()之后的线程状态
- 就绪:在新建之后调用线程的start()方法之后启动线程之后的状态
- 运行:再就绪之后调用run()方法让线程出于运行状态
- 阻塞:处于运行状态的线程调用sleep()/wait(),suspend()/yield()等方法之后线程处于阻塞状态
- 死亡:run()方法运行结束
注意:线程放弃CPU的占有权有一下几种方式:
- 优先级较低时遇到优先级较高的线程
- 线程调用yield()/sleep()
- 线程调用suspend()挂起
- 线程调用wait()等待
- 输入输出流发生线程阻塞
重点在于掌握线程的就绪,运行河阻塞三种状态的转换,转换的实现(及实现状态转换的方法)
线程优先级:Thread.getPriority()可以获取线程的优先级,setProirity()可以设置线程优先级,默认为5.
3 线程的实现
- 继承Thread类
- 实现Runnable接口
继承Thread类创建一个线程类
public class CountingThread extends Thread{
public void run(){
System.out.println();
System.out.println("子线程"+this+"开始");
for(int i=0;i<8;i++){
System.out.println(this.getName()+".i"+(i+1)+"/t");
}
System.out.println();
System.out.println("子线程"+this+"结束");
}
}
在测试类中新建三个线程对象
public class ThreadTest {
public void mian(String[] args){
System.out.println("主线程开始");
CountingThread th1=new CountingThread();
th1.start();
CountingThread th2=new CountingThread();
th2.start();
CountingThread th3=new CountingThread();
th3.start();
System.out.println("主线程结束");
}
}
这里有点也许是别人不会说到的,就是在控制台里编和运行可能都直接用javac,java指令即可,但是我用的是myeclipse,当写好线程类和测试类的时候,要进行调试和运行,却不知道应该如何操作,现在问了一些人也没找到答案,先放着,后面专门写一篇来介绍这个问题吧,不然妨碍进度。
以上程序每次运行的结果都不一样,而且运行的时候线程的切换也是无序的,体现线程的不可控性。
- 线程的暂停与恢复
暂停:sleep(),yield(),wait()比较:
sleep(st),线程在休眠st毫秒之后被唤醒;
yield(),线程让出CPU,不允许优先级更低的线程获得CPU;
wait(),线程进入等待状态,必须由notify()或notifyAll()唤醒;
线程使用的一些技巧
- 编写一个类继承Thread类
- 增加一个running变量并初始化为false
- 覆盖start()方法,先将running设置为true,然后调用super.start()
- 提供一个halt()方法将running设置为false
- 如下写run()方法
public void run(){ while(running){ ... } }
这样在一个线程调用halt方法的时候便可以结束线程,而不用调用stop()(已经建议不再使用的方法)。
课本上还实现了一个继承Thread类实现多线程的例子,一个笼子里有多只鸟的例子,不再介绍,可以认真的研读书本上的代码,学习框架和细节技巧。
同样书上也实现了一个实现Runnable接口实现多线程的例子,重点在于重写Runnable的run()方法,代码不多,但框架很完整,值得好好研究。
4 线程的同步
书本上给出了两个个在不做同步处理的情况下的例子,表现了非同步状态下数据如何崩溃的,这点是我们学习多线程必须知道的,但是研究数据崩溃不是我们的主要目的,我们的目的是学习如何写出不会造成数据错误和崩溃的多线程应用。即重点是学习如何做线程的同步。
线程同步的方法:
- synchronized 方法:即用synchronized修饰方法,则此方法在任何时候都只允许一个线程来调用,其他线程必须等待,直到这个线程将该方法执行结束。同样是只能控制任何时候只能允许一个线程执行,但在多线程时不能控制是哪个线程获得调用权。
- 管程(Monitor),也叫互斥锁。管程是一个能够拒绝访问和允许访问的对象。每个拥有synchronized方法的对象拥有唯一的一个互斥锁,放过来说,只有获得互斥锁的对象才能去调用synchronized方法。实现机制:一个调用管程入口的线程发现资源已经被分配时则调用wait()方法进入等待队列排队,而拥有资源的线程如果想放弃资源的使用权即释放资源需要调用notify()或者notifyAll()方法通知某个正在等待的线程或者是通知 所用正在等待的线程。
以上即使实现同步的方法,但是,这种同步很有可能会产生死锁,即导致所有的线程都处于等待状态,浪费CPU资源。所以在程序设计的时候要考虑是否会导致死锁的情况,即慎用wait()和notify().
课本上给出了发生死锁的例子,同样也给出了解决方法。不再细说,好好看代码和说明。