如果线程是通过 Runnable 接口实现的,就意味着类中的属性将被多个线程共享。 这样会出现一种问题,就是资源被多消耗问题,我们以售票为例子,代码如下:
package com.haicoder.net.thread;
public class TicketThread implements Runnable {
private int ticket = 5; //共有 5 张门票
@Override
public void run() {
for(int i=0;i<100;i++){
if(ticket > 0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终还剩:ticket = " + ticket -- );
}
}
}
public static void main(String[] args) {
System.out.println("嗨客网(www.haicoder.net)");
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
Thread thread2 = new Thread(ticketThread);
Thread thread3 = new Thread(ticketThread);
thread1.start();
thread2.start();
thread3.start();
}
}
我们看到会发生票的剩余数量为 -1 的情况。这是因为多个线程可以对 ticket 变量同时进行 --
操作,由于 --
操作不是原子性的,所以导致了其变成了 -1 的情况。
从图上可以看出,没有线程同步的时候,同一个资源,多个线程可以同一时间访问,而加了线程同步的资源,在每个时间只能够有一个线程访问,其他的线程只能够等待着,等当前获取到资源的线程执行完之后等待着的线程会进行竞争,然后获取资源进行相应的操作。
线程同步我们可以用到 synchronize
关键字。它可以加在变量,方法和代码块上面。
我们可以在属性和方法上面加 synchronize 同步来限制资源的访问。被 synchronize 包围的代码块称为同步代码块。
package com.haicoder.net.thread;
public class TicketThread implements Runnable {
private int ticket = 5; //共有 5 张门票
@Override
public void run() {
for (int i = 0; i < 100; i++) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终还剩:ticket = " + ticket--);
}
}
}
}
public static void main(String[] args) {
System.out.println("嗨客网(www.haicoder.net)");
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
Thread thread2 = new Thread(ticketThread);
Thread thread3 = new Thread(ticketThread);
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果如下:
可以发现,ticket 到打印量就只有 5 个,而且是有序的,我们可以想象成在每次操作 ticket-- 之前先上来一把锁,一个线程一个线程访问。
package com.haicoder.net.thread;
public class TicketThread implements Runnable {
private int ticket = 5; //共有 5 张门票
@Override
public void run() {
for (int i = 0; i < 100; i++) {
this.sale();
}
}
public synchronized void sale() {
if (ticket > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("最终还剩:ticket = " + ticket--);
}
}
public static void main(String[] args) {
System.out.println("嗨客网(www.haicoder.net)");
TicketThread ticketThread = new TicketThread();
Thread thread1 = new Thread(ticketThread);
Thread thread2 = new Thread(ticketThread);
Thread thread3 = new Thread(ticketThread);
thread1.start();
thread2.start();
thread3.start();
}
}
运行效果如下:
在方法上面加锁,也可以保证资源被访问的顺序性。
Java 中多线程同时访问一个资源的时候,容易引起问题,资源被过度消耗,我们可以考虑现实场景中给资源加一个锁,一个线程获取,就不让另外的线程获取,在外面排队,等当前获取到资源的线程处理完之后再让在外面排队的线程进行竞争获取资源处理相应的操作。