Java多线程3

Java多线程3

 1. 通过继承Thread类所创建的线程不能实现资源共享功能,

package com.wangxing.thread.test1;

public class MyThread implements Runnable {
	//定义票数
	private int piao=5;
	@Override
	public void run() {
		String name=Thread.currentThread().getName();
		boolean flag=true;
		while (flag) {
			if (piao>0) {
				//线程休眠一秒钟
				//模拟--收钱--》出票--》找钱
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				//票数减一
				piao=piao-1;
				System.out.println(name+"卖出一张票,还剩"+piao+"张!");
			}else{
				flag=false;
			}
		}
		
		
	}
	
}
package com.wangxing.thread.test1;

public class TestMain {
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		Thread thread=new Thread(myThread);
		Thread thread2=new Thread(myThread);
		Thread thread3=new Thread(myThread);
		
		thread.setName("窗口一");
		thread2.setName("窗口二");
		thread3.setName("窗口三");
		
		thread.start();
		thread2.start();
		thread3.start();
		
	}

}

2.通过实现Runnable接口所创建的线程可以实现资源共享功能。

package com.wangxing.thread.test1;

public class MyThread2 implements Runnable {
	//定义票数
		private  int  piao=5;
	@Override
	public void run() {
		//得到线程名称
				String name=Thread.currentThread().getName();
				boolean flag=true;
				while(flag){
					if(piao>0){
						//线程休眠1秒
						//模拟--收钱--》出票--》找钱
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						//票数减1
						piao=piao-1;
						System.out.println(name+"卖出1张票,还剩"+piao+"张!!");
					}else{
						flag=false;
					}
				}
			}
		
	}
	
package com.wangxing.thread.test1;

public class TestMain {
	public static void main(String[] args) {
		MyThread2 myThread=new MyThread2();
		Thread thread=new Thread(myThread);
		Thread thread2=new Thread(myThread);
		Thread thread3=new Thread(myThread);
		
		thread.setName("窗口一");
		thread2.setName("窗口二");
		thread3.setName("窗口三");
		
		thread.start();
		thread2.start();
		thread3.start();
		
	}

}

 通过上面的实现Runnable接口的买票程序可以实现资源共享,但是卖出会卖出剩下负数的情况

  分析:当窗口一开始卖最后一张票的时候,窗口一判断还有一张票,这时窗口一开始收钱打票,当窗口一开始收钱打票的时候,线程就切换给了窗口二,由于窗口一还有来得及对票数减1,因此窗口二判断还有一张票,这时窗口二开始收钱打票,当窗口二开始收钱打票的时候,线程就切换给了窗口三,由于窗口二还有来得及对票数减1,因此窗口三判断还有一张票,这时窗口三开始收钱打票,线程切换给了窗口一,所以窗口一输出“窗口一卖出1张票,还剩0张”,输出途中线程二就开始工作了窗口二获得了线程二的使用权,所以窗口二输出剩余票数的时候输出的是“窗口二卖出1张票,还剩0张”,之后窗口三获得了cup使用权所以窗口三输出“窗口三卖出1张票,还剩-1张”。

  经过上面运行程序的分析,得到的结果是:

  当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。

  为了解决这种数据不一致的错误情况,需要学习线程同步。

一、为什么需要线程同步/线程安全?

  因为当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。为了解决这种数据不一致的错误情况,我们才学习线程同步。

二、什么是线程同步/线程安全?

   线程同步:当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步也叫线程安全。

三、线程同步/线程安全的实现方式有几种,分别是什么,有什么区别?

有两种方式可以实现线程同步/线程安全

  1. Synchronized关键字 【同步代码块/同步方法】

           1.1同步代码块

格式:

synchronized(同步对象){



}
package com.wangxing.thread.test1;
/**
 * 基于synchronized代码块
 * synchronized(同步对象){

	}
 * @author Administrator
 *
 */
public class SynchronizedDemo1 implements Runnable{
	//定义票数
	private  int  piao=5;
	@Override
	public void run() {
		//得到线程名称
		String name=Thread.currentThread().getName();
		boolean flag=true;
		while(flag){
			//同步代码块
			synchronized(this){
			if(piao>0){
				//线程休眠1秒
				//模拟--收钱--》出票--》找钱
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//票数减1
				piao=piao-1;
				System.out.println(name+"卖出1张票,还剩"+piao+"张!!");
			}else{
				flag=false;
			}
			}
		}
	}
}

  同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。

1.2同步方法

同步方法也是方法,所以它一定是符合方法的定义格式的。

方法的定义格式:

访问限制修饰符   方法返回值类型 方法名称(){

 

}

同步方法的定义格式:

访问限制修饰符  synchronized  方法返回值类型 方法名称(){

 

}

package com.wangxing.thread.test1;
/**
 * 基于synchronized方法
 * 访问限制修饰符 synchronized 方法返回值类型 方法名称(){}
 * @author 14336
 *
 */
public class SynchronizedMethodDemo2 implements Runnable {

	//定义票数
	private int piao=5;
	private boolean flag=true;
	
	@Override
	public void run() {
	//得到线程名称
		String name =Thread.currentThread().getName();
		while(flag){
			sellpiao(name);
			
		}
	}
	//买票的同步方法
	private synchronized void sellpiao(String name){
		if (piao>0){
			//线程休眠一秒钟
			//模拟--收钱--》出票--》找钱
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
			//票数减一
			piao=piao-1;
			System.out.println(name+"卖出一场票,还剩"+piao+"张!");
			
		}else {
			flag=false;
		}
	}
	
}

2.通过Lock接口

public interface Lock

Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作

常用的接口方法

void

lock() 获得锁。

void

unlock() 释放锁。

由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。

Lock接口的子类ReentrantLock

ReentrantLock() 创建一个 ReentrantLock的实例。

实例:

Lock  lock=new ReentrantLock();
ReentrantLock  reentrantLock=new ReentrantLock();
package com.wangxing.thread.test1;

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

/**
 * 基于Lock接口的线程实现
 * Lock接口不能new
 * Lock lock=new ReentrantLock();
 * ReentrantLock reentrantLock=new ReentrantLock();
 *   void   lock();获得锁
 *   void   unlock();释放锁
 */
public class LockDemo implements Runnable {


	//定义票数
	private int piao=5;
	
	//private Lock lock=new ReentrantLock();
	private ReentrantLock lock2=new ReentrantLock();

	@Override
	public void run() {
	
		//得到线程名称
		String name =Thread.currentThread().getName();
		boolean falg=true;
		while (falg) {
			//void  lock()获得锁锁定资源
			lock2.lock();
			if (piao>0) {
				//线程休眠一秒钟
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				//票数减一
				piao=piao-1;
				System.out.println(name+"卖出一张票,还剩"+piao+"张");
			}else {
				falg=false;
				
			}
			//void  unlock()释放锁
			lock2.unlock();
		}
	}
}

synchronized

Lock

关键字

接口

自动锁定资源

手动锁定资源

不灵活

灵活

异常时会自动释放锁

不会自动释放锁,所以需要在finally中实现释放锁

不能中断锁,必须等待线程执行完成释放锁。

可以中断锁

猜你喜欢

转载自blog.csdn.net/weixin_52192654/article/details/112371406