java 多线程浅谈
1.要想了解多线程。首先,要知道进程和线程的区别。
进程:是操作系统进行资源分配的基本单位,进程间的切换会有较大的开销。
线程:是cpu任务调度和执行的基本单位,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
进程也指 一个具有独立功能的程序模块,对一组数据集合进行的一次运行活动,一个进程可以包含多个线程。
2.多线程的并行与并发
- 并行,多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发,一个cpu运行多个任务,而cpu每次只能处理一个任务,就把任务轮流执行,由于时间间隔短,感觉像是每个任务都在运行。(表面看是cpu在同时执行多个任务,实际上是因为cpu瞬间切换到其他任务的速度特别快,给不同的任务分配了不同的时间。)
即,并行是在不同实体上进行多个事件,并发是在同一实体上进行多个事件。
线程安全:指在并发的情况下,程序经过多线程使用,线程的调度顺序不影响任何结果。
线程同步:当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。
3.实现多线程,有三种方式,一种是继承Thread类,另外一种是实现Runable接口,还有一种是实现Callable接口,第三种要与Future、线程池结合使用,常用的是前两种。
关于Thread和Runable这两种有哪些区别,
- 适合多个相同的程序代码的线程去处理同一个资源;
- 可以避免java中的单继承的限制;
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立;
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。
4、线程状态
1、新建状态(New):创建了一个线程对象。
2、就绪状态(Runnable):该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):获取了CPU使用权,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
- 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
- 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
5、线程优先级
java线程的优先级用整数表示,取值范围1~10,优先级高的线程会获得较多的运行机会:
static int MAX_PRIORITY 值为10。
static int MIN_PRIORITY 值为1。
static int NORM_PRIORITY 默认优先级,值为5。
- 使用Thread类的setPriority()和getPriority()方法可以设置和获取线程的优先级。
- 每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
- 线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
- JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类的这三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
6、线程的调度方法
1、线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,转为就绪(Runnable)状态。sleep()平台移植性好。
2、线程等待:Object类的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。
3、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
4、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
5、线程唤醒:Object类的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。这两个唤醒方法的行为等价于调用 wait(0) 。
sleep()和yield()的区别:
sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
wait和sleep区别:
共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点:
1. Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
所以sleep()和wait()方法的最大区别是:
sleep()睡眠时,保持对象锁,仍然占有该锁;
而wait()睡眠时,释放对象锁。
但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。
提醒:1. Thread中suspend()和resume()两个方法在JDK1.5中已经废除,有死锁倾向。
线程类的常用方法:
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级。
6、线程同步
synchronized关键字:
- 某个对象实例,synchronized aMethod(){},可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
- 某个类的范围,synchronized static aStaticMethod{},防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
- synchronized用于方法中的某个区块中,synchronized(this){} ,表示只对这个区块的资源实行互斥访问。它的作用域是当前对象。
synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
使用synchronized,需要明确几点:
- 无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
- 每个对象只有一个锁(lock)与之相关联。
- 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
Lock类:
主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似.但有一些区别。
- lock更灵活,性能更高,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)
- 提供多种加锁方案,lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式, 还有trylock的带超时时间版本。
- 必须控制好加锁和解锁,否则会导致灾难。
有三种实现方式:
ReentrantLock
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
这里主要讲ReentrantLock的使用。
1.先new一个实例
[code]static ReentrantLock r=new ReentrantLock();
2.加锁
[code]r.lock()或r.lockInterruptibly(); 这2种方法有所不同,后者可被打断。当a线程lock后,b线程阻塞, 此时如果是lockInterruptibly,那么在调用b.interrupt()之后,b线程退出阻塞, 并放弃对资源的争抢,进入catch块。 (如果使用后者,必须throw interruptable exception 或catch)
3.释放锁
[code]r.unlock(); 必须做!何为必须做呢,要放在finally里面。以防止异常跳出了正常流程,导致灾难。 这里补充一个小知识点,finally是可以信任的: 经过测试,哪怕是发生了OutofMemoryError,finally块中的语句执行也能够得到保证。
关于Thread类的中断:它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛InterruptedException。
Thread.interrupted()检查当前线程是否发生中断,返回boolean
synchronized在获锁的过程中是不能被中断的。
中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体
- 浅谈Java多线程的同步问题
- 浅谈多线程在java程序中的应用
- 浅谈java的多线程
- 浅谈Java多线程的同步问题
- 【转】浅谈Java多线程的同步问题
- 浅谈Java中的多线程
- 浅谈Java多线程的同步问题
- 浅谈Java多线程的同步问题
- 最近在研究多线程,浅谈JAVA中多线程的几种实现方式
- 浅谈Java中的ThreadLocal的多线程应用问题
- 浅谈Java多线程的同步问题
- 浅谈Java多线程
- 最近在研究多线程,浅谈JAVA中多线程的几种实现方式
- 浅谈Java多线程的同步问题
- java学习之浅谈多线程3--线程间协作
- 浅谈Java多线程的同步问题
- 浅谈java多线程中的内存可见性
- 浅谈 Java 多线程环境下 如何跟踪死锁 (例子)
- 浅谈多线程在java程序中的应用
- 浅谈Java多线程的同步问题