首页 > 代码库 > Java多线程的理解

Java多线程的理解

一个线程创建之后,总是处于其生命周期的4个状态之一中。线程的状态表明此线程当前正在进行的活动,而线程的状态是可以通过程序来进行控制的,就是说,可以对线程进行操作来改变状态。这些操作包括启动(start)、终止(stop)、睡眠(sleep)、挂起 (suspend)、恢复(resume)、等待(wait)和通知(notify)。每一个操作都对应了一个方法,这些方法是由软件包java.lang提供的。 
    ①创建(new)状态 
    如果创建了一个线程而没有启动它,那么,此线程就处于创建状态。比如,下述语句执行以后,使系统有了一个处于创建状态的线程myThread: 
        Thread myThread= new MyThreadClass(); 
其中,MyThreadClass()是Thread的子类,而Thread是由Java系统的java.lang软件包提供的。 处于创建状态的线程还没有获得应有的资源,所以,这是一个空的线程。线程只有通过启动(start)后,系统才会为它分配资源。
    ②可运行(runnable)状态 
    如果对一个处于创建状态的线程进行启动操作,则此线程便进入可运行状态。仍以前面创建的myThread线程为例,用下列语句: myThread.start(); 则线程myThread进入可运行状态。上述语句实质上是调用了线程体即run()方法。 
    线程处于可运行状态只说明它具备了运行条件,但可运行状态并不一定是运行状态。因为在单处理器系统中运行多线程程序,实际上在一个时间点只有一个线程在运行,而系统中往往有多个线程同时处于可运行状态。系统通过快速切换和调度使所有可运行线程共享处理器,造成宏观上的多线程并发运行。 
    可见,一个线程是否处于运行状态,除了必须处于可运行状态外,还取决于系统的调度。 
    在可运行状态可以进行多种操作,最通常的是从run()方法正常退出而使线程结束,进入消亡状态。此外,还可以有如下操作: 
    挂起操作,通过调用suspend方法来实现; 
    睡眠操作,通过调用sleep方法来实现; 
    等待操作,通过调用wait方法来实现; 
    退让操作,通过调用yield方法来实现; 
    终止操作,通过调用stop方法来实现。 
    前面三种操作都会使一个处于可运行状态的线程进入不可运行状态。比如,仍以 myThread线程为例,当其处于可运行状态后,再用如下语句: 
        myThreadsleep(5000); 
则调用sleep方法使myThread线程睡眠5秒(5000毫秒)。这5秒内,此线程不能被系统调度运行,只有过5秒后,myThread线程才会醒来并自动回到可运行状态。 
    如果一个线程被执行挂起操作而转到不可运行状态,则必须通过调用恢复(resume)操作,才能使这个线程再回到可运行状态。 
    退让操作是使某个线程把CPU控制权提前转交给同级优先权的其他线程。 
    对可运行状态的线程也可以通过调用stop方法使其进入消亡状态。 
    ③不可运行(not runnable)状态 
    不可运行状态都是由可运行状态转变来的。一个处于可运行状态的线程,如果遇到挂起 (suspend)操作、睡眠(sleep)操作或者等待(wait)操作,就会进入不可运行状态。另外,如果一个线程是和I/O操作有关的,那么,在执行I/O指令时,由于外设速度远远低于处理器速度而使线程受到阻塞,从而进入不可运行状态,只有外设完成输入/输出之后,该线程才会自动回到可运行状态。线程进入不可运行状态后,还可以再回到可运行状态。通常有三种途径使其恢复到可运行状态。 
    一是自动恢复。 
    通过睡眠(sleep)操作而进入不可运行状态的线程会在过了指定睡眠时间以后自动恢复到可运行状态;由于I/O阻塞而进入不可运行状态的线程在外设完成I/O操作后,自动恢复到可运行状态。 
    二是用恢复(resume)方法使其恢复。 
    如果一个线程由于挂起(suspend)操作而从可运行状态进入不可运行状态,那么,必须用恢复(resume)操作使其再恢复到可运行状态。 
    三是用通知(notify或notiyA11)方法使其恢复。 
    如果一个处于可运行状态的线程由于等待(wait)操作面转入不可运行状态,那么,必须通过调用notify方法或notifyAll方法才能使其恢复到可运行状态。采用等待操作往往是由于线程需要等待某个条件变量,当获得此条件变量后,便可由notify或notifyAll方法使线程恢复到可运行状态。 
    恢复到可运行状态的每一种途径都是有针对性的,不能交叉。比如,对由于阻塞而进入不可运行状态的线程采用恢复操作将是无效的。 
    在不可运行状态,也可由终止(stop)操作使其进入消亡状态。 
    ④消亡(dead)状态 
    一个线程可以由其他任何一个状态通过终止(stop)操作而进入消亡状态。线程一旦进入消亡状态,那它就不再存在,所以也不可能再转到其他状态。 
    通常,在一个应用程序运行时,如果通过其他外部命令终止当前应用程序,那么就会调用stop方法终止线程。但是,最正常、最常见的途径是由于线程在可运行状态正常完成自身的任务而“寿终正寝”,从而进入消亡状态,这个完成任务的动作是由run方法实现的




