Java多线程之( 线程 )第十七天(--线程安全--同步代码块( 锁 )--线程锁死--线程中断( interrupt( ))-- 同步状态( volatile )--)

1. 火车上买票产生的问题;

       1 : 会产生重复买票  2. 卖票卖到最后可能是负数;

 产生问题的原因: CPU执行线程是随机的, 可能会在任何位置停止,当两个或者以上的进程同时操作共享数据时候产生;

 解决方法: 保证同一时间只有一个线程操作共享数据;

使用工具1 : 同步代码块(  也叫锁 )  synchronzed(  ){  } ; 将共有数据写入大括号, 并且添加任意对象锁,可以用this锁;

使用工具2 :同步方法: 在方法名上,添加synchronized 锁;

 使用工具3: JDK1.5出现的Lock类  1. 接口实现类中声明锁;private Reentreantlock lock = new Reentreantlock( ) ;

                                               2. 在run()方法中调用 lock( )方法;  3. 使用Try {  操作共享的代码 } finally{ 释放锁 }

   同步代码块执行原理: 当程序进入同步代码块的时候,先看看有没有锁,如果有锁,就带着锁进入到同步代码块中, 执行完毕以后

   出代码块,同时交出锁, 没有锁,就在同步代码块前面等待;

1.  卖票问题的联系;

//采用接口实现线程方式,实现资源共享
 class TicketRunnable implements Runnable{
    //声明50张票;
  private int ticket = 50;
//可以声明一下对象锁;
 private Object object= new Object;
 //重写run()方法;
 @Override
  public void run () {
       while(true){
         synchronized(this){
            if(ticket>0)           
                System.out.println(Thread.currentThread().getName() + "-----" + ticket);
                 ticket--;
          }else{
              //票卖光了
               break;
            }

      }
       //让出CPU的执行资源;
          Thread.yield();
   }

}
//给main 方法进行测试;
public static void main(String[] args) {
            //创建三个线程,
		 TicketRunnable t = new TicketRunnable();
		  Thread t1 = new Thread(t);
		  Thread t2 = new Thread(t);
		  Thread t3 = new Thread(t);
		  //开启线程
		  t1.start();
		  t2.start();
		  t3.start();
	}

2 . 封装一个同步方法; 我们需要一个返回值;

class TicketRunnable2 implements Runnable {
	// 声明50个票
	private int ticket = 100;
	//生产锁,保证同一把;------------------------------------(第一步)
	private ReentrantLock lock = new ReentrantLock();

	@Override
	// 买票,票--
	public void run() {
		while (true) {
			// 同步锁
			// 获取锁; ------------------------------------(第二步)
			lock.lock();
			try { //try finally----------------------------(第三步)
				// 判断卖完没有
				if (ticket > 0) {
					// 为了让测试结果更明显加个休眠;
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					// 卖
					System.out.println(Thread.currentThread().getName() + "-----" + ticket);
					ticket--;
				} else {
					// 卖光了
					break;
				}
			} finally {
				lock.unlock();
			}

			// 让出cpu的执行资源(增加让出的概率)
			Thread.yield();

		}
	}

//同步方法
public synchronized static boolean fun() {

		// 判断卖完没有
		if (ticket > 0) {
			// 为了让测试结果更明显加个休眠;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			// 卖
			System.out.println(Thread.currentThread().getName() + "-----" + ticket);
			ticket--;
			// 返回值
			return false;
		} else {
			// 卖光了
			return true;
		}

	}

3. 利用 Lock类加锁

// 采用接口实现线程方式;
class TicketRunnable2 implements Runnable {
	// 声明50个票
	private int ticket = 100;
	//生产锁,保证同一把;------------------------------------(第一步)
	private ReentrantLock lock = new ReentrantLock();
         public void run() {
		while (true) {
			// 同步锁
			// 获取锁; ------------------------------------(第二步)
			lock.lock();
			try { //try finally----------------------------(第三步)
				// 判断卖完没有
				if (ticket > 0) {
					// 为了让测试结果更明显加个休眠;
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					// 卖
					System.out.println(Thread.currentThread().getName() + "-----" + ticket);
					ticket--;
				} else {
					// 卖光了
					break;
				}
			} finally {
				lock.unlock();
			}

			// 让出cpu的执行资源(增加让出的概率)
			Thread.yield();

		}
	}

 4. 线程死锁

         前提: 1. 必须同步代码块( 锁 )的嵌套;  2 . 锁对象唯一,使用的是同一把锁;  嵌套的锁保证唯一;

测试死锁

class LockB{
   // 保证锁唯一, 不让外界创建,构造方法私有化
    private LockB{
   //定义一个常量;用final修饰,定义成静态的(只能用类名去调用),外界无法调用;
 public static final LockB lOCK_B = new LockB();

 }
}

//定义A锁;
class LockA {
	// 为了唯一不然外人创建自己创建;构造方法私有化
	private LockA() {

	}

