(java)多线程(二)线程安全问题

【线程安全】

   线程安全 -- 如果有多个线程同时运行,程序每次运行的结果和单线程运行的结果一样,且其他变量的值也和预期的一样。

   线程安全产生都是全局变量、静态变量引起的。若每个线程对对全局变量、静态变量只有读操作,而无写操作,一般来说这个全局变量是线程安全的;若有多个线程同时执行写操作,一般需考虑线程同步,否则就有可能影响线程安全

   线程同步机制能够解决上述线程安全问题    -- 1. 同步代码块    2. 同步方法(静态同步方法)

【1. 同步代码块】

/*
 * 同步代码块解决线程安全问题
 *   同步代码块公式:
 *    synchronized(任意对象){
 *        线程要操作的共享数据
 *    }
 *    任意对象又称:同步锁  、对象监视器 obj
 *       作用:保证同步安全性,没有锁的线程不能执行同步代码块,只能等
 *    原理:线程遇到同步代码块时,判断代码块的同步锁还有没有
 *           1. 若有锁,获取锁,进入同步代码块执行程序     执行完毕后 出了代码块   线程再将锁还回去
 *           2. 若没有锁,线程只能等待,不能进入同步代码块
 */
/*
 *   同步代码块解决线程安全问题
 *   同步代码块公式:
 *    synchronized(任意对象){
 *        线程要操作的共享数据 -- 可能会产生线程安全问题的代码
 *    }
 */
public class Tickets implements Runnable{
	// 定义出售票的数目
	private int tickets = 100;
	private Object obj = new Object();
	
	public void run(){
		while(true){
			// 为了保证共享数据的安全,加入同步代码块
			synchronized (obj) {
				// 对票数进行判断,大于0可以出售,进行变量--操作
				if(tickets >0){	
					try{
						Thread.sleep(100); // 此处线程休眠后,会出现线程安全问题。售票数会出现负数
					}catch(Exception ex){}
					
					System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
				}
			}	
		}
	}

}
/*
 *  多线程售票案例:多线程并发访问同一数据资源 
 *     本场电影只有100张票,使用多线程模拟窗口同时卖票 
 *     即三个线程,出售一个票资源
 */
public class ThreadDemo1 {
	public static void main(String[] args) {
		
		// 创建Runnable接口实现类对象
		Tickets t = new Tickets();
		// 创建3个Thread类对象,传递Runnable接口实现类
		Thread t0 = new Thread(t);
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t0.start();t1.start();t2.start();           // 运行结果会出现出售 第0张票   和 第 -1 张票 还有出售相同的票数  出现线程安全问题
		
	}
}

【2. 同步方法】

/*
 *   采用同步方法形式,解决线程的安全问题    好处:代码简洁
 *     步骤:
 *       1. 将线程共享数据和同步,抽取到一个方法中去
 *       2. 在方法声明上加上同步关键字   synchronized()

 *       同步方法中的对象锁,是本类引用对象this
            public synchronized void method() {
 *      	      可能会产生线程安全问题的代码
 *          }

         静态同步方法  对象锁是   本类.class (Tickets2.class)
 *          public static synchronized void method() {
 *      	      可能会产生线程安全问题的代码
 *          }
 */
public class Tickets2 implements Runnable{
	// 定义出售票的数目
	private int tickets = 100;
	public void run(){
		while(true){
			payTicket();
		}
	}
	
	public synchronized void payTicket(){
		// 对票数进行判断,大于0可以出售,进行变量--操作
		if(tickets >0){					
			try{
				Thread.sleep(100);          // 此处线程休眠后,会出现线程安全问题。售票数会出现负数
			}catch(Exception ex){}		
				 System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
		} 				
	}
		
}

【同步锁使用的弊端】

       死锁 -- 当线程任务出现多个同步时(会有多个锁),若同步中出现了同步嵌套,这时就会引发一种现象:程序出现无线等待,这种现象我们称为死锁。

【死锁问题】

// 类LockA 对象充当锁
public class LockA {
	// 私有构造方法,类外不能new
	private  LockA(){}
	// 一个静态成员变量  是自己的对象  外界可以通过类名调用静态变量,拿到这个对象
	public static final LockA locka = new LockA();
	
}
//类LockB 对象充当锁
public class LockB {
	// 私有构造方法,类外不能new
	private  LockB(){}
	// 一个静态成员变量  是自己的对象  外界可以通过类名调用静态变量,拿到这个对象
	public static final LockB lockb = new LockB();
		
}
//  Runnable 接口实现类
//    类LockA、LockB  创建对象锁
public class DeadLock1 implements Runnable {
	
	private int i = 0;
	public void run(){
		while(true){
			if(i % 2 == 0){
				// 先进A同步,再进B同步
				synchronized (LockA.locka) {
					System.out.println("if...Locka");
					synchronized (LockB.lockb) {
						System.out.println("if...Lockb");
					}
				}
			}else{
				// 先进B同步,再进A同步
				synchronized (LockB.lockb) {
					System.out.println("else...Lockb");
					synchronized (LockA.locka) {
						System.out.println("else...Locka");
					}
					
				}
			}
			i++;
		}
	}

}
/*
 *  线程的死锁代码实现:
 *     LockA、LockB类,构造方法私有了不能new,需要通过类名调用静态方法创建对象
 */
public class ThreadDemo1 {
	public static void main(String[] args) {
		//
		DeadLock1 dead = new DeadLock1();
		Thread t0 = new Thread(dead);
		Thread t1 = new Thread(dead);
		
		t0.start(); 
		t1.start();
		
		/* 
		  if...Locka
          if...Lockb
          else...Lockb
          if...Locka
                              运行后,程序进入死锁
		 */
	}

}

 

