1、Java线程
一、Java多线程常用的两种实现方法
1、 继承Thread类
子类继承Thread类具备多线程能力
启动线程:子类对象.start()
不建议使用:避免OOP单继承局限性
2、 实现Runnable接口
实现接口Runnable接口具有多线程能力
启动线程:传入目标对象+Thread对象.start()
二、静态代理
真实对象和代理对象都要实现同一个接口;
代理对象要代理真实角色;
四、线程的状态
1、线程状态相关API
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程(不建议使用)
boolean isAlive() 测试线程是否处于活状态
2、线程休眠 sleep
sleep(时间)指定当前线程阻塞的毫秒数;
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态;
sleep可以模拟网络延时,到计时等;
每一个对象都有一个锁,sleep不会释放锁;
** wait和sleep
共同点:
都是使线程暂停一段时间的方法。
不同点:
①原理不同-sleep()是属于Thread类中的,而wait()方法,则是属于Object类中的。
②锁处理机制不同-sleep()最主要作用使线程暂停执行一段时间,时间一到自动恢复,不涉及线程通讯,因此,调用sleep()方法并不会释放锁。而当调用wait()方法的时候,线程会释放它所占的锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
③使用区域不同-wait()方法必须放在同步代码块或者同步方法中使用,sleep()可以用在任何地方
synchronized(x){
x.notify()
//或者wait()
}
4、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
四、线程之间通信原理
线程的通信可以被定义为:当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以避免无效的资源争夺。线程间通信的方式可以有很多种:等待-通知、共享内存、管道流。每种方式用不同的方法来实现,这里首先介绍等待-通知的通信方式。“等待-通知”通信方式是Java中使用普遍的线程间通信方式。
wait()方法的原理:首先,JVM会释放当前线程的对象锁监视器的Owner资格;其次,JVM会将当前线程移入监视器的WaitSet队列,而这些操作都和对象锁监视器是相关的。所以,wait()方法必须在synchronized同步块的内部调用。在当前线程执行wait()方法前,必须通过synchronized()方法成为对象锁的监视器的Owner。
notify()方法的原理:JVM从对象锁的监视器的WaitSet队列移动一个线程到其EntryList队列,这些操作都与对象锁的监视器有关。所以,notify()方法也必须在synchronized同步块的内部调用。在执行notify()方法前,当前线程也必须通过synchronized()方法成为对象锁的监视器的Owner。
2、各种奇奇怪怪的线程锁
由于多个线程是配合占有所属历程的资源和地址空间的,那么就会存在一个问题:
若是多个线程要同时接见某个资源,怎么处置?
在Java并发编程中,经常遇到多个线程接见同一个 共享资源 ,这时刻作为开发者必须思量若何维护数据一致性,这就是Java锁机制(同步问题)的泉源。
Java提供了多种多线程锁机制的实现方式,常见的有:
synchronized
ReentrantLock
AtomicInteger等
每种机制都有优瑕玷与各自的适用场景,必须熟练掌握他们的特点才能在Java多线程应用开发时轻车熟路。
4种Java线程锁(线程同步)
1.synchronized
在Java中synchronized关键字被常用于维护数据一致性。
synchronized机制是给共享资源上锁,只有拿到锁的线程才可以接见共享资源,这样就可以强制使得对共享资源的接见都是顺序的。
Java开发人员都熟悉synchronized,使用它来实现多线程的同步操作是异常简朴的,只要在需要同步的对方的方式、类或代码块中加入该关键字,它能够保证在同一个时刻最多只有一个线程执行同一个工具的同步代码,可保证修饰的代码在执行过程中不会被其他线程滋扰。使用synchronized修饰的代码具有原子性和可见性,在需要历程同步的程序中使用的频率异常高,可以知足一样平常的历程同步要求。
synchronized(obj) {
//方式
…….
}
synchronized实现的机理依赖于软件层面上的JVM,因此其性能会随着Java版本的不停升级而提高。
到了Java1.6,synchronized举行了许多的优化,有顺应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7与1.8中,均对该关键字的实现机理做了优化。
需要说明的是,当线程通过synchronized守候锁时是不能被Thread.interrupt()中止的,因此程序设计时必须检查确保合理,否则可能会造成线程死锁的尴尬田地。
最后,只管Java实现的锁机制有许多种,而且有些锁机制性能也比synchronized高,但照样强烈推荐在多线程应用程序中使用该关键字,由于实现利便,后续事情由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才思量使用其他机制,如ReentrantLock等。
2.ReentrantLock
可重入锁,顾名思义,这个锁可以被线程多次重复进入举行获取操作。
ReentantLock继续接口Lock并实现了接口中界说的方式,除了能完成synchronized所能完成的所有事情外,还提供了诸如可响应中止锁、可轮询锁请求、准时锁等制止多线程死锁的方式。
Lock实现的机理依赖于特殊的CPU指定,可以以为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准。
因此我们建议在高并发量情形下使用ReentrantLock。
ReentrantLock引入两个观点:公正锁与非公正锁。
公正锁指的是锁的分配机制是公正的,通常先对锁提出获取请求的线程会先被分配到锁。反之,JVM按随机、就近原则分配锁的机制则称为不公正锁。
ReentrantLock在组织函数中提供了是否公正锁的初始化方式,默以为非公正锁。这是由于,非公正锁现实执行的效率要远远超出公正锁,除非程序有特殊需要,否则最常用非公正锁的分配机制。
ReentrantLock通过方式lock()与unlock()来举行加锁与解锁操作,与synchronized会被JVM自动解锁机制差别,ReentrantLock加锁后需要手动举行解锁。为了制止程序泛起异常而无法正常解锁的情形,使用ReentrantLock必须在finally控制块中举行解锁操作。通常使用方式如下所示:
Lock lock = new ReentrantLock();
try {
lock.lock();
//…举行义务操作5 }
finally {
lock.unlock();
}
3.Semaphore
上述两种锁机制类型都是“互斥锁”,学过操作系统的都知道,互斥是历程同步关系的一种特殊情形,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。然则,在现实庞大的多线程应用程序中,可能存在多个临界资源,这时刻我们可以借助Semaphore信号量来完成多个临界资源的接见。
Semaphore基本能完成ReentrantLock的所有事情,使用方式也与之类似,通过acquire()与release()方式来获得和释放临界资源。
经实测,Semaphone.acquire()方式默以为可响应中止锁,与ReentrantLock.lockInterruptibly()作用效果一致,也就是说在守候临界资源的过程中可以被Thread.interrupt()方式中止。
此外,Semaphore也实现了可轮询的锁请求与准时锁的功效,除了方式名tryAcquire与tryLock差别,其使用方式与ReentrantLock险些一致。Semaphore也提供了公正与非公正锁的机制,也可在组织函数中举行设定。
Semaphore的锁释放操作也由手动举行,因此与ReentrantLock一样,为制止线程因抛出异常而无法正常释放锁的情形发生,释放锁的操作也必须在finally代码块中完成。
4.AtomicInteger
首先说明,此处AtomicInteger是一系列相同类的代表之一,常见的另有AtomicLong、AtomicLong等,他们的实现原理相同,区别在与运算工具类型的差别。
我们知道,在多线程程序中,诸如++i
或
i++等运算不具有原子性,是不安全的线程操作之一。通常我们会使用synchronized将该操作酿成一个原子操作,但JVM为此类操作特意提供了一些同步类,使得使用更利便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。
Java线程锁总结
1.synchronized:
在资源竞争不是很猛烈的情形下,偶然会有同步的情形下,synchronized是很合适的。缘故原由在于,编译程序通常会尽可能的举行优化synchronize,另外可读性异常好。
2.ReentrantLock:
在资源竞争不猛烈的情形下,性能稍微比synchronized差点点。然则当同步异常猛烈的时刻,synchronized的性能一下子能下降好几十倍,而ReentrantLock确还能维持常态。
高并发量情形下使用ReentrantLock。
3.Atomic:
和上面的类似,不猛烈情形下,性能比synchronized略逊,而猛烈的时刻,也能维持常态。猛烈的时刻,Atomic的性能会优于ReentrantLock一倍左右。然则其有一个瑕玷,就是只能同步一个值,一段代码中只能泛起一个Atomic的变量,多于一个同步无效。由于他不能在多个Atomic之间同步。
以是,我们写同步的时刻,优先思量synchronized,若是有特殊需要,再进一步优化。ReentrantLock和Atomic若是用的欠好,不仅不能提高性能,还可能带来灾难。
以上就是Java线程锁的详解,除了从编程的角度应对高并发,更多还需要从架构设计的层面来应对高并发场景,例如:Redis缓存、CDN、异步新闻等
4、volatile和synchronized区别
1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法.
3)volatile仅能实现变量的修改可见性,而synchronized则可以保证变量的修改可见性和原子性.
《Java编程思想》上说,定义long或double变量时,如果使用volatile关键字,就会获得(简单的赋值与返回操作)原子性。
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞.
5、当一个域的值依赖于它之前的值时,volatile就无法工作了,如n=n+1,n++等。如果某个域的值受到其他域的值的限制,那么volatile也无法工作,如Range类的lower和upper边界,必须遵循lower<=upper的限制。
6、使用volatile而不是synchronized的唯一安全的情况是类中只有一个可变的域。
4、synchronized和lock区别
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
网友评论