java线程同步是什么意思,java线程同步的几种方法
如何解决写爬虫IP受阻的问题?立即使用。
线程同步
当同一个对象被多个线程调用时,为了安全准确地运行,需要对对象进行同步,以保证每个线程使用时对象的结果正确,对象的状态合理。这部分涉及到同步、线程锁等知识点。这部分只涉及同步和同步锁的概念。
synchronized
同步关键字可以修改对象和方法,通常如下使用:
//同步代码块
同步(对象对象){
.
}
//或者
//同步方法
公共同步void测试(){
.
}有一个同步监视器的概念。例如,上述同步代码块的object对象和synchronized方法的this对象将同步监视。当多个线程同时调用一个同步的代码块或方法时,任何时候都只有一个线程可以获取同步的被监控对象的锁,代码执行后锁会被释放。在此期间,其他调用线程只能在锁释放后调用。
文中提到的SellRunnable类中的sell方法也使用了synchronized方法。上面文章中的代码执行速度太快,所以无法感知。修改一下就能明白有没有同步差异。
公共类ThreadTest {
公共静态void main(String[] args) {
sell runnable sell runnable=new sell runnable();
Thread thread1=新线程(sellRunnable, 1 );
Thread thread2=新线程(sellRunnable, 2 );
Thread thread3=新线程(sellRunnable, 3 );
thread 2 . start();
thread 1 . start();
thread 3 . start();
}
}
类SellRunnable实现Runnable {
//有十张票。
int index=10
public void sell() {
if (index=1) {
尝试{
thread . sleep(1000);
} catch (InterruptedException e) {
e . printstacktrace();
}
索引-;
System.out.println(销售窗口: Thread.currentThread()。getName()
卖出一张票,剩下的:
指数);
}否则{
System.out.println(销售窗口: Thread.currentThread()。getName()‘买的时候没票’);
}
}
@覆盖
公共无效运行(){
while(索引0) {
System.out.println(销售窗口: Thread.currentThread()。getName()开始买票);
sell();
}
}
}
//执行结果:
销售窗口:1开始买票。
售票窗口:开始买票。
售票窗口:3点开始买票。
销售窗口:售出一张票,剩余9张
售票窗口:开始买票。
销售窗口:售出1张票,剩余9张
销售窗口:1开始买票。
销售窗口:售出3张票,剩余8张
售票窗口:3点开始买票。
销售窗口:1售出一张票,其余:6
销售窗口:1开始买票。
销售窗口:售出一张票,剩余6张
售票窗口:开始买票。
销售窗口:售出3张票,剩余5张
售票窗口:3点开始买票。
销售窗口:1卖出了一张票,剩下的:4
销售窗口:1开始买票。
销售窗口:售出一张票,剩余3张
销售窗口:售出一张票,剩余2
售票窗口:3点开始买票。
售票窗口:开始买票。
销售窗口:售出3张票,剩余1张
销售窗口:2售出一张票,剩余:0
销售窗口:1卖出一张票,其余:1
过程结束,退出代码为0 //如您所见,减少投票数是错误的。
//sell方法在添加synchronized修饰符后执行结果:
公共同步void sell() {
if (index=1) {
尝试{
thread . sleep(1000);
} catch (InterruptedException e) {
e . printstacktrace();
}
索引-;
System.out.println(销售窗口: Thread.currentThread()。getName()
卖出一张票,剩下的:
指数);
}否则{
System.out.println(销售窗口: Thread.currentThread()。getName()‘买的时候没票’);
}
}
售票窗口:开始买票。
售票窗口:3点开始买票。
销售窗口:1开始买票。
销售窗口:售出一张票,剩余9张
售票窗口:开始买票。
销售窗口:1卖出一张票,其余:8
销售窗口:1开始买票。
销售窗口:售出一张票,剩余7张
售票窗口:3点开始买票。
销售窗口:1售出一张票,其余:6
销售窗口:1开始买票。
销售窗口:售出一张票,剩余5张
售票窗口:开始买票。
销售窗口:1卖出了一张票,剩下的:4
销售窗口:1开始买票。
销售窗口:1卖出一张票,其余:3
销售窗口:1开始买票。
销售窗口:售出一张票,剩余2
售票窗口:3点开始买票。
销售窗口:1卖出一张票,其余:1
销售窗口:1开始买票。
销售窗口:1售出一张票,剩余:0
销售窗口:买的时候没有票。
销售窗口:3买的时候没有票。
过程结束,退出代码为0 //如您所见,投票数正常减少。同步sell方法后,在某个时刻,只有一个线程会调用该方法,所以在内部判断索引时得到的结果就是正确的结果。
在上述同步过程中,为了保证线程安全,降低了运行效率。为此,不要在线程使用类中同步标识不必要的方法和对象,只标识竞争的资源或代码。
经过鉴定,有以下几点可以开锁:
代码和方法的执行完成(正常完成、返回或中断、抛出异常)
调用了wait方法,导致当前线程暂停。
当线程执行到同步代码块时,sleep和yield方法不会释放同步锁,suspend方法也不会(尽量避免在线程操作过程中使用suspend和resume来操作线程状态,容易导致死锁。)
同步锁Lock
文中提到的synchronized是java中的一个关键词。还提到线程在睡眠或IO操作时不会释放线程锁,其他线程要一直等待,有时会降低执行的效率。因此,需要一种在线程被阻塞时可以释放线程锁的替代方案。Lock的出现解决了这个问题。
Lock是java中的一个类。在java.util.concurrent.locks包中,具体代码如下:
公共接口锁{
void lock();//锁定
void lockInterruptibly()引发InterruptedException//锁定
布尔tryLock();//锁定
布尔tryLock(long time,TimeUnit单位)抛出InterruptedException//锁定
void unlock();//释放锁定
condition new condition();//用于线程协作
lock接口的一个实现子类是ReentrantLock。在java.util.concurrent.locks包下,ReentrantLock的源代码如下:
公共类ReentrantLock实现锁,Serializable {
private static final long serial version uid=7373984872572414699 l;
private final ReentrantLock。Sync同步;
public ReentrantLock() {
this.sync=new ReentrantLock。NonfairSync();
}
public reentrant lock(boolean var 1){//创建一个公平锁?
this.sync=(ReentrantLock。Sync)(var1?新的ReentrantLock。FairSync():新的ReentrantLock。
NonfairSync());
}
公共void锁(){
this . sync . lock();
}
public void lockInterruptibly()抛出InterruptedException {
this . sync . acquire interruptible(1);
}
公共布尔tryLock() {
返回this . sync . nonfairtryacquire(1);
}
public boolean tryLock(long var1,TimeUnit var3)抛出InterruptedException {
返回this.sync.tryAcquireNanos(1,var 3 . tonanos(var 1));
}
公共void解锁(){
this . sync . release(1);
}
公共条件newCondition() {
返回this . sync . new condition();
}
public gethold count(){//当前线程持有的锁的数量
返回this . sync . getholdcount();
}
public boolean isheldbycurrentThread(){//当前线程是否持有锁
返回this . sync . isheldexclusive();
}
Public boolean isLocked() {//锁是否被另一个线程持有?
返回this . sync . is locked();
}
public final boolean is fair(){///是公平锁吗?
返回ReentrantLock的this.sync实例。FairSync
}
受保护线程getOwner() {//持有当前锁的线程
返回this . sync . get owner();
}
public boolean hasqueuedthreads(){//是否有线程在等待锁?
返回this . sync . hasqueuedthreads();
}
public boolean hasqueuedthread(thread var 1){//目标线程是否在等待锁?
返回this . sync . is queued(var 1);
}
public int getqueuelength(){//等待此锁的线程数
返回this . sync . getqueuelength();
}
受保护的集合线程GetQueuedThreads(){//获取等待此锁的所有线程的集合。
返回this . sync . getqueuedthreads();
}
.
}Lock的使用方法
锁
Lock()用于获取锁,如果锁被其他线程占用,就会进入等待。
公共类锁定测试{
公共静态void main(String[] args) {
com . test . Java . sell runnable sell runnable=new com . test . Java . sell runnable();
ThreadThread1=新线程(sellrunnable, window 1 );
ThreadThread2=新线程(sellrunnable, window 2 );
ThreadThread3=新线程(sellrunnable, window 3 );
thread 1 . start();
thread 2 . start();
thread 3 . start();
}
}公共类SellRunnable实现Runnable {
//有十张票。
int index=10
lock lock=new reentrant lock();
public void sell() {
尝试{
lock . lock();
System.out.println(销售计数器: Thread.currentThread()。getName()
获取票源’);
if (index=1) {
索引-;
System.out.println(销售计数器: Thread.currentThread()。getName()
卖出一张票,剩下的:
指数);
}否则{
System.out.println(销售计数器: Thread.currentThread()。getName()
买票的时候000);
}
}最后{
lock . unlock();
}
}
@覆盖
公共无效运行(){
while(索引0) {
尝试{
thread . sleep(100);
} catch (InterruptedException e) {
e . printstacktrace();
}
sell();
}
}
}运行结果:
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余的9张
销售柜台:1号窗口有票源。
销售柜台:1号窗口卖了一张票,剩余8张。
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余7张
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余6张
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余5张
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余数量为4张。
销售柜台:3号窗口有票源。
销售柜台:一张票在3号窗口售出,剩下的是3张
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余的2
销售柜台:2号窗口有票源。
销售柜台:2号窗口卖了一张票,剩下一张是1。
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余:0
销售柜台:1号窗口有票源。
销售柜台:1000号窗口没有票。
销售柜台:2号窗口有票源。
销售柜台:2000号窗口没有票。
进程结束,退出代码为0 //每个窗口随机获取票源,然后出售车票tryLock。
TryLock()尝试获取锁,成功则返回true,失败则返回false,因此不会进入等待状态。
公共类SellRunnable实现Runnable {
//有十张票。
int index=10
lock lock=new reentrant lock();
public void sell() {
if (lock.tryLock()) {
尝试{
System.out.println(销售计数器: Thread.currentThread()。getName()
获取票源’);
if (index=1) {
索引-;
System.out.println(销售计数器: Thread.currentThread()。getName()
卖了一张票,剩下的:‘指数);
}否则{
System.out.println(销售计数器: Thread.currentThread()。getName()
买票的时候000);
}
}最后{
lock . unlock();
}
}否则{
System.out.println(销售计数器: Thread.currentThread()。 getName()未获得票证来源!);
}
}
@覆盖
公共无效运行(){
while(索引0) {
尝试{
thread . sleep(100);
} catch (InterruptedException e) {
e . printstacktrace();
}
sell();
}
}
}运行结果:
销售柜台:1号窗口有票源。
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口没有获得票源!
销售柜台:1号窗口售出一张票,剩余9张
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口有票源。
销售柜台:3号窗口卖了一张票,剩余8张。
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余7张
销售柜台:1号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余6张
销售柜台:1号窗口有票源。
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口售出一张票,剩余5张
销售柜台:2号窗口有票源。
销售柜台:1号窗口没有获得票源!
销售柜台:2号窗口售出一张票,剩余数量为4张。
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口有票源。
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口售出一张票,剩余的3张
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余的2
销售柜台:2号窗口有票源。
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口卖了一张票,剩下一张是1。
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余票为0
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口没有获得票源!
流程结束退出代码0//如果没有拿到货源的开票,就不用等了。转到下一次购票tryLock(长时间,时间单位单位)
TryLock(long time,TimeUnit单位)可以设置为在无法获得锁的情况下等待一段时间。//第一个参数总是长整型,第二个参数是时间单位
公共类SellRunnable实现Runnable {
//有十张票。
int index=10
lock lock=new reentrant lock();
public void sell() {
尝试{
if (lock.tryLock(1000,时间单位。毫秒)){
尝试{
System.out.println(销售计数器: Thread.currentThread()。getName()
获取票源’);
if (index=1) {
索引-;
System.out.println(销售计数器: Thread.currentThread()。getName()
卖了一张票,剩下的:‘指数);
}否则{
System.out.println(销售计数器: Thread.currentThread())。
GetName()买的时候没有000的票));
}
尝试{
线程.睡眠(2000年);//人为加入购票时间
} catch (InterruptedException e) {
e . printstacktrace();
}
}最后{
lock . unlock();
}
}否则{
System.out.println(销售计数器: Thread.currentThread()。getName()
没有获得票源! );
}
} catch (InterruptedException e) {
e . printstacktrace();
}
}
@覆盖
公共无效运行(){
while(索引0) {
尝试{
thread . sleep(500);//否则执行太快看不到效果。
} catch (InterruptedException e) {
e . printstacktrace();
}
sell();
}
}
}执行结果:
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余9张
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口有票源。
销售柜台:2号窗口卖了一张票,剩余8张。
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口没有获得票源!
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余数量为7张。
销售柜台:1号窗口没有获得票源!
销售柜台:2号窗口没有获得票源!
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余6张
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余5张
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口没有获得票源!
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余数量为4张。
销售柜台:1号窗口没有获得票源!
销售柜台:2号窗口没有获得票源!
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余的3张
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余的票是2
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口没有获得票源!
销售柜台:3号窗口有票源。
销售柜台:3号窗口卖了一张票,剩下一张是1。
销售柜台:1号窗口没有获得票源!
销售柜台:2号窗口没有获得票源!
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余票为0
销售柜台:2号窗口没有获得票源!
销售柜台:3号窗口没有获得票源!
流程结束退出代码0 //当购票时间约为等待时间时,无票源的窗口不购票,进入下一个购票机会会缩短购票时间:
尝试{
thread . sleep(500);//人为加入购票时间
} catch (InterruptedException e) {
e . printstacktrace();
}执行结果:
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余9张
销售柜台:2号窗口有票源。
销售柜台:2号窗口卖了一张票,剩余8张。
销售柜台:3号窗口没有获得票源!
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余7张
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余6张
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余5张
销售柜台:3号窗口没有获得票源!
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余数量为4张。
销售柜台:3号窗口有票源。
销售柜台:一张票在3号窗口售出,剩下的是3张
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余的2
销售柜台:2号窗口有票源。
销售柜台:2号窗口卖了一张票,剩下一张是1。
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余:0
销售柜台:1号窗口有票源。
销售柜台:1000号窗口没有票。
销售柜台:2号窗口有票源。
销售柜台:2000号窗口没有票。
过程结束,退出代码为0 //如果在等待时间内获得票源,将售出车票。
当lockinterrupt()通过这个方法获取一个锁时,如果这个锁正在被其他线程持有,那么它将进入等待状态,但是这个等待过程是可以被中断的。可以通过调用Thread对象的interrupt方法来中断等待,中断时抛出InterruptedException,需要捕捉或声明抛出。
公共类ThreadTest {
公共静态void main(String[] args) {
sell runnable sell runnable=new sell runnable();
ThreadThread1=新线程(sellrunnable, window 1 );
ThreadThread2=新线程(sellrunnable, window 2 );
ThreadThread3=新线程(sellrunnable, window 3 );
thread 1 . start();
尝试{
thread . sleep(500);//确保1号窗口首先获得锁
} catch (InterruptedException e) {
e . printstacktrace();
}
thread 2 . start();
thread 3 . start();
尝试{
线程.睡眠(2000年);//等待两秒后,中断windows 2和3的等待。
} catch (InterruptedException e) {
e . printstacktrace();
}
thread2.interrupt()。
thread3.interrupt()。
}
}
SellRunnable中的长等待时间:
尝试{
thread . sleep(5000);//人为加入购票时间
} catch (InterruptedException e) {
e . printstacktrace();
}执行结果:
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余9张
销售柜台:3号窗口中断//这个地方中断。
销售柜台:2号窗口中断//这个地方中断。
销售柜台:2号窗口有票源。
销售柜台:2号窗口卖了一张票,剩余8张。
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余数量为7张。
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余6张
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余5张
销售柜台:3号窗口有票源。
销售柜台:3号窗口售出一张票,剩余数量为4张。
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余的3张
销售柜台:2号窗口有票源。
销售柜台:2号窗口售出一张票,剩余的票是2
销售柜台:3号窗口有票源。
销售柜台:3号窗口卖了一张票,剩下一张是1。
销售柜台:1号窗口有票源。
销售柜台:1号窗口售出一张票,剩余票为0
销售柜台:2号窗口有票源。
销售柜台:2000号窗口没有票。
销售柜台:3号窗口有票源。
销售柜台:3000号窗口没有票。
过程结束,退出代码为0synchronized和Lock对比
通过上面的代码,我们可以看到Lock和synchronized之间的几个联系和区别:
两个都是重入锁。
重入锁是指在一个线程获得一个对象的锁后,该线程可以再次获得该对象的锁而不被阻塞。比如同一个类中的多个方法(或者一个方法的递归调用)被synchronized修饰或者被Lock加持后,同一个线程在调用这两个方法时可以获得对象的锁而不会被阻塞。
不可重入锁的例子:
公共类锁{
私有布尔值isLocked=false
公共void锁(){
while(isLocked){
wait();
}
isLocked=true
}
公共void解锁(){
isLocked=false
notify();
}
}
//用法:
公共类测试{
Lock Lock=new Lock();
公共void test1(){
lock . lock();
test2();
lock . unlock();
}
public void test2(){
lock . lock();
.
lock . unlock();
}
}当}Test类调用test1方法,执行lock.lock()后调用test2时,会一直等待,变成死锁。
重入锁的设计原则:
公共类锁{
私有布尔值isLocked=false
私有线程lockedThread=null
int locked count=0;
公共void锁(){
thread thread=thread . current thread();
while(isLocked线程!=lockedThread){
wait();
}
isLocked=true
lockedCount
lockedThread=thread
}
公共void解锁(){
thread thread=thread . current thread();
if(thread==lockedThread){
locked count-;
if(lockedCount==0){
isLocked=false
lockedThread=null
notify();
}
}
}
}这样调用Test类的Test1方法后,test2方法也可以成功执行。
Synchronized基本上是通过计数器的方式实现可重入的。
Lock是可中断锁,synchronized是不能中断的。
当A线程B执行被锁对象的代码,发现A线程已经持有锁,那么B线程会等待,但是synchronized不能中断等待过程,lock可以通过lockInterruptibly方法抛出异常来中断等待,处理其他事情。
Lock可以创建公平锁,synchronized是不公平锁。
公平锁意味着锁是按照请求的顺序获取的,但是不公平锁不能保证线程获取锁的顺序。
Lock可以知道锁是否被获取,synchronized则不能。
当异常发生或操作完成时,Synchronized将自动释放线程持有的锁。锁需要主动解锁,否则会被锁住;
当synchronized被阻塞时,其他线程不能获得锁,但lock可以(这也是锁设计的目的之一)。
读写锁
当多个线程写入同一个文件时,会产生冲突,所以需要锁定它。但是,当读取同一个文件时,使用上述方法会降低效率。因此,基于这种情况,创建了读写锁接口:
公共接口读写锁{
/**
*返回用于读取的锁。
*
* @返回用于读取的锁。
*/
lock read lock();//读取锁
/**
*返回用于写入的锁。
*
* @返回用于写入的锁。
*/
lock writeLock();//写锁
}这个接口的实现类是ReentrantReadWriteLock,其源代码如下:
公共类ReentrantReadWriteLock实现读写锁,Serializable {
private static final long serialVersionUID=-6992448646407690164 l;
私有final ReentrantReadWriteLock。ReadLock readerLock
私有final ReentrantReadWriteLock。WriteLock writerLock
.
publicreentrantreadwritelock . write lock writelock(){//Get writelock
返回this.writerLock
}
publicreentrantreadwritelock . read lock read lock(){//Get read lock
返回this.readerLock
}
.
}使用与Lock相同的方法。使用write时,调用writeLock()方法获得锁,使用read时,调用readLock()方法获得锁。需要注意的知识点如下:
线程A占用写锁,线程B在申请读写时需要等待。
线程A占用读锁,线程B在申请写操作时需要等待。
线程A占用读锁,线程B在获取读操作时可以获取读锁。
总结
如果需要提高效率,建议使用Lock。如果效率不高,那么synchronized满足使用条件,业务逻辑编写简单,不需要手动解锁。
PHP中文网站,有很多免费的JAVA入门教程,欢迎学习!即java线程同步的细节是什么,更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。