JavaSE基础(93) 线程安全问题的3种处理方式(通过线程同步)

2021年1月17日 18点热度 0条评论 来源: 郑清

出现线程安全问题我们如何处理??  ==》同步原理
    1.同步方法synchronized 修饰的方法      ex:public synchronized void test(){}
              弊端:方法中的所有代码,都只允许一个线程访问。
(有一种情况:一个方法中,有一个部分代码不会涉及到线程安全问题,可以允许多个线程同时访问 == 》即下面的2.同步代码块)
    2.同步代码块synchronized(被加锁的对象){ 代码 }
    3.锁机制Lock
        ①创建ReentrantLock对象
        ②调用lock方法:加锁
                {代码....}
        ③调用unlock方法:解锁

         注意:可把解锁的unlock方法的调用放在finally{}代码块中,保证一定能解锁

提醒:在同步的时候,其他代码都可以多个线程同时执行!只是被同步的代码不能同时执行!

ex1:  同步方法  使用synchronized 修饰方法  ==》 解决线程安全问题

/**
 * 线程安全问题
 * @author 郑清
 */
public class Demo {
	public static void main(String[] args) {
		A a = new A();
		MyThread t1 = new MyThread(a);
		MyThread t2 = new MyThread(a);
		t1.start();
		t2.start();
	}
}
class A{
	private int tickets = 20;//记录车票的数量
	/*
	 * 在方法定义时,添加一个synchronized修饰符
	 * 效果: 
 	 * 使用synchronized修饰的方法就是同步方法
	 * 	特点: 1.效率会变低
	 * 		  2.没有线程安全问题了
	 *  原理:在方法添加synchronized之后,就相等于给当前对象(当前场景是a)加了一把锁.
	 *  锁的作用:当一个线程进入该方法时,先看锁还在不在,锁在:把锁取走
	 *  在第一个线程还没有结束时,第二个线程访问该方法,先看锁还在不在?       锁不在==》等待锁回来
	 *  第一个线程执行结束,把锁还回去,第二个线程发现锁回来了,把锁取走,开始执行方法
	 */
	public synchronized void getTicket(){
		if(tickets>0){
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			/*
			 * 打印出来的结果有可能有重复的车票数量
			 * 甚至有负数,就是发生了线程安全问题
			 * 原因是:两个线程同时进入了此对象的getBean方法,读取的车票数量是另一个线程修改之前的值
			 */
			System.out.println("车票还剩: "+ (--tickets) + "张 !");
		}
	}
}
class MyThread extends Thread{
	private A a;
	public MyThread(A a) {
		this.a = a;
	}
	public void run() {
		while(true){
			a.getTicket();
		}	
	}
}

运行结果图:(如果没有使用synchronized 修饰方法 getTicket( )   ==》 可能就会出现下图2线程安全问题)

                       

ex2 :  同步代码块  synchronized(被加锁的对象){ 代码 }  ==》 解决线程安全问题

/**
 * 同步代码块 :synchronized(被加锁的对象){ 代码 }
 * @author 郑清
 */
public class Demo2 {
	public static void main(String[] args) {
		WC wc = new WC();
		
		Person person1 = new Person(wc);
		Thread t1 = new Thread(person1);
		t1.setName("张三");
		
		Person person2 = new Person(wc);
		Thread t2 = new Thread(person2);
		t2.setName("李四");
		
		t1.start();
		t2.start();
	}
}
//上厕所
class WC{
	//定义一个方法实现上厕所
	public void test(){
		try{
			System.out.println(Thread.currentThread().getName()+" : 拿纸...");
			Thread.sleep(1000);
			/*
			 * 把上厕所的代码 放在 同步代码块中
			 * 该代码块中的代码在一个时间点只能被一个线程访问,其他线程需要排队等待
			 */
			synchronized (this) {
				System.out.println(Thread.currentThread().getName()+" : 上厕所...");
				Thread.sleep(2000);
				System.out.println(Thread.currentThread().getName()+" : 出厕所");
			}
			System.out.println(Thread.currentThread().getName()+" : 上完厕所,真舒服,洗个手 ... end");
		}catch (Exception e) {
			// TODO: handle exception
		}
	}
}
class Person implements Runnable{
	private WC wc;
	public Person(WC wc) {
		super();
		this.wc = wc;
	}
	public void run() {
		wc.test();
	}
}

运行结果图: (如果不处理线程安全问题可能就会出现下图2情况:张三和李四同时在一个厕所上厕所 很恶心...哈哈)

     

ex3 : 锁机制Lock  ==》 解决线程安全问题   (这个例子在ex2上修改而成的)

/**
 * 使用Lock的步骤:
 * 1.创建Lock实现类的对象
 * 2.使用Lock对象的lock方法加锁
 * 3.使用Lock对象的unlock方法解锁
 * 注意:可把unlock方法的调用放在finally代码块中,保证一定能解锁
 * @author 郑清
 */
public class Demo3 {
	public static void main(String[] args) {
		WC wc = new WC();
		//自定义线程步骤③:创建自定义类对象
		Person person1 = new Person(wc);
		Person person2 = new Person(wc);
		//自定义线程步骤④:创建Thread类对象
		Thread t1 = new Thread(person1);
		Thread t2 = new Thread(person2);
		//给线程名称赋值
		t1.setName("张三");
		t2.setName("李四");
		//自定义线程步骤⑤:启动线程
		t1.start();
		t2.start();
	}
}
// 上厕所
class WC {
	// 1.创建Lock实现类的对象  注意:需在test()方法外执行,如果在test()方法里执行,在test()方法被调用的时候就会被创建很多ReentrantLock对象,即出现很多锁
	ReentrantLock lock = new ReentrantLock();
	// 定义一个方法实现上厕所
	public void test() {
		System.out.println(Thread.currentThread().getName() + " : 拿纸...");
		// 2.使用Lock对象的lock方法加锁
		lock.lock();
		try {
			System.out.println(Thread.currentThread().getName() + " : 上厕所...");
			System.out.println(Thread.currentThread().getName() + " : 出厕所");
			System.out.println(Thread.currentThread().getName() + " : 上完厕所,真舒服,洗个手 ... end");
		} finally {
			// 3.使用Lock对象的unlock方法解锁
			lock.unlock();
		}
	}
}
//自定义线程步骤①:创建自定义类 实现 Runnable接口
class Person implements Runnable {
	private WC wc;
	public Person(WC wc) {
		super();
		this.wc = wc;
	}
	//自定义线程步骤②:覆写run方法
	public void run() {
		wc.test();
	}
}

运行结果图:(这里注意:在lock锁的地方  比如这个例子,图2中 张三上厕所的同时,李四可以去拿纸,但是不能去厕所!!)

  

    原文作者:郑清
    原文地址: https://blog.csdn.net/qq_38225558/article/details/82154321
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。