首页 > 代码库 > 线程简单学习2
线程简单学习2
一、理解多线程
多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。
多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的。多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执行的,将会带来线程调度,同步等问题。
二、在Java中实现多线程
http://hanchaohan.blog.51cto.com/2996417/923199
三、线程的四种状态
1、新状态:线程已被创建但尚未执行(start() 尚未被调用)。
2、可执行状态:线程可以执行,虽然不一定正在执行。CPU 时间随时可能被分配给该线程,从而使得它执行。
3、阻塞状态:线程不会被分配 CPU 时间,无法执行;可能阻塞于I/O,或者阻塞于同步锁。
4、死亡状态:正常情况下run() 返回使得线程死亡。调用 stop()或 destroy() 亦有同样效果,但是不被推荐,前者会产生异常,后者是强制终止,不会释放锁。
四、线程的优先级
线程的优先级代表该线程的重要程度,当有多个线程同时处于可执行状态并等待获得CPU 时间时,线程调度系统根据各个线程的优先级来决定给谁分配CPU 时间,优先级高的线程有更大的机会获得 CPU 时间,优先级低的线程也不是没有机会,只是机会要小一些罢了。
你可以调用 Thread 类的方法 getPriority() 和 setPriority()来存取线程的优先级,线程的优先级界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之间,缺省是5(NORM_PRIORITY)。
五、线程的同步
1. synchronized 方法:通过在方法声明中加入synchronized关键字来声明 synchronized 方法。synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为synchronized的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为synchronized)。
synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法run()声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronized方法的调用都永远不会成功。
2. synchronized 块:通过 synchronized关键字来声明synchronized 块。
synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject 的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。
六、线程的阻塞
阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。
1. sleep()方法:sleep()允许指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。
2. suspend()和resume()方法:两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume()被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用resume()使其恢复。
3. yield() 方法:yield() 使得线程放弃当前分得的CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程。
4. wait() 和 notify() 方法:两个方法配套使用,wait()使得线程进入阻塞状态,它有两种形式,一种允许指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify()被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的notify() 被调用。
2和4区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。
首先,前面叙述的所有方法都隶属于Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的wait()方法导致线程阻塞,并且该对象上的锁被释放。而调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。
其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在synchronized方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的synchronized方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException异常。
wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用。
关于 wait() 和 notify() 方法最后再说明两点:
第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。
第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll()方法将把因调用该对象的wait()方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。
谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。
死锁就是一个进程中的每个线程都在等待这个进程中的其他线程释放所占用的资源,从而导致所有线程都无法继续执行的情况。死锁是多线程编程中一个隐藏的陷阱,它经常发生在多个线程共用资源的时候。在实际开发中,死锁一般隐藏的较深,不容易被发现,一旦死锁现象发生,就必然会导致程序的瘫痪。因此必须避免它的发生。
程序中必须同时满足以下四个条件才会引发死锁:
1. 互斥(Mutual exclusion): 线程所使用的资源中至少有一个是不能共享的,它在同一时刻只能由一个线程使用。
2. 持有与等待(Hold and wait): 至少有一个线程已经持有了资源,并且正在等待获取其他的线程所持有的资源。
3. 非抢占式(No pre-emption): 如果一个线程已经持有了某个资源,那么在这个线程释放这个资源之前,别的线程不能把它抢夺过去使用。
4. 循环等待(Circular wait): 假设有N个线程在运行,第一个线程持有了一个资源,并且正在等待获取第二个线程持有的资源,而第二个线程正在等待获取第三个线程持有的资源,依此类推……第N个线程正在等待获取第一个线程持有的资源,由此形成一个循环等待。
还有一个方法比较有用,Thread的join()方法可用于让当前线程阻塞,以等待特定线程的消亡。
5.sleep()和wait()的区别和联系
相同点:sleep()方法和wait()方法都成产生让当前运行的线程停止运行的效果,这是它们的共同点。
①sleep()方法是本地方法,属于Thread类,
调用sleep()方法并不会让线程释放它所持有的同步锁;而且在这期间它也不会阻碍其它线程的运行。
抛出一个InterruptedException类型的异常
因为线程在sleep()期间,有可能被持有它的引用的其它线程调用它的interrupt()方法而中断。中断一个线程会导致一个InterruptedException异常的产生,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码
②wait()方法也是本地方法,属于Object类,
wari()和wait(long timeout,int nanos)方法都是基于wait(long timeout)方法实现的。同样地,timeout代表毫秒数,nanos代表纳秒数。当调用了某个对象的wait()方法时,当前运行的线程就会转入等待状态(WAITING),等待别的线程再次调用这个对象的notify()或者notifyAll()方法(这两个方法也是本地方法)唤醒它,或者到了指定的最大等待时间,线程自动醒来。如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法同样会被Thread类的interrupt()方法中断,并产生一个InterruptedException异常,效果同sleep()方法被中断一样。
五:线程池
线程池就像数据库 连接池一样,是一个对象池。所有的对象池都有一个共同的目的,那就是为了提高对象的使用率,从而达到提高程序效率的目的。比如对于Servlet,它被设计为多线程的(如果它是单线程的,你就可以想象,当1000个人同时请求一个网页时,在第一个人获得请求结果之前,其它999个人都在郁闷地等待),如果为每个用户的每一次请求都创建一个新的线程对象来运行的话,系统就会在创建线程和销毁线程上耗费很大的开销,大大降低系统的效率。因此,Servlet多线程机制背后有一个线程池在支持,线程池在初始化初期就创建了一定数量的线程对象,通过提高对这些对象的利用率,避免高频率地创建对象,从而达到提高程序的效率的目的。
原理:需要四个类!
Task(任务):
TaskQueue(任务队列)
TaskThread(执行任务的线程)
ThreadPoolService(线程池服务 类)
事实上Java5.0及以上版本已经为我们提供了线程池功能,无需再重新实现。这些类位于java.util.concurrent包中。
Executors类提供了一组创建线程池对象的方法
本文出自 “我的JAVA世界” 博客,请务必保留此出处http://hanchaohan.blog.51cto.com/2996417/1537557