Java多线程-synchronized与ReentrantLock
java中多线程非常重要,尤其在高并发的时候,多线程和线程之间的通信尤为重要。下面用一个抢车票的例子来演示多线程。
场景
现有余票100张,多个人(多个线程)来抢票。
创建多线程
库存100张票
public class Tickets {
// 库存
private static int num = 100;
// 买票的方法
public static int buyTicket(){
if (Tickets.num <= 0){
System.out.println("无余票");
return 0;
}
Tickets.num--;
System.out.println("还剩" + Tickets.num);
return Tickets.num;
}
}
买票的操作,该类要实现Runnable接口,操作放在run()方法中。也可以继承Thread类,也可以在Runnable中使用lambda表达式加锁后,用lambda表达式试试。
public class BuyTicket implements Runnable{
public String name = "";
public BuyTicket(String name) {
this.name = name;
}
public void buy() {
int buyTicket = Tickets.buyTicket();
System.out.println(this.name + "买到了第" + buyTicket + "张票");
}
@Override
public void run() {
buy();
}
}
测试
public class TicketTest{
public static final int MAX_THREAD_NUM = 100;
public static void main(String[] args) {
for (int i = 0; i < TicketTest.MAX_THREAD_NUM; i++) {
BuyTicket buyTicket = new BuyTicket("客户" + i);
new Thread(buyTicket).start();
}
}
}
多线程已经创建完毕,这样就OK了吗?运行看看效果
客户36买到了第40张票
客户87买到了第41张票
客户86买到了第41张票
客户89买到了第42张票
???黑人问号
一张票被重复购买了这么多次。
必须要采取一些措施才能不发生这种情况了。
同步
Java SE 5
之前使用synchronized
关键字同步,Java SE 5
之后引入ReentrantLock
对象。
ReentrantLock
当需要手动的加锁,释放锁的时候,使用该方式。
通过ReentrantLock
对象,可以加锁和释放锁。
Lock myLock = new ReentrantLock();
myLock.lock();
try{
...
}
...
finally{
myLock.unlock();
}
条件对象
Condition xx;
myLock.lock();
xx = myLock.newCondition();
if (余票<0){
// 阻塞自己
xx.await();
}
条件对象除了await
方法,还有唤醒的方法。signal
方法在该条件的等待集合中随机的唤醒一个,signalAll
唤醒全部在该条件的等待集合中线程。
synchronized
当不需要手动加锁,释放锁的,有需要同步机制的时候,使用该关键字。
修改后:
票的库存 获取票的方法要加锁,每次只能一个线程访问。
0-110,有110个人
0-100,有99张票,有11个人买不到票
public class Tickets {
private static int num = 100;
public synchronized static int buyTicket(){
if (Tickets.num <= 0){
System.out.println("无余票");
return 0;
}
Tickets.num--;
return Tickets.num;
}
}
买票 不用实现Runnable接口,在测试类中通过lambda表达式。
public class BuyTicket{
public String name = "";
public BuyTicket() {
}
public BuyTicket(String name) {
this.name = name;
}
public void buy() {
int buyTicket = Tickets.buyTicket();
System.out.println(this.name + "买到了第" + buyTicket + "张票");
}
}
测试类
public class TicketTest{
public static final int MAX_THREAD_NUM = 110;
public static void main(String[] args) {
for (int i = 0; i < TicketTest.MAX_THREAD_NUM; i++) {
int customer = i;
Runnable r = ()->{
BuyTicket buyTicket = new BuyTicket("客户" + customer);
buyTicket.buy();
};
new Thread(r).start();
}
}
}
线程的几种状态
- New(新创建): 当用new操作符创建一个新线程时,如
new Thread(r)
,该线程还没有运行,状态是new
- Runnable(可运行): 当调用了线程的start()方法的时候,该线程的方法即是
runnable
。调用了start()方法之后,该线程可能正在运行,也可能没有在运行,要看操作系统调度的时间 - Blocked(被阻塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(被终止)
想要获取该线程的状态,可以调用getState()
方法。