一.多线程概念


1.进程与线程
一个程序启动, 至少会有一个进程, 一个进程中至少有一个线程.
2.创建线程
a).写一个类继承Thread, 重写run方法(), 在子类的run()方法中写线程要做的事. 创建此类对象, 调用start()方法.
这时程序会开启一条新的线程, 在新的线程上运行run()方法.
b).写一个类实现Runnable接口, 创建Thread类对象时在构造函数中传入Runnable对象, 在调用start()方法时, 就会调用Runnable的run()方法.
3.线程交替
在多线程并发的时候, CPU同一时间内只能为一条线程工作, 多条线程共同竞争CPU资源.
如果多个线程中做的事都是不停的占用CPU, 那么多线程并发并不能提高效率.
如果多个线程中做的事都是做一会停一会, 那么多线程并发会提高效率. 这样会提高CPU的使用率.

注:我们写的程序 通常只写一个MAIN函数 程序运行的时候 虚拟机会执行MAIN函数,不会执行其他的东西,MAIN函数里面的代码是从上到下,从左到右有顺序的执行的,所以这种模式基本上就是单线程。为了能运行多个线程,我们可以在MAIN函数下开启新的线程,这个时候MAIN函数下有多少新开的线程就会运行多少个新开的线程的RUN方法。各个线程之间没有关联,各自运行各自的,各个线程同时抢CPU,谁抢到就执行谁,为了让各个线程有序的工作,不抢,我们可以用SLEEP方法让当前线程休眠指定的时间(以毫秒为单位),在此线程休眠的时间内,此线程会把CPU让出去,但是此方法不一定会成功,有的程序很猛,不让SLEEP,因此SLEEP方法会抛异常。线程的切换是以方法为单位的。当一个线程里的方法执行完毕以后CPU就会提供给所有的线程再次抢的机会,因此如果一个线程里面写有多个方法,很容易并没有在这个线程的所有方法都执行结束的情况下别的线程又抢到了CPU。为了解决这个问题我们引用了锁的概念。把要执行的方法或者代码全都放在一个同步代码块里面。这样这个线程抢到CPU以后就可以安心的执行自己的代码了,只有当这个同步代码块里面的代码执行结束以后,CPU才会再次让其他的线程来抢。因此我们可以用这种办法保证一个线程里面的方法都执行完毕,然后才让别的线程执行,这样就减少了安全问题。
我们可以通过setDaemon(true)方法将线程设置为后台(守护)线程,必须在线程开启之前设置。如果一个程序的线程剩下的都是后台线程,那么该程序将自动结束。一个程序的MAIN函数里面只要有一个非后台(守护)线程在运行,程序就不会结束。
二.常用API
1.sleep
控制当前线程休眠, 实参单位为毫秒
Thread.sleep(1000); // 休眠1000毫秒
2.setDaemon()
设置当前线程为守护线程, 程序中的守护线程不会单独运行, 如果程序中所有非守护线程都执行结束, 那么程序直接退出.
注意要在线程开启直线设置.
3.join() 哪个线程要合并到另外一个线程就在另外的线程里面写哪个线程.JOIN();
此时,等待另一条线程运行结束, 加入进去的线程会继续运行.
4.currentThread()
获取主线程
5.getName(), setName()
获取, 设置线程的名字
  


三.线程的同步
1.线程安全问题
多线程并发操作同一数据时, 有可能出现线程安全问题.
2.同步代码块
使用关键字 synchronized(){} 来定义一个同步代码块.
多条线程中如果遇到同步代码块而且使用相同的锁旗标时, 只能有1条线程进入, 其他等待, 直到这条线程将同步代码块中的代码执行完毕, 其他线程才能进入.
3.同步方法
使用 synchronized 关键字修饰一个方法, 那么整个方法中的代码都是同步的
同步方法使用的锁旗标是this
4.死锁
在多个同步代码块中互相调用时, 容易产生死锁.
在多线程同步的时候, 如果需要使用多个锁, 尽量避免嵌套使用, 不要产生死锁. 

