Java之线程并发同步——synchronized方法总结
我们知道,多线程存在着数据安全问题。在并发线程中,一个对象同时可以被多个线程访问,修改等。比如购买火车票的例子,若只剩下最后一张火车票,但此时同时有多个线程正在访问该数据,那么如果不加约束,那么这些线程都将成功购得同一张火车票,最后导致系统中火车票剩余数量变为负数!
以下是一个不加约束的示范:
//线程并发即多个线程操作同一个对象
//买火车票的例子
public class TestThread3 implements Runnable{
private int ticket=1;//这是第一张票
public void run() {//重写run方法
while (true){
if(ticket>10) //当取走10张票后结束此线程
{break;}
try {
Thread.sleep(100);//此处增加短暂的阻塞,使问题更容易被发现。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticket++ +"票");
}
}
public static void main(String[] args) {
TestThread3 test=new TestThread3();
//以下三个线程准备就绪,由CPU随机调度
new Thread(test,"小明").start();
new Thread(test,"小张").start();
new Thread(test,"小李").start();
}
}
以下是运行的结果(由于在这CPU调度调度是随机的,所以每次结果都有不同,在此我特意选了一个存在问题的结果展示)
从上面的结果我们发现了问题,第2张票同时被两个人取到。这里的原因就在于,每个线程都是在自身的工作内存中进行交互,这里小张和小明都看到了第2张票,于是都将第2张信息拷入自身内存进行处理,从而都获得了第2张票。所以要解决这个数据安全问题,我们需要对线程进行排队(队列+锁)处理,即实现线程的同步!
接下来,介绍同步。
首先是方法的同步(同步方法,即synchronized方法)。
简单来说,synchronized方法就是控制对“对象”的访问,每一个对象都对应着一把锁,而每一个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞。而synchronized方法一旦执行,就独占该锁,使得其他需要获取该锁线程阻塞,直到该方法返回后才会释放其所占的锁。而后其他线程可以重新抢夺该锁,继续执行。
为了更好示范同步,这里我将之前的代码更改:
public class BuyTicket{
public static void main(String[] args) {
Buy thread =new Buy();
new Thread(thread,"小张").start();
new Thread(thread,"小李").start();
new Thread(thread,"小明").start();
}
}
class Buy implements Runnable{
private int ticket=1;
boolean flag=true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if (ticket>10) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到第"+ticket++ +"票");
}
}
面的代码仅仅是对之前进行了修改,但本质不变,其仍存在数据安全问题。
但是我们在这里可以使用synchronized方法解决这个问题,synchronized方法使用非常简单,以下是使用synchronized方法后的代码:
public class BuyTicket{
public static void main(String[] args) {
Buy thread =new Buy();
new Thread(thread,"小张").start();
new Thread(thread,"小李").start();
new Thread(thread,"小明").start();
}
}
class Buy implements Runnable{
private int ticket=1;
boolean flag=true;
@Override
public void run() {
while(flag) {
try {
Thread.sleep(100);
buy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//同步方法,锁的是this对象
private synchronized void buy() throws InterruptedException {
if (ticket>10) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到第"+ticket++ +"票");
}
}
执行结果如下图,数据安全问题被顺利解决:
最后synchronized方法总结一下:
synchronized 方法其实就是线程在使用该方法操作对象时,会获取该对象的锁,将其锁住,而其他线程同样使用synchronized 方法的时候因为无法获取该锁(一个对象只有一个锁),因此只能陷入阻塞状态,直到第一个获取该锁的synchronized 方法释放该锁!
不过synchronized方法只能锁住一个固定的对象(this对象),因此当我们需要锁住其他对象的时候,我们需要利用到同步块。
————————————————
版权声明:本文为「小白说java」的原创文章