「java线程锁有什么用」java 多线程 锁
本篇文章给大家谈谈java线程锁有什么用,以及java 多线程 锁对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、JAVA中,线程死锁是什么意思
- 2、java多线程,对象锁是什么概念?
- 3、Java锁有哪些种类,以及区别
- 4、Java 多线程中 什么是死锁有什么作用
- 5、Java多线程中,锁是什么,所谓的获取锁是什么意思
JAVA中,线程死锁是什么意思
一. 什么是线程
在谈到线程死锁的时候,我们首先必须了解什么是Java线程。一个程序的进程会包含多个线程,一个线程就是运行在一个进程中的一个逻辑流。多线程允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。
线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信较进程简单。笔者的经验是编写多线程序,必须注意每个线程是否干扰了其他线程的工作。每个进程开始生命周期时都是单一线程,称为“主线程”,在某一时刻主线程会创建一个对等线程。如果主线程停滞则系统就会切换到其对等线程。和一个进程相关的线程此时会组成一个对等线程池,一个线程可以杀死其任意对等线程。
因为每个线程都能读写相同的共享数据。这样就带来了新的麻烦:由于数据共享会带来同步问题,进而会导致死锁的产生。
二. 死锁的机制
由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。
导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。
Java中每个对象都有一把锁与之对应。但Java不提供单独的lock和unlock操作。下面笔者分析死锁的两个过程“上锁”和“锁死” 。
(1) 上锁
许多线程在执行中必须考虑与其他线程之间共享数据或协调执行状态,就需要同步机制。因此大多数应用程序要求线程互相通信来同步它们的动作,在 Java 程序中最简单实现同步的方法就是上锁。在 Java 编程中,所有的对象都有锁。线程可以使用 synchronized 关键字来获得锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。
为了防止同时访问共享资源,线程在使用资源的前后可以给该资源上锁和开锁。给共享变量上锁就使得 Java 线程能够快速方便地通信和同步。某个线程若给一个对象上了锁,就可以知道没有其他线程能够访问该对象。即使在抢占式模型中,其他线程也不能够访问此对象,直到上锁的线程被唤醒、完成工作并开锁。那些试图访问一个上锁对象的线程通常会进入睡眠状态,直到上锁的线程开锁。一旦锁被打开,这些睡眠进程就会被唤醒并移到准备就绪队列中。
(2)锁死
如果程序中有几个竞争资源的并发线程,那么保证均衡是很重要的。系统均衡是指每个线程在执行过程中都能充分访问有限的资源,系统中没有饿死和死锁的线程。当多个并发的线程分别试图同时占有两个锁时,会出现加锁冲突的情形。如果一个线程占有了另一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。
在编写多线程代码时,笔者认为死锁是最难处理的问题之一。因为死锁可能在最意想不到的地方发生,所以查找和修正它既费时又费力。例如,常见的例子如下面这段程序。
public int sumArrays(int[] a1, int[] a2) ...{ int value = 0; int size = a1.length; if (size == a2.length) ...{ synchronized(a1) ...{ //1 synchronized(a2) ...{ //2 for (int i=0; isize; i++) value += a1[i] + a2[i]; } } } return value; }
这段代码在求和操作中访问两个数组对象之前锁定了这两个数组对象。它形式简短,编写也适合所要执行的任务;但不幸的是,它有一个潜在的问题。这个问题就是它埋下了死锁的种子。
没有完结,请楼主看下面的网址。
java多线程,对象锁是什么概念?
java线程:
1.线程中一些基本术语和概念
1.1线程的几个状态
初始化状态
就绪状态
运行状态
阻塞状态
终止状态
1.2 Daemon线程
Daemon线程区别一般线程之处是:主程序一旦结束,Daemon线程就会结束。
1.3锁的定义
为了协调多个并发运行的线程使用共享资源才引入了锁的概念。
1.4死锁
任何多线程应用程序都有死锁风险。当一组线程中的每一个都在等待一个只
有该组中另一个线程才能引起的事件时,我们就说这组线程死锁了。换一个说法
就是一组线程中的每一个成员都在等待别的成员占有的资源时候,就可以说这组
线程进入了死锁。死锁的最简单情形是:线程 A 持有对象 X 的独占锁,并且
在等待对象 Y 的锁,而线程 B 持有对象 Y 的独占锁,却在等待对象 X 的锁。
除非有某种方法来打破对锁的等待(Java 锁定不支持这种方法),否则死锁的线
程将永远等下去。
1.5.Java对象关于锁的几个方法
1.5.1 wait方法
wait方法是java根对象Object含有的方法,表示等待获取某个锁。在wait方法进入前,会释放相应的锁,在wait方法返回时,会再次获得某个锁。
如果wait()方法不带有参数,那只有当持有该对象锁的其他线程调用了notify或者notifyAll方法,才有可能再次获得该对象的锁。
如果wait()方法带有参数,比如:wait(10),那当持有该对象锁的其他线程调用了notify或者notifyAll方法,或者指定时间已经过去了,才有可能再次获得该对象的锁。
参考 thread.lock.SleepAndWait
1.5.2 notify/notifyAll方法
这里我就不再说明了。哈哈,偷点懒。
1.5.3 yield方法
yield()会自动放弃CPU,有时比sleep更能提升性能。
1.6锁对象(实例方法的锁)
在同步代码块中使用锁的时候,担当锁的对象可以是这个代码所在对象本身或者一个单独的对象担任,但是一定要确保锁对象不能为空。如果对一个null对象加锁,会产生异常的。原则上不要选择一个可能在锁的作用域中会改变值的实例变量作为锁对象。
锁对象,一种是对象自己担任,一种是定义一个普通的对象作为private property来担任,另外一种是建立一个新的类,然后用该类的实例来担任。
参考 :
thread.lock.UseSelfAsLock,使用对象自己做锁对象
thread.lock.UseObjAsLock 使用一个实例对象作锁对象
thread.lock.UseAFinalObjAsLock使用常量对象作为一个锁对象
1.7类锁
实例方法存在同步的问题,同样,类方法也存在需要同步的情形。一般类方法的类锁是一个static object来担任的。当然也可以采用类本身的类对象来作为类锁。
一个类的实例方法可以获得该类实例锁,还可以尝试去访问类方法,包含类同步方法,去获得类锁。
一个类的类方法,可以尝试获得类锁,但是不可以尝试直接获得实例锁。需要先生成一个实例,然后在申请获得这个实例的实例锁。
参考
thread.lock.UseStaticObjAsStaticLock 使用类的属性对象作为类锁。
thread.lock.UseClassAsStaticLock使用类的类对象作为类锁
1.8.线程安全方法与线程不安全方法
如果一个对象的所有的public方法都是同步方法,也就是说是public方法是线程安全的,那该对象的private方法,在不考虑继承的情况下,可以设置为不是线程安全的方法。
参考 thread.lock.SynMethrodAndNotSynMethrod
1.9类锁和实例锁混合使用
在实例方法中混合使用类锁和实例锁;可以根据前面说的那样使用实例锁和类锁。
在类方法中混合使用类锁和实例锁,可以根据前面说的那样使用类锁,为了使用实例锁,先得生成一个实例,然后实例锁。
参考 thread.lock.StaticLockAndObjLock
1.10锁的粒度问题。
为了解决对象锁的粒度过粗,会导死锁出现的可能性加大,锁的粒度过细,会程序开发维护的工作加大。对于锁的粒度大小,这完全要根据实际开发需要来考虑,很难有一个统一的标准。
1.11.读写锁
一个读写锁支持多个线程同时访问一个对象,但是在同一时刻只有一个线程可以修改此对象,并且在访问进行时不能修改。
有2种调度策略,一种是读锁优先,另外就是写锁优先。
参考 thread.lock.ReadWriteLock
1.12 volatile
在Java中设置变量值的操作,除了long和double类型的变量外都是原子操作,也就是说,对于变量值的简单读写操作没有必要进行同步。这在JVM 1.2之前,Java的内存模型实现总是从主存读取变量,是不需要进行特别的注意的。而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用变得非常重要。在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。要解决这个问题,只需要像在本程序中的这样,把该变量声明为volatile(不稳定的)即可,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。一般说来,多任务环境下各任务间共享的标志都应该加volatile修饰。
2.线程之间的通讯
在其他语言中,线程之间可以通过消息队列,共享内存,管道等方式来实现
线程之间的通讯,但是java中可以不采用这样方式,关注的是线程之间的同步。
只要保证相关方法运行的线程安全,信息共享是自然就可以显现了。
2.1屏障
屏障就是这样的一个等待点: 一组线程在这一点被同步,这些线程合并各自的结果或者运行到整体任务的下一阶段。
参考:
thread.lock. BarrierUseExample
thread.lock.Barrier
2.2.锁工具类
提供对线程锁的获取,释放功能。展示了锁的获取释放过程。可以作为一个工具类来使用。
参考:thread.lock. BusyFlag
2.3.条件变量
条件变量是POSIX线程模型提供的一种同步类型,和java中的等待通知机制类似。
虽然java中已经有了等待通知机制,但是为了减少在notify/notifyAll方法中
线程调度的开销,把一些不需要激活的线程屏蔽出去,引入了条件变量。
Java中2个(多个)条件变量可以是同一个互斥体(锁对象)。
参考:thread.lock.CondVar 条件变量类
常见的应用情形:
一个锁控制多个信号通道(例如:多个变量),虽然可以采用简单java等待通知机制,但是线程调度效率不高,而且线程可读性也不是太好,这时候可以采用创建一个锁对象(BusyFlag实例),同时使用这个BusyFlag实例来创建多个条件变量(CondVar 实例)。
经常使用到CondVar类的地方是缓冲区管理,比如:管道操作之类的。先创建一个BusyFlag实例,然后创建CondVar 实例,用这个条件变量描述缓冲区是否为空,另外创建CondVar 实例作条件变量述缓冲区是否满。
现实中,马路的红绿灯,就可以采用条件变量来描述。
3. Java线程调度
3.1 Java优先级
java的优先级别共有10种,加上虚拟机自己使用的优先级别=0这种,总共11种。
大多数情况来说,java线程的优先级设置越高(最高=10),那线程越优先运行。
3.2. 绿色线程
线程运行在虚拟机内,操作系统根本不知道这类线程的存在。
线程是由虚拟机调度的。
3.3 本地线程
线程是由运行虚拟机的操作系统完成的。
3.4 Windows本地线程
操作系统,完全能够看得到虚拟机内的每一个线程,同时虚拟机的线程和操作系统的线程是一一对应的。Java的线程调度室由操作系统底层线程决定的。
在win32平台下,windows线程只有6个优先级别。和java线程优先级别对应如下:
Java线程优先级 Windows 95/nt/2000线程优先级
0 THREAD_ PRIORITY_IDLE
1(Thread.MIN_PRIORITY) THREAD_ PRIORITY_LOWEST
2 THREAD_ PRIORITY_LOWEST
3 THREAD_ PRIORITY_BELOW_NORMAL
4 THREAD_ PRIORITY_BELOW_NORMAL
5 (Thread.NORM_PRIORITY) THREAD_ PRIORITY _NORMAL
6 THREAD_ PRIORITY _ABOVE_NORMAL
7 THREAD_ PRIORITY _ABOVE_NORMA
8 THREAD_ PRIORITY _HIGHEST
9 THREAD_ PRIORITY _HIGHEST
10 (Thread.MAX_PRIORITY) THREAD_ PRIORITY _CRITICAL
3.5线程优先级倒置与继承
如果一个线程持有锁(假设该线程名字=ThreadA,优先级别=5),另外一个线程(假设该线程名字=ThreadB,优先级别=7),现在该线程(ThreadA)处于运行状态,但是线程ThreadB申请需要持有ThreadA所获得的锁,这时候,为了避免死锁,线程A提高其运行的优先级别(提高到ThreadB的优先级别=7),而线程ThreadB为了等待获得锁,降低线程优先级别(降低到ThreadA原来的优先级别=5).
上述的这种情况,对于ThreadA,继承了ThreadB的优先级别,这成为优先级别的继承;对于ThreadB暂时降低了优先级别,成为优先级别的倒置。
当然,一旦线程ThreadA持有的锁释放了,其优先级别也会回到原来的优先级别(优先级别=5)。线程ThreadB获得了相应的锁,那优先级别也会恢复到与原来的值(优先级别=7)。
3.6循环调度
具有同样优先级的线程相互抢占成为循环调度。
4.线程池
创建一个线程也是需要一定代价的,为了降低这个代价,采用了和普通对象池的思想建立线程池,以供系统使用。
线程消耗包括内存和其它系统资源在内的大量资源。除了 Thread 对象所需的内存之外,每个线程都需要两个可能很大的执行调用堆栈。除此以外,JVM 可能会为每个 Java 线程创建一个本机线程,这些本机线程将消耗额外的系统资源。最后,虽然线程之间切换的调度开销很小,但如果有很多线程,环境切换也可能严重地影响程序的性能。
使用线程池的方式是,先建立对象池,然后申请使用线程,程序线程运行,运行完毕,把线程返回线程池。
使用线程池的风险:同步错误和死锁,与池有关的死锁、资源不足和线程泄漏。
大家有空可以研究一下tomcat的线程池实现原理思想。
实际上是tomcat已经在从线程池的使用线程时候加上了事件处理机制。
个人认为,线程池之类的实现,一般不要自己实现,因为自己实现主要是稳定性等方面可能作的不够好。
可以参考 apache的jakarta-tomcat-5.5.6的相关代码,具体是:
jakarta-tomcat-connectors\util\java\org\apache\tomcat\util\threads的相关代码
5工作队列
使用工作队列的好处是不象直接使用线程池那样,当线城池中没有线程可以使用的时
候,使用者需要处于等待状态,不能进行其他任务的处理。
工作队列的工作原理是:
采用后台线程处理方式,客户端把任务提交给工作队列,工作队列有一组内部可以工作线程,这些工作线程从工作队列中取出任务运行,一个任务完成后,就从队列获取下一个任务进行处理。当工作队列中没有任务可以处理时候,工作线程就处于等待状态,直到获得新的任务时候,才进行新的处理。
Java锁有哪些种类,以及区别
一、公平锁/非公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。
对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。
对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。
二、可重入锁
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。
对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。
三、独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java
ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。
四、互斥锁/读写锁
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock
读写锁在Java中的具体实现就是ReadWriteLock
五、乐观锁/悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。
乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。
从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。
悲观锁在Java中的使用,就是利用各种锁。
乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
六、分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。
我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。
但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。
分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
七、偏向锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java
5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。
八、自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
典型的自旋锁实现的例子,可以参考自旋锁的实现
Java 多线程中 什么是死锁有什么作用
所谓死锁:
是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
由于资源占用是互斥的,当某个进程提出申请资源后,使得有关进程在无外力协助下,永远分配不到必需的资源而无法继续运行,这就产生了一种特殊现象死锁。
Java多线程中,锁是什么,所谓的获取锁是什么意思
简单的跟你讲一下,当有多个线程使用同一个资源的时候,为了避免死锁,往往在一个线程在使用一个资源的时候给这段代码一个锁(也就是说我在操作的时候别人都不能动),在执行完后再把这个锁放开(这时候别的线程就可以使用该资源了)。
java线程锁有什么用的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于java 多线程 锁、java线程锁有什么用的信息别忘了在本站进行查找喔。