第18章 多线程----线程同步

Java提供了线程同步的机制来防止资源访问的冲突。

1、线程安全

实际开发中,使用多线程程序的情况很多,如银行排号系统、火车站售票系统等。这种多线程的程序通常会发生问题。

以火车站售票系统为例,在代码中判断当前票数是否大于0,如果大于0则执行将该票出售给乘客功能,但当两个线程同时访问这段代码时(假如这时只剩下一张票),第一个线程将票售出,与此同时第二个线程也已经执行完成判断是否有票的操作,并得出结论票数大于0,于是它也执行售出操作,这样就会产生负数。所以在编写多线程程序时,应该考虑到线程安全问题。实质上线程安全问题来源于两个线程同时存取单一对象的数据。

例如:在项目中创建ThreadSafeTest类,该类实现了Runnable接口,主要实现模拟火车站售票系统的功能。

public class ThreadSafeTest implements Runnable {
    int num = 10; // 设置当前总票数
    
    public void run() {
        while (true) {
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("tickets" + num--);
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadSafeTest t = new ThreadSafeTest(); // 实例化类对象
        Thread tA = new Thread(t); // 以该类对象分别实例化4个线程
        Thread tB = new Thread(t);
        Thread tC = new Thread(t);
        Thread tD = new Thread(t);
        tA.start(); // 分别启动线程
        tB.start();
        tC.start();
        tD.start();
    }
}

从运行结果中看出,最后售的票为负值,这样就出现了问题。这是由于同时创建了4个线程,这4个线程执行run()方法,在num变量为1时,线程1、线程2、线程3、线程4都对num变量有存储功能,当线程1执行run()方法时,还没来得及做递减操作,就指定它调用sleep()方法进入休眠状态,这时线程2、线程3和线程4都进入了run()方法,发现num变量依然大于0,但此时线程1休眠时间已到,将num变量递减,同时线程2、线程3、线程4也都对num变量进行递减操作,从而产生了赋值。

2、线程同步机制

基本上所有解决多线程资源冲突问题的方法都是采用给定时间允许一个线程访问共享资源,这时就需要给共享资源上一道锁。这就好比一个人上洗手间时,他进入洗手间后会将门锁上,出来时再将锁打开,然后其他人才可以进入。

(1)同步块

在Java中提供了同步机制,可以有效第防止资源冲突。同步机制使用synchronized关键字。将资源放在同步块中,这个同步块被称为临界区。

synchronized(Object){
}

通常将共享资源的操作放置在synchronized定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁被释放时才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1。一个线程运行到同步块时首先检查对象的标志位,如果为0状态,表明此同步块中存在其他线程在运行。这时该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这时该对象的标识位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标识位设置为0,防止其他线程执行同步块中的代码。

例如:在本实例中,创建类ThreadSafeTest.java,在该类中修改上述中run()方法,把对num操作的代码设置在同步块中。

public class ThreadSafeTest implements Runnable {
    int num = 10;
    
    public void run() {
        while (true) {
            synchronized ("") {
                if (num > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("tickets" + --num);
                }
            }
        }
    }
    
    public static void main(String[] args) {
        ThreadSafeTest t = new ThreadSafeTest();
        Thread tA = new Thread(t);
        Thread tB = new Thread(t);
        Thread tC = new Thread(t);
        Thread tD = new Thread(t);
        tA.start();
        tB.start();
        tC.start();
        tD.start();
    }
}

(2)同步方法

同步方法就是在方法前面修饰synchronized关键字的方法,其语法如下:

synchronized  void f(){
}

当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。

 

原文地址:https://www.cnblogs.com/chamie/p/4713704.html