java 常用多线程同步_Java多线程之线程同步

多线程共享数据(多个线程共同访问相同的数据),需要进行数据同步,保证同一数据、同一时刻只能被一个线程访问。

使用同步是为了防止多个线程同一时刻对同一数据进行读写,如果对同一数据数据都只进行读操作、不进行修改,则不必使用同步。

以售票为例   不使用同步

public class SaleTicketThread extendsThread {private static int ticket = 100; //票数,static

@Overridepublic voidrun() {while (ticket > 0) {

System.out.println("售出第" + (100 - ticket + 1) + "个座位");

ticket--;

}

}

}

public classSaleTicket {public static voidmain(String[] args) {

SaleTicketThread thread1= newSaleTicketThread();

thread1.start();

SaleTicketThread thread2= newSaleTicketThread();

thread2.start();

SaleTicketThread thread3= newSaleTicketThread();

thread3.start();

}

}

启动三个线程来售票,这3个线程都要访问同一个数据 SaleTicketThread.ticket  剩余票数

47f72c288038eb781fd399ad7470a019.png

很容易出问题

三种常见的同步方式

同步方法   默认用当前对象(this)或者当前类的class对象作为锁,会给整个方法都加锁

同步代码块   可以选择以什么作为锁,可以只同步会发生同步问题的部分代码而不是整个方法,比同步方法更加细粒度

ReentrantLock  以ReentrantLock对象作为锁,可以只给部分代码加锁,比同步方法更加细粒度

1、同步方法

public class SaleTicketThread extendsThread {private static int ticket = 100; //票数,static

protected static synchronized void saleTicket(){ //用synchronized修饰方法

while (ticket > 0) {

System.out.println("售出第" + (100 - ticket + 1) + "个座位");

ticket--;

}

}

@Overridepublic voidrun() {

saleTicket();

}

}

如果方法没有使用static修饰,是实例方法,默认以this(当前对象)作为锁,如果此线程类有多个实例(多条线程),这些线程使用的锁不是同一个对象,起不到同步的作用。

如果方法是静态方法,默认以当前类的class对象作为锁,如果此线程类有多个实例(多条线程),这些线程使用的锁是同一个对象,实现了同步,显然应该使用static。

同步方法有一个很大的问题:只作用于当前线程类,比如查询、售票2个线程类都要使用剩余票数这个共享数据,都使用同步方法,锁对象不同,这2个线程类之间并没有同步。

所以同步方法一般用于此共享数据只作用于一个线程类的多个实例。

锁的范围是固定(整个方法),不能修改,不灵活;使用情况还有限制,有点鸡肋。

相比之下,同步代码块、ReentrantLock的使用不受限制,强大多了。

2、同步代码块

public class SaleTicketThread extendsThread {private static Integer ticket = 100; //票数,static

@Overridepublic voidrun() {synchronized (ticket){ //锁要是Object类型

while (ticket > 0) {

System.out.println("售出第" + (100 - ticket + 1) + "个座位");

ticket--;

}

}

}

}

同步代码块可以自己决定要使用的锁对象,可以直接把共享数据作为锁,也可以新建一个Object对象作为锁。

要注意2点:

1、锁要是Object类型,int、float、char等基本类型没有继承Object,不能作为锁,要使用对应的包装类型。String继承了Object,可以作为锁。

2、访问这一共享数据的线程,使用的锁对象要相同(对象地址要相同)。

3、ReentrantLock

public class SaleTicketThread extendsThread {private static Integer ticket = 100; //票数,static

private final static ReentrantLock lock=new ReentrantLock(); //锁对象,final static修饰,

@Overridepublic voidrun() {

lock.lock();//加锁

while (ticket > 0) {

System.out.println("售出第" + (100 - ticket + 1) + "个座位");

ticket--;

}

lock.unlock();//释放锁

}

}

以ReentrantLock对象作为锁,注意所有要使用此共享数据的线程使用的应该是同一个ReentrantLock对象(要是同一个锁对象)。

其实和同步代码块差不多,lock()标志同步代码块开始,unlock()标志同步代码块结束。

可能发生异常的情况:

public class XxxThread extendsThread {

private final static ReentrantLock lock=new ReentrantLock(); //锁对象,final static修饰

@Overridepublic voidrun() {//...

lock.lock();//加锁

try{//......//放在try中

}catch( ){//.....

}finally{

lock.unlock();//在finally中释放锁,防止发生异常后锁没有释放,其它线程一直等待

}//.....

}

}

说明

可以把共享数据、锁可以放在某个线程中作为静态变量,

如果要在不同的线程类中使用,比如在查询、售票2个线程类中都要使用剩余票数这个成员变量,那就声明为public static,暴露出来;

如果只在一个线程类中使用(此线程类的多个实例共享此数据),设置成private static即可。

也可以把共享数据、锁放在父线程(开启它们的线程)中,在线程类的构造函数中传入共享数据、锁对象。

总之,要所有访问这个共享数据的线程都可以访问到共享数据、锁。

访问这一共享数据的所有线程,使用的锁对象要相同(对象地址要相同)。

线程死锁

2条线程都在等待对方的锁,会造成这2条线程的死锁。比如

thread1:等待thread2释放x锁,获取x锁才能继续执行

thread2:等待thread1释放y锁,获取到y锁才能继续执行

都在等待对方释放锁,一直等待下去,2条线程都无法继续往下执行,即这2条线程发生死锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值