四.线程的通信
1.等待
锁旗标.wait()方法可以控制当前线程等待, 任何对象都有这个方法, 因为该方法是声明在Object类中的.
wait()之后, 当前线程会让出cpu资源, 直到被其他线程唤醒时继续.
2.唤醒
锁旗标.notify()方法可以唤醒在制定锁旗标上等待的随机一个线程, 这个方法也是声明在Object类中的.
notify()是唤醒随机一个
notifyAll()唤醒所有的

五.JDK5的线程处理
1.同步
使用ReentrantLock的lock()方法开始同步, unlock()方法结束同步.
2.通信
使用Condition类的await()和signal()来等待和唤醒, Condition类对象可以通过Lock类的newCondition()获取.

线程总结
1.启动线程
一般使用Runnable形式, 创建Thread类对象, 在构造函数中传入一个Runnable接口的子类, 那么当调用Thread对象的start()方法时, 就会调用Runnable的run()
2.线程的同步
一般使用同步代码块 synchronized(){} 注意多个需要同步的线程必须使用同一个锁旗标
如果一个方法中所有代码都需要同步, 那么可以使用同步方, 同步方法使用的锁旗标是this

public class ThreadTest {


* 程序只要有一个非后台(守护)的线程在运行,就不会结束。

public static void main(String[] args) {
		
		run();
		new MyThread().start(); 
		MyThread mt = new MyThread();
		mt.setName("mythread");
		//mt.setDaemon(true);	//将线程置为后台线程
		mt.start();
		int num = 0;
		while(true) {
			if(num++>20)
				try{ mt.join(); }catch(Exception e){e.printStackTrace();}
				break;
		System.out.println("main() in " + Thread.currentThread().getName());
	try{ Thread.sleep(100); } catch(InterruptedException e){e.printStackTrace();}
		}
	}
	
	static void run() {
		while(true) {
			System.out.println("run()");
		}
	}
}

class MyThread extends Thread {
	public void run() {
		while(true) {
			System.out.println("run() in " + Thread.currentThread().getName());
			try{ Thread.sleep(100); } catch(InterruptedException e){e.printStackTrace();}
		}
	}
}
---------------------------------------------------------------------------------
		
	public class TicketsSale1 {

	/**
	 * 多线程售票,同时开启四个线程售票
	 * 需要同步的多段代码要用同一把锁
	 * 死锁:
	 */
	public static void main(String[] args) {
		/*四个线程运行四个售票程序
		new SaleThread().start();
		new SaleThread().start();
		new SaleThread().start();
		new SaleThread().start();*/
		SaleThread1 st = new SaleThread1();
		new Thread(st).start();
		new Thread(st).start();
		try{ Thread.sleep(10); }catch(Exception e){ e.printStackTrace();}
		st.lock = "method";
		new Thread(st).start();
		new Thread(st).start();
		
	}

}

class SaleThread1 implements Runnable //extends Thread 
{
	int tickets = 100;
	String lock = "";
	public void run() {
		if(lock.equals("method")) {
			while(true) {
				sale();
			}
		}
		else {
			while(true) {
				synchronized(lock) {	//每个对象都可以作为同步的锁,对象都有一个标志位(0、1),锁旗标
					sale();
					if(tickets>0) {
						try{ Thread.sleep(100); }catch(Exception e){ e.printStackTrace();}
						System.out.println(Thread.currentThread().getName() + "  : is sale " + tickets--);
					}
				}
			}
		}
	}
	
	//同步函数,同步函数用的锁:this  (st)
	public synchronized void sale() {
		synchronized(lock) {
			
		}
		if(tickets>0) {
			try{ Thread.sleep(100); }catch(Exception e){ e.printStackTrace();}
			System.out.println(Thread.currentThread().getName() + " : sale() is sale " + tickets--);
		}
	}
}
====================================================================================
class SQL {
	String name;
	String sex;
	boolean change = false;
}

