第十三章总结
一、进程和线程
1、程序:一段静态的代码
2、进程:程序的一次动态执行过程它对应从代码加载、执行到执行完毕的一个完整过程。
3、进程也称任务,支持多个进程同时执行的os就被称为多进程os或多个任务os。
4、在一个程序内部也可以实现多个任务并发执行,其中每个任务称为线程。
5、线程是比进程更小的执行单位,它是在一个进程中独立的控制流,即程序内部的控制流。
6、特点:线程不能独立运行,必须依赖与进程,在进程中运行。
7、每个程序至少有一个线程称为主流程。
8、单线程:只有一条线程的进程称为单线程
9、多线程:有不止一个线程的进程称为多线程
二、多线程的优势
1、提高界面的响应速度;
2、充分利用系统资源。
三、多线程的实现
1、继承Thread;
class MyThread extends Thread{
public void run(){
//线程体
}
}
注意:对于同一个线程类,也可以启动多个线程;
同一个线程不能启动两次。
2、实现Runable接口;
class MyThread2 implements Runable{
public void run(){} //重写Runable接口中的run()方法
}
3、使用Timer和TimerTask组合;
实例:
package com.hrxy;
public class ThreadDemo extends Thread{
public static void main(String[] args){
//创建对象
ThreadDemo t=new ThreadDemo();
//启动线程
t.start();
try {
for(int i=0;i<5;i++){
//延长一秒
Thread.sleep(1000);
System.out.println("Main:"+i);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void run(){
for(int i=0;i<5;i++){
try {
Thread.sleep(1000);
System.out.println("Run:"+i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//Runnable测试类
public class ThreadDemo{
public static void main(String[] args){
//创建对象
Runnable1 t=new Runnable1();
Thread t1=new Thread(t);
//启动线程t1
t1.start();
try {
for(int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println("Main"+i);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//使用实现Runnable接口的方式实现多线程
class Runnable1 implements Runnable{
public void run(){
try {
for(int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println("Run"+i);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四、线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程,这就是所谓的生命周期。一个线程在它的生命周期内有5种状态(有六种)
1、新建(new):
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
2.就绪(runable):
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。
例如:t1.start();
3、运行(running):
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
4、死亡(dead):
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
5、堵塞(blocked)
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,即进入堵塞状态。
正在睡眠:用sleep(long t) 方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
正在等待:调用wait()方法。(调用notify()方法回到就绪状态)
被另一个线程所阻塞:调用suspend()方法。(调用resume()方法恢复)
实例:电子时钟
package com.hbsi;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class ClockTest extends JFrame{
/**
* @param args
*/
JLabel clock;
public ClockTest(){
super("我的时钟");
clock=new JLabel("");
clock.setHorizontalAlignment(JLabel.CENTER);
add(clock);
setBounds(200,150,300,200);
setVisible(true);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
卖票:
package com.hbsi;
public class SaleTicket implements Runnable{
/**
* @param args
*/
private int ticket=100;
public static void main(String[] args) {
SaleTicket st=new SaleTicket();
Thread t1=new Thread(st);
Thread t2=new Thread(st);
t1.start();
t2.start();
}
@Override
public void run() {
Object obj=new Object();
while(true){
synchronized (obj) {
if(ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"....."+ticket--);
}
}
}
}
}
五、多线程问题及处理
多线编程为程序开发带来了很多的便利,但也带来了一些问题。这些问题是在开发过程中中必须进行处理的。
这些问题的核心是,如果多个线程同时访问一个资源,如变量、文字等,如何保证访问安全?在多线编程中,这种会被多个线程同时访问的资源较临界资源。
(一)为什么需要“线程同步”?
1、线程间共享代码和数据可以节省系统开销,提高程序运行效率,但同时也导致了数据的“访问冲突”问题,如何实现线程间的有机交互、并确保共享资源在某些关键时段只能被一个线程访问,即所谓的“线程同步”(Synchronization)就变得至关重要。
2、Synchronization关键字是一个修饰符,可以修饰方法或代码块。其作用是:对于同一个对象(不是一个类的不同对象),当多个线程都同时调用该方法或代码块,必须依次执行,也就是说,如果两个或两个以上的线程同时执行该代码,如果一个线程已经开始执行该段代码,则另一个线程必须等待这个线程执行完这段代码才能开始执行。
3、synchronized关键字的使用方式有两种:
(1)用在对象前面限制一段代码的执行(同步代码块)
public void push(char c){
…
sychronized(this){
data[index]=c;
index++
}
}
(2)用在方法声明中,表示整个方法为同步方法:
public sychronized void show(){}
实例:
package com.hbsi;
public class TestSaleTicket {
/**
* @param args
*/
public static void main(String[] args) {
Tickets t=new Tickets();
TicketThread t1=new TicketThread(t,"张三");
TicketThread t2=new TicketThread(t,"李四");
}
}
class Tickets{
public int ticket;
public Tickets(){
ticket=10;
}
public synchronized void action(String name){
System.out.println(name+"买到了第"+ticket+"号票");
ticket--;
}
}
class TicketThread extends Thread{
Tickets t;
String name;
public TicketThread(Tickets t,String name){
this.t=t;
this.name=name;
start();
}
public void run(){
for(int i=0;i<5;i++){
t.action(name);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
(二)死锁
两个线程A、B用到同一个对象s(s为共享资源),且线程A在执行中要用到B运行后所创造的条件。在这种前提下A先开始运行,进入同步块后,对象s被锁定,接着线程A因等待B运行结束而进入阻塞状态,于是B开始运行,但因无法访问对象s,线程B也进入阻塞状态,等待s被线程A解锁。最终的结果:两个线程互相等待,都无法运行。
(三)单例
如果一个类始终只能创建一个实例,则这个类被称为单例类。
分两种
1、懒汉式
class Single{
//使用一个变量来缓存创建的实例
private static Single s=null;
//将构造方法用private修饰,隐藏其构造方法
private Single(){}
//提供一个静态方法,用于返回Single实例
//该方法可以加入自定义的控制,保证只产生一个Single对象
public static Single getInstance(){
if(s==null)
synchronized(Singel.class){
if(s==null)
s=new Single();
}
return s;
}
class Single{
private static Single s=null;
private Single(){}
public static synchronized Single getInstance(){
if(s==null)
s=new Single();
return s;
}
Single.getInstance();
2、饿汉式
class Single{
private static Single s=new Single();
private Single(){}
public static Single getInstance(){
return s;
}
}
六、线程同步通信
为避免死锁,就应该让线程在进入阻塞状态时尽量释放其锁定的资源,以为其他的线程提供运行的机会,Object类中定义了几个有用的方法:wait()、notify()、notifyAll()。
1、wait():被锁定的对象可以调用wait()方法,这将导致当前线程被阻塞并释放该对象的互斥锁,即解除了wait()方法当前对象的锁定状态,其他的线程就有机会访问该对象。
2、notify():唤醒调用wait()方法后被阻塞的线程。每次运行该方法只能唤醒一个线程。
3、notifyAll():唤醒所有调用wait()方法被阻塞的线程。
实例:
package com.hbsi;
class Res1{
private String name;
private String sex;
private boolean flag;
public synchronized void set(String name,String sex){
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name=name;
this.sex=sex;
flag=true;
this.notify();
}
public synchronized void out(){
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name+"....."+sex);
flag=false;
this.notify();
}
}
class Input1 implements Runnable{
private Res1 r;
public Input1(Res1 r){
this.r=r;
}
public void run() {
int i=0;
while(true){
if(i==0){
r.set("张三","男");
}else{
r.set("lisi","nv");
}
i=(i+1)%2;
}
}
}
class Output1 implements Runnable{
private Res1 r;
public Output1(Res1 r){
this.r=r;
}
public void run() {
while(true){
r.out();
}
}
}
public class ThreadRes {
/**
* @param args
*/
public static void main(String[] args) {
Res1 r=new Res1();
Input1 in=new Input1(r);
Output1 out=new Output1(r);
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}