简述线程安全问题及同步机制

 1.介绍

 线程安全问题是在多线程编程中常见的一类问题,指的是当多线程同时访问共享资源时,可能出现数据竞争、死锁等问题,导致程序运行出错或异常。

 2.常见问题

(1)数据竞争:当多个线程同时访问同一个资源时,会导致数据不一致,比如在多线程环境下,多条线程同时修改同一个变量,可能导致该变量值不可测。

(2)死锁:多线程间同时等待对方释放资源,导致程序无法继续运行,进入死锁状态。

(3)非原子操作:某些操作需要多条指令才能完成,如果多条线程同时执行这些操作,就会出现部分执行的情况,导致程序结果不正确。

(4)内存泄漏:由于程序设计不当,可能出现内存无法回收的情况,导致内存泄漏。

3.解决方法:使用线程同步机制

(1)同步代码块:

synchronized(监视器(对象锁)){
        //需要同步的代码...

}

(2)同步方法:

public synchronized void 方法名(){
        //需要同步的代码...

}

(3)注意:

a.监视器(对象锁)可以是任何一个对象,但在并发线程中使用的监视器(对象锁)必须是同一个对象。

b.可以这样理解:每条线程都有一扇访问共享资源的门,但开门的钥匙也就是监视器(对象锁)只有一把,这样就保证了同一时间只有一条线程能访问共享资源。

c.同步方法中synchronized的监视器固定为当前类的对象 this 。也因此,若想在线程类中使用同步方法,只能用implements Runnable,而不能extends Thread:因为implements Runnable后若想使用多线程,只需创建一个该线程类对象和多个Thread类对象,这样就保证了该线程类的this只有一个;但extends Thread后若想使用多线程,需创建多个该线程类对象,此时该线程类中的this就有多个,不符合监视器的要求。

d.同步机制虽然避免了线程安全的问题,但也降低了线程并发的效率。

4.实践

我们以一个售票系统为例子理解线程安全和同步机制。

package ThreadSafe;

public class TicketSystem {
	//售票系统
	public int tickets = 100;//总票数是100
}
package ThreadSafe;
	//买票线程
public class TicketThread extends Thread{
	public TicketSystem ts;//售票系统
	//初始化售票系统
	public TicketThread(TicketSystem ts) {
		this.ts = ts;
	}
	public void run() {
		for(int i=0;i<50;i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+"买到票:"+ts.tickets);//输出当前线程所买到的票序
			ts.tickets--;//每售出一张票,总票数-1
		}
	}
}
package ThreadSafe;

public class Main {
	public static void main(String[]args) {
		TicketSystem ts = new TicketSystem();//售票系统
		TicketThread t1 = new TicketThread(ts);//购票线程
		t1.setName("线程A");
		t1.start();
		TicketThread t2 = new TicketThread(ts);
		t2.setName("线程B");
		t2.start();
	}
}

在上述代码中两线程同时买票,对tickets进行数据更改,在未使用同步机制的·情况下来看看运行结果:

可以看到结果中出现了两线程买到同一张票,且出现跳序的情况 。

出现上述情况的原因可能是:

线程A在第一次执行ts.tickets--的这一步操作时,只完成了tickets-1这一运算操作,却还未更新tickets的值,就轮到线程B执行,导致线程B拿到的tickets仍为100,这便出现了两线程同时买到了第100张票的情况。然后线程B完成了ts.tickets--这一操作,此时tickets=99,再次轮到线程A,线程A在tickets=99的基础上更新了数据tickets = tickets-1,导致tickets直接变成98。

接下来我们先采用同步代码块对上述代码进行更改,由于在线程类中,售票系统ts只创建了一次,所以我们可以用ts作为synchronized的监视器。

public void run() {
		for(int i=0;i<50;i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
            //同步代码块
			synchronized(ts) {
				System.out.println(Thread.currentThread().getName()+"买到    票:"+ts.tickets);//输出当前线程所买到的票序
				ts.tickets--;//每售出一张票,总票数-1
			}
		}	
	}

输出结果:

 成功解决问题。

接下来我们采用同步方法对上述代码进行修改,需将线程类改成implements Runnable

package ThreadSafe;
	//买票线程
public class TicketThread implements Runnable{
	public TicketSystem ts;//售票系统
	//初始化售票系统
	public TicketThread(TicketSystem ts) {
		this.ts = ts;
	}
	public void run() {
		for(int i=0;i<50;i++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			print();
		}	
	}
	//同步方法
	public synchronized void print() {
		System.out.println(Thread.currentThread().getName()+"买到票:"+ts.tickets);//输出当前线程所买到的票序
		ts.tickets--;//每售出一张票,总票数-1
	}
}
package ThreadSafe;

public class Main {
	public static void main(String[]args) {
		TicketSystem ts = new TicketSystem();//售票系统
		TicketThread t = new TicketThread(ts);//购票线程
		Thread t1 = new Thread(t);
		t1.setName("线程A");
		t1.start();
		Thread t2 = new Thread(t);
		t2.setName("线程B");
		t2.start();
	}
}

                                                                输出结果:

 成功解决问题。

猜你喜欢

转载自blog.csdn.net/m0_73249076/article/details/129961944