class DBA implements Runnable {
	private SQL sql;
	public DBA(SQL sql) {
		this.sql = sql;
	}
	public void run() {
		int num = 0;
		while(true) {
			synchronized(sql) {
				if(sql.change)
					try{ sql.wait(); } catch(Exception e){e.printStackTrace();}
				if(num==0) {
					sql.name = "hukai";
					sql.sex = "male";
				} else {
					sql.name = "shaoyuejiao";
					sql.sex = "female";
				}
				//try { Thread.sleep(10);} catch(Exception e){ e.printStackTrace(); }
				num = (num+1)%2;
				sql.change = true;
				sql.notify();
			}
		}
	}
}

class Coder implements Runnable {
	private SQL sql;
	public Coder(SQL sql) {
		this.sql = sql;
	}
	public void run() {
		while(true) {
			synchronized(sql) {
				if(!sql.change)
					try{ sql.wait();} catch(Exception e){ e.printStackTrace(); }
				System.out.print("name=" + sql.name + " ; ");
				//try { Thread.sleep(10);} catch(Exception e){ e.printStackTrace(); }
				System.out.println("sex=" + sql.sex);
				sql.change = false;
				sql.notify();
			}
		}
	}
}

public class SqlThread {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		SQL sql = new SQL();
		DBA dba = new DBA(sql);
		Coder coder = new Coder(sql);
		
		new Thread(dba).start();
		new Thread(coder).start();
	}

}


--------------------------------------------------------------------------------
//存满了,两个存的线程等待,一个取的线程取了一个元素会唤醒一个存的线程
//存的线程存了一个,又存满了,然而此时它会唤醒,就会唤醒另一个在等待的存的线程,出错了
//解决这个问题很简单,将线程等待的条件放在一个循环里面去判断
//而实际上wait方法允许发声虚假唤醒,所以最好放在一个循环里面
//但是像上面的做法,存的线程会去唤醒存的线程,没有必要,非常影响程序的效率

/*class MyArray {
	private int[] arr = new int[10];
	private String lock = "";
	private int savePos = 0;
	private int getPos = 0;
	private int count = 0;
	public void add(int num) {
		synchronized(lock) {
			while(count==10)
				try{ lock.wait(); } catch(Exception e) {e.printStackTrace();}
			if(savePos==10)
				savePos = 0;
			arr[savePos++] = num;
			count++;
			lock.notify();
			//try{Thread.sleep(10);} catch(Exception e) { e.printStackTrace(); }
		}
	}
	
	public int get() {
		synchronized(lock) {
			try {
				while(count==0)
					try{ lock.wait(); } catch(Exception e) {e.printStackTrace();}
				if(getPos==10)
					getPos = 0;
				//try{Thread.sleep(10);} catch(Exception e) { e.printStackTrace(); }
				count--;
				
				return arr[getPos++];
			} finally {
				lock.notify();
			}
		}
	}
}*/

//使用1.5的lock和condition解决存和取之间的通信问题
class MyArray {
	private int[] arr = new int[10];
	private int savePos = 0;
	private int getPos = 0;
	private int count = 0;
	private Lock lock = new ReentrantLock();
	private Condition isFull = lock.newCondition();
	private Condition isEmpty = lock.newCondition();
	public void add(int num) throws InterruptedException {
		try {
			lock.lock();
			while(count==10)
				isFull.await();
			if(savePos==10)
				savePos = 0;
			arr[savePos++] = num;
			count++;
			isEmpty.signal();
		} finally {
			lock.unlock();
		}
		//try{Thread.sleep(10);} catch(Exception e) { e.printStackTrace(); }
		
	}
	
	public int get() throws InterruptedException {
		try {
			lock.lock();
			while(count==0)
				isEmpty.await();
			if(getPos==10)
				getPos = 0;
			//try{Thread.sleep(10);} catch(Exception e) { e.printStackTrace(); }
			count--;
			isFull.signal();
			return arr[getPos++];
		} finally {
			
			lock.unlock();
		}
	}
}

public class ArrayThread {

	/**
	 * 写一个多线程的程序,实现两个线程存元素,两个线程取元素
	 */
	static int num = 0;
	public static void main(String[] args) {
		final MyArray marr = new MyArray();
		
		new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<30; i++) {
					try {
						marr.add(num++);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}).start();
		

		new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<30; i++)
					try {
						System.out.println(marr.get());
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
		}).start();
		
		
		new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<30; i++) {
					try {
						marr.add(num++);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			public void run() {
				for(int i=0; i<30; i++)
					try {
						System.out.println(marr.get());
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
			}
		}).start();
		
	}

}


Java多线程的理解