【使用Lock接口】

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/*
 *  使用Lock接口,解决线程安全问题
 *    Lock接口方法:
 *        1. lock()获取锁
 *        2. unlock()释放锁
 *    接口实现类ReentrantLock();
 */
public class TicketsLock1 implements Runnable {
	
	private int tickets = 100;
	// 创建Lock接口的实现类对象 ,声明在成员变量位置
	private Lock l = new ReentrantLock();
	@Override
	public void run() {
		while(true){
			// 调用Lock接口方法lock()获取锁
			l.lock();
			if(tickets >0){
				try{
					Thread.sleep(10);
					System.out.println(Thread.currentThread().getName()+"出售第"+tickets--);
				}catch(Exception e){
					
				}finally{
					// 调用方法unlock() 释放锁
					l.unlock();
				}
			}
		}
	}
}

【等待与唤醒机制】

           线程之间的通信:多个线程在处理同一个资源,处理的任务却不相同。而通过一定的手段使各个线程能够有效的利用资源,这种手段即 ---- 等待唤醒机制。

          wait():等待。将正执行的线程释放其执行资格和执行权,并存储到线程池中。

          notify():唤醒。唤醒线程池中被wait()的线程。一次唤醒一个,而且是任一的。

          notifyAll():唤醒全部。可以将线程池中的所有wait()线程都唤醒。

          这些方法定义在Object中, 因为这些方法使用必须要标明所属的锁,而锁又可以是任意对象,能被任意对象调用的方法一定在Object类中

/*
 *   输入线程:对Resource资源对象中的成员变量进行赋值
 *        张三   男     wang  w    进行轮流赋值 
     标记:flag为true 说明:赋值完成 ;flag为false 说明:获取值完成      
 */
public class InpputDemo1 implements Runnable {
	private Resource r ;
	public InpputDemo1(Resource r){
		this.r = r;
	}
	public void run(){
		int i =0;
		while(true){
			synchronized (r) {
				// 若flag为true,等待
				if(r.flag == true){
					try{r.wait();}catch(Exception e){}
				}
				if(i%2 == 0){
					r.name = "张三";
					r.sex = "男";	
				}else{
					r.name = "wang";
					r.sex = "w";
				}
				// 唤醒输出线程,标记改为true
				r.flag = true;
				r.notify();	
			}
			i++;
		}
	}

}
/*
 *  输出线程:对Resource资源对象中的成员变量name  sex 进行输出
 */
public class OutputDemo1 implements Runnable{
	private Resource r ;
	public OutputDemo1(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			synchronized (r) {
				// 若标记flag 为false,等待
				if(!r.flag){
					try{r.wait();}catch(Exception e){}
				}
				System.out.println(r.name+"  "+r.sex);
				// 将flag改为false,唤醒输入线程
				r.flag = false;
				r.notify();
			}
		}
	}
}
/*
 *  定义资源类Resource 
 *     成员变量name sex
 */
public class Resource {
	public String name;
	public String sex;
	// 标记:flag为true 说明:赋值完成 ;flag为false 说明:获取值完成 
	//    线程输入:需不需赋值看flag状态。若flag为true,等待;  若flag为false,进行赋值,然后将flag改为true
	//    线程输出:需不需要输出看flag状态。若flag为true,进行输出,然后将flag改为false; 若flag为false,等待
	public boolean flag = false;
	
}
/*
 *   使用等待与唤醒完成以下任务:
 *      输入线程向Resource中输入name,sex 
 *      输出线程从Resource中输出name,sex
 *      
 *      1. 若InpputDemo1发现Resource中没有数据时,开始输入, 输入完成唤醒notify() OutputDemo1来输出;若发现有数据就wait()
 *      2. 若OutputDemo1发现Resource中没有数据时,就wait();当发现有数据时,就输出,输出完成后唤醒notify() InpputDemo1来输入数据
 */
public class ThreadTestDemo1 {
	public static void main(String[] args) {
		
		Resource r = new Resource();
		//创建 Runnable接口实现类对象
		InpputDemo1 in = new InpputDemo1(r);
		OutputDemo1 out = new OutputDemo1(r);
		
		Thread tin = new Thread(in);
		Thread tout = new Thread(out);
		tin.start();
		tout.start();
		/*
		         张三  男
           wang  w
                                张三  男
           wang  w
                               张三  男
           wang  w
                               张三  男
           wang  w
		  */
	}

}

【总结】

  1. 多线程有哪些实现方案?

            a. 继承Thread类

            b. 实现Runnable接口

            c. 通过线程池,实现Callable接口

  2. 同步的实现方式

            a. 同步代码块

                 synchronized(锁对象) {

                                 可能产生线程安全问题的代码

                 }  // 同步代码块的锁对象可以是任意的对象

            b. 同步方法

                 public synchronized void method() {

                                 可能产生线程安全问题的代码

                 }  // 同步方法中的锁对象是 this

                   静态同步方法

                  public static synchronized void method() {

                                 可能产生线程安全问题的代码

                 } // 静态同步方法中的锁对象是  类名.class

   3. 启动线程的是什么方法? run() 与start() 方法的区别?

            启动线程的方法是start(),

            区别:start() 启动线程,并调用线程中的run()方法;run() 执行该线程对象要执行的任务

   4. sleep() 和 wait() 方法的区别?

            sleep()  释放cpu使用权,不释放锁,休眠时间内不能被唤醒;

            wait()   释放cpu使用权, 释放锁,在等待的时间内能唤醒。

   5. 为什么wait()  notify()  notifyAll() 等方法都定义在Object对象中?

             因为锁对象可以是任意类型的对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值