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()方法。

原文地址:https://www.cnblogs.com/to-red/p/12158016.html