整理总结Java多线程程序编写的要点
线程状态图
线程共包括以下5种状态。
1. 新建状态(new) : 线程对象被创建后,就进入了新建状态。例如,thread thread = new thread()。
2. 就绪状态(runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被cpu调度执行。
3. 运行状态(running) : 线程获取cpu权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(blocked) : 阻塞状态是线程因为某种原因放弃cpu使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了i/o请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者i/o处理完毕时,线程重新转入就绪状态。
5. 死亡状态(dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
实现多线程的方式thread和runnable
thread:继承thread类,实现run方法,在main函数中调用start方法启动线程
runnable:接口,实现runnable接口,作为参数传递给thread的构造函数,在main中调用start方法
例子:
class task implements runnable { private int ticket = 10; @override public void run() { for (int i = 0; i < 20; i++) { if (this.ticket > 0) { system.out.println(thread.currentthread().getname() + " sold ticket " + this.ticket--); } } } }; public class runnabletest { public static void main(string[]args){ task mytask = new task(); thread t1 = new thread(mytask); thread t2 = new thread(mytask); thread t3 = new thread(mytask); t1.start(); t2.start(); t3.start(); } } //threadtest.java 源码 class mythread extends thread { private int ticket = 10; public void run() { for (int i = 0; i < 20; i++) { if (this.ticket > 0) { system.out.println(this.getname() + " 卖票:ticket" + this.ticket--); } } } } public class threadtest { public static void main(string[] args) { // 启动3个线程t1,t2,t3;每个线程各卖10张票! mythread t1 = new mythread(); mythread t2 = new mythread(); mythread t3 = new mythread(); t1.start(); t2.start(); t3.start(); } };
thread与runnable的区别
thread 是类,而runnable是接口;thread本身是实现了runnable接口的类。我们知道“一个类只能有一个父类,但是却能实现多个接口”,因此runnable具有更好的扩展性。此外,runnable还可以用于“资源的共享”。即,多个线程都是基于某一个runnable对象建立的,它们会共享runnable对象上的资源。通常,建议通过“runnable”实现多线程!
thread的run与start
start() : 它的作用是启动一个新线程,新线程会执行相应的run()方法。start()不能被重复调用。start()实际上是通过本地方法start0()启动线程的。而start0()会新运行一个线程,新线程会调用run()方法。
run() : run()就和普通的成员方法一样,可以被重复调用。单独调用run()的话,会在当前线程中执行run(),而并不会启动新线程!run()就是直接调用thread线程的runnable成员的run()方法,并不会新建一个线程。
// demo.java 的源码 class mythread extends thread{ public mythread(string name) { super(name); } public void run(){ system.out.println(thread.currentthread().getname()+" is running"); } }; public class demo { public static void main(string[] args) { thread mythread=new mythread("mythread"); system.out.println(thread.currentthread().getname()+" call mythread.run()"); mythread.run(); system.out.println(thread.currentthread().getname()+" call mythread.start()"); mythread.start(); } }
输出:
main call mythread.run() main is running main call mythread.start() mythread is running
synchronized
在java中每个对象都有一个同步锁,当我们调用对象的synchronized方法就获取了对象锁,synchronized(obj)就获取了“obj这个对象”的同步锁.不同线程对同步锁的访问是互斥的.某时间点对象的同步锁只能被一个线程获取到.通过同步锁,我们就能在多线程中,实现对“对象/方法”的互斥访问. 例如,现在有两个线程a和线程b,它们都会访问“对象obj的同步锁”。假设,在某一时刻,线程a获取到“obj的同步锁”并在执行一些操作;而此时,线程b也企图获取“obj的同步锁” —— 线程b会获取失败,它必须等待,直到线程a释放了“该对象的同步锁”之后线程b才能获取到“obj的同步锁”从而才可以运行。
基本规则
第一条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的该“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
第二条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程仍然可以访问“该对象”的非同步代码块。
第三条: 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对“该对象”的其他的“synchronized方法”或者“synchronized代码块”的访问将被阻塞。
synchronized 方法
public synchronized void foo1() { system.out.println("synchronized methoed"); } synchronized代码块 public void foo2() { synchronized (this) { system.out.println("synchronized methoed"); } }
synchronized代码块中的this是指当前对象。也可以将this替换成其他对象,例如将this替换成obj,则foo2()在执行synchronized(obj)时就获取的是obj的同步锁。
synchronized代码块可以更精确的控制冲突限制访问区域,有时候表现更高效率
实例锁 和 全局锁
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
pulbic class something { public synchronized void issynca(){} public synchronized void issyncb(){} public static synchronized void csynca(){} public static synchronized void csyncb(){} }
(01) x.issynca()与x.issyncb() 不能被同时访问。因为issynca()和issyncb()都是访问同一个对象(对象x)的同步锁!
(02) x.issynca()与y.issynca() 可以同时被访问。因为访问的不是同一个对象的同步锁,x.issynca()访问的是x的同步锁,而y.issynca()访问的是y的同步锁。
(03) x.csynca()与y.csyncb()不能被同时访问。因为csynca()和csyncb()都是static类型,x.csynca()相当于something.issynca(),y.csyncb()相当于something.issyncb(),因此它们共用一个同步锁,不能被同时反问。
(04) x.issynca()与something.csynca() 可以被同时访问。因为issynca()是实例方法,x.issynca()使用的是对象x的锁;而csynca()是静态方法,something.csynca()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
线程阻塞与唤醒wait,notify,notifyall
在object.java中,定义了wait(), notify()和notifyall()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyall()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyall()是唤醒所有的线程。
object类中关于等待/唤醒的api详细信息如下:
notify() -- 唤醒在此对象监视器上等待的单个线程。
notifyall() -- 唤醒在此对象监视器上等待的所有线程。
wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyall() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyall() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyall() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
// waittest.java的源码 class threada extends thread{ public threada(string name) { super(name); } public void run() { synchronized (this) { system.out.println(thread.currentthread().getname()+" call notify()"); // 唤醒当前的wait线程 notify(); } } } public class waittest { public static void main(string[] args) { threada t1 = new threada("t1"); synchronized(t1) { try { // 启动“线程t1” system.out.println(thread.currentthread().getname()+" start t1"); t1.start(); // 主线程等待t1通过notify()唤醒。 system.out.println(thread.currentthread().getname()+" wait()"); t1.wait(); system.out.println(thread.currentthread().getname()+" continue"); } catch (interruptedexception e) { e.printstacktrace(); } } } }
输出
main start t1 main wait() t1 call notify() main continue
(01) 注意,图中"主线程" 代表“主线程main”。"线程t1" 代表waittest中启动的“线程t1”。 而“锁” 代表“t1这个对象的同步锁”。
(02) “主线程”通过 new threada("t1") 新建“线程t1”。随后通过synchronized(t1)获取“t1对象的同步锁”。然后调用t1.start()启动“线程t1”。
(03) “主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。等待t1对象上的线程通过notify() 或 notifyall()将其唤醒。
(04) “线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”;接着调用notify()唤醒“当前对象上的等待线程”,也就是唤醒“主线程”。
(05) “线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!
package thread.test; public class notifyalltest { private static object obj = new object(); public static void main(string[] args) { threada t1 = new threada("t1"); threada t2 = new threada("t2"); threada t3 = new threada("t3"); t1.start(); t2.start(); t3.start(); try { system.out.println(thread.currentthread().getname()+" sleep(3000)"); thread.sleep(3000); } catch (interruptedexception e) { e.printstacktrace(); } synchronized(obj) { system.out.println(thread.currentthread().getname()+" notifyall()"); obj.notifyall();//在此唤醒t1.t2.t3 } } static class threada extends thread{ public threada(string name){ super(name); } public void run() { synchronized (obj) { try { // 打印输出结果 system.out.println(thread.currentthread().getname() + " wait"); //释放obj对象锁 obj.wait(); // 打印输出结果 system.out.println(thread.currentthread().getname() + " continue"); } catch (interruptedexception e) { e.printstacktrace(); } } } } }
输出:
t1 wait main sleep(3000) t3 wait t2 wait main notifyall() t2 continue t3 continue t1 continue
(01) 主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
(02) 主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityall()来唤醒它;相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityall()来唤醒它们。
(03) 主线程休眠3秒之后,接着运行。执行 obj.notifyall() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了!
notify,notifyall与锁的关系
object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
ok,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyall()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyall()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在object类,而不是thread类中的原因。
线程让步yield
线程让步,使线程从执行状态变为就绪状态,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行。
yield 与wait
(01) wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而不yield()是让线程由“运行状态”进入到“就绪状态”。
(02) wait()是会线程释放它所持有对象的同步锁,而yield()方法不会释放锁。
(03) wait是object的方法,yield是thread的方法
线程休眠 sleep
sleep() 的作用是让当前线程休眠,即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待cpu的调度执行。
sleep与wait的区别
wait()的作用是让当前线程由“运行状态”进入“等待(阻塞)状态”的同时,也会释放同步锁。而sleep()的作用是也是让当前线程由“运行状态”进入到“休眠(阻塞)状态。(这个其实区别不大)
wait()会释放对象的同步锁,而sleep()则不会释放锁
wait是object的方法,sleep是thread的方法
join
让主线程等待,子线程运行完毕,主线程才能继续运行
interrupt
用来终止处于阻塞状态的线程
@override public void run() { try { while (true) { // 执行任务... } } catch (interruptedexception ie) { // 由于产生interruptedexception异常,退出while(true)循环,线程终止! } }
在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生interruptedexception中断。中断的捕获在while(true)之外,这样就退出了while(true)循环
终止处于运行状态的线程
@override public void run() { while (!isinterrupted()) { // 执行任务... } }
通用的终止线程的方式
@override public void run() { try { // 1. isinterrupted()保证,只要中断标记为true就终止线程。 while (!isinterrupted()) { // 执行任务... } } catch (interruptedexception ie) { // 2. interruptedexception异常保证,当interruptedexception异常产生时,线程被终止。 } }
线程优先级
java 中的线程优先级的范围是1~10,默认的优先级是5。“高优先级线程”会优先于“低优先级线程”执行。java 中有两种线程:用户线程和守护线程。可以通过isdaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:java虚拟机在“用户线程”都结束后会后退出。
每个线程都有一个优先级。“高优先级线程”会优先于“低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于“创建它的主线程的优先级”,当且仅当“创建它的主线程是守护线程”时“子线程才会是守护线程”。
当java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。jvm会一直运行直到下面的任意一个条件发生,jvm就会终止运行:
(01) 调用了exit()方法,并且exit()有权限被正常执行。
(02) 所有的“非守护线程”都死了(即jvm中仅仅只有“守护线程”)。
守护进程
(01) 主线程main是用户线程,它创建的子线程t1也是用户线程。
(02) t2是守护线程。在“主线程main”和“子线程t1”(它们都是用户线程)执行完毕,只剩t2这个守护线程的时候,jvm自动退出。
生产者消费者问题
(01) 生产者仅仅在仓储未满时候生产,仓满则停止生产。
(02) 消费者仅仅在仓储有产品时候才能消费,仓空则等待。
(03) 当消费者发现仓储没产品可消费时候会通知生产者生产。
(04) 生产者在生产出可消费产品时候,应该通知等待的消费者去消费。
线程之间的通信
方式:共享内存和消息传递
共享内存:线程a和线程b共享内存,线程a更新共享变量的值,刷新到主内存中,线程b去主内存中读取线程a更新后的变量。整个通信过程必须通过主内存。同步是显式进行的。
如果一个变量是volatile类型,则对该变量的读写就将具有原子性。如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。
volatile变量自身具有下列特性:
[可见性]:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
[原子性]:对任意单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性。
volatile写:当写一个volatile变量时,jmm会把该线程对应的本地内存中的共享变量刷新到主内存。
volatile读:当读一个volatile变量时,jmm会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
消息传递:消息的发送在消息的接受之前,同步隐式进行。
threadlocal
threadlocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过threadlocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的.threadlocal使得各线程能够保持各自独立的一个对象,并不是通过threadlocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过threadlocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行threadlocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,threadlocal实例是作为map的key来使用的。 如果threadlocal.set()进去的东西本来就是多个线程共享的同一个对象,那么多个线程的threadlocal.get()取得的还是这个共享对象本身,还是有并发访问问题。
threadlocal的一个实现
import java.util.collections; import java.util.hashmap; import java.util.map; /** * 使用了threadlocal的类 * * @author leizhimin 2010-1-5 10:35:27 */ public class mythreadlocal { //定义了一个threadlocal变量,用来保存int或integer数据 private com.lavasoft.test2.threadlocal<integer> tl = new com.lavasoft.test2.threadlocal<integer>() { @override protected integer initialvalue() { return 0; } }; public integer getnextnum() { //将tl的值获取后加1,并更新设置t1的值 tl.set(tl.get() + 1); return tl.get(); } } class threadlocal<t> { private map<thread, t> map = collections.synchronizedmap(new hashmap<thread, t>()); public threadlocal() { } protected t initialvalue() { return null; } public t get() { thread t = thread.currentthread(); t obj = map.get(t); if (obj == null && !map.containskey(t)) { obj = initialvalue(); map.put(t, obj); } return obj; } public void set(t value) { map.put(thread.currentthread(), value); } public void remove() { map.remove(thread.currentthread()); } }
事实上threadlocal是这样做的:
public t get() { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) { threadlocalmap.entry e = map.getentry(this); if (e != null) return (t)e.value; } return setinitialvalue(); }
上一篇: 模糊控制器设计实验
下一篇: FastDFS分布式文件系统集群部署