	// 定义一个常量;外界没法访问到.(对象点成员方法); 所以定义成静态的,
	public static final LockA lOCK_A = new LockA();

}

   2.创建多线程

class DieLockRunnable implements Runnable {
	
	//声明一个标记;可以让两种结果重复执行;
	private boolean isflag=true;
	//第一次 先进A锁再进B锁;
	//第二次 先进B锁再进A锁;
	
	@Override
	public void run() {
		// 死循环,为了增加死锁的几率;
		while (true) {
			if (isflag) {
				// A-->B
				synchronized (LockA.lOCK_A) {
					System.out.println("我是if的LOCK_A锁");
					synchronized (LockB.lOCK_B) {
						System.out.println("我是if的LOCK_B锁");
					}
				}

			} else {
				// B-->A
				synchronized (LockB.lOCK_B) {
					System.out.println("我是if的LOCK_B锁");
					synchronized (LockA.lOCK_A) {
						System.out.println("我是if的LOCK_A锁");
					}
				}
			}
			// 修改标记
			isflag = !isflag;
		}
	}
}

   3 创建测试类

public static void main(String[] args) {
             //测试
		DieLockRunnable runnable = new DieLockRunnable() ;
		Thread t1= new Thread(runnable);
		Thread t2 = new Thread(runnable);
		t1.start();
		t2.start();
	}

5. 线程中断

    如何让线程停止:  stop(); 不推荐使用;

     interrupt() 方法中断线程; 

               1. 调用interrupt方法的时候,线程中如果有wait() sleep()方法的时候, 这时会抛出一个异常,InterruptException异常;

            并且清除中断状态; 这时是不会中断线程的;

              2. 调用interrupt方法的时候,如果没有上述方法, 这时会设置改变中断状态的值;( true ---flase--)

                测试 循环条件用 while( !Thread.interrupted){  } 初始值是true; 后来变成了false ;

最终我们得出的结论是: 用标记中断线程;  1. 接口实现类中 public boolean isflag = false;  2 ,在run 里面while( !isflag ){}

     3. 在主程序里面,改变  isflag的状态,这样就可以直接中断线程;

public class Demo06线程如何停止 {
	public static void main(String[] args) {
		InterruptRunnable interruptRunnable = new InterruptRunnable();
		Thread t1 = new Thread(interruptRunnable);
		t1.start();
		// 给子线几秒的执行时间然后再中断线程;
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// 中断线程这里就改变了  Thread .interrupted() 的状态, 从true,变成了false;

		t1.interrupt();
		
		//标记中断线程
	   // interruptRunnable.isFlag=true;
	    
	    
		System.out.println("中断线程");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("主线程结束");
	}
}

class InterruptRunnable implements Runnable {
	
	//声明 一个标识用来停止变成;
	public boolean isFlag = false;
	public void run() {
		//直接使用中断的状态来中断线程;可以用类去调静态的;
		while (!Thread.interrupted()) {
			
			   try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			// 休眠1秒,利用循环完成秒间隔
			//long time = System.currentTimeMillis();
			//while (System.currentTimeMillis() - time < 1000) {
                            
			//}
		

			System.out.println(Thread.currentThread().getName()+"...run");
		}
	}
}

6 测试: 子线程更改中断线程的标记; 主线程是否能接收到标记改变吗;

         结论: 是肯定的   代码如下;             

class VolRunnable implements Runnable{
	// 当子线程修改的状态不能及时同步到主线程中, (让其他线程及时收到这个信息)
	//这时可以使用volatile让线程中的这个状态信息,可以及时得到修改;
	//标记
	public volatile  boolean isflag = false;
	 //声明一个变量 记录什么时候更改状态
	private int  num= 0;
	  

	@Override
	public void run() {
		while (!isflag) {
			num++;
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (num>5) {
				 //更改中断状态
				 isflag = true;
			}
			System.out.println(Thread.currentThread().getName()+"..--run");
		}
	}

}
 public static void main(String[] args) {
			    VolRunnable volRunnable = new VolRunnable();
			    Thread t1 = new Thread(volRunnable);
			    t1.start();
			    
			    //利用中断状态卡住主线程 开始时候一直为true; 后来子线程
			    //在循环过程中让 isflag的状态发生改变了;从而影响到到了主线程;
			    while (!volRunnable.isflag) {
					   
				}
			    System.out.println("主线程结束");
		}

打印结果

Thread-0..--run
Thread-0..--run
Thread-0..--run
Thread-0..--run
Thread-0..--run
Thread-0..--run
主线程结束



猜你喜欢

转载自blog.csdn.net/a18755425397/article/details/80586856