java中的多线程3

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

public class MyThread extends Thread{
	//定义车票【共享资源】
	private int  piao=5;
	@Override
	public void run() {
		while(piao>0) {
				//我们通过线程的暂停来模拟
				//收钱-->打票-->找钱
				try {
					Thread.sleep(500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+
						"--卖出1张票,还剩"+(--piao)+"张");
		}
	}
}
//测试类
package com.wangxing.test1;
public class TestMain1 {
	public static void main(String[] args) {
		MyThread th1=new MyThread();
		MyThread th2=new MyThread();
		MyThread th3=new MyThread();
		th1.setName("窗口1");
		th2.setName("窗口2");
		th3.setName("窗口3");
		th1.start();
		th2.start();
		th3.start();
	}
}

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

package com.wangxing.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){
				//线程休眠5秒
				//模拟--收钱---》出票---》找钱
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
				    e.printStackTrace();
				}
				//票数-1
				piao=piao-1;
				System.out.println(name+"卖出一张票,还剩"+piao+"张");
			}else{
				flag=false;
			}
		}
	}
}
//测试类
package com.wangxing.test1;
public class TestMain {
	public static void main(String[] args) {
		//得到目标对象
		MyThread myth=new MyThread();
		Thread th1=new Thread(myth);
		Thread th2=new Thread(myth);
		Thread th3=new Thread(myth);
		th1.setName("窗口1");
		th2.setName("窗口2");
		th3.setName("窗口3");
		th1.start();
		th2.start();
		th3.start();
	}
}

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

java中的多线程3_第1张图片

         分析:当窗口3开始卖最后一张票的时候,窗口3判断还有一张票,这时窗口3开始收钱打票,当窗口3开始收钱打票的时候,线程
  就切换给了窗口1,由于窗口3还有来得及对票数减1,因此窗口1判断还有一张票,这时窗口1开始收钱打票,当窗口1开始收钱打票
  的时候,线程就切换给了窗口2,由于窗口1还有来得及对票数减1,因此窗口2判断还有一张票,这时窗口2开始收钱打票,线程切换
  给了窗口3,所以窗口3输出“窗口3卖出1张票,还剩0张”,输出结束以后线程就切换给窗口1,由于窗口3已经对票数减1,所以窗口1
  输出剩余票数的时候在窗口3减1以后的基础上再一次减1,就得到剩余-1张票,所以窗口1输出“窗口1卖出1张票,还剩-1张”,输出结束
  以后线程就切换给窗口2,由于窗口1已经对票数减1,所以窗口2输出剩余票数的时候在窗口1减1以后的基础上再一次减1,就得到剩余-2
  张票,所以窗口2输出“窗口2卖出1张票,还剩-2张”.

  经过上面运行程序的分析,我得到的结果是:
  当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。

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

1.为什么需要线程同步/线程安全?

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

2.什么是线程同步/线程安全?

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

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

  有2中方式可以实现线程同步/线程安全

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

    (1) 同步代码块

        格式:

               synchronized(同步对象){

                }

package com.wangxing.test1;
/**
 * 基于synchronized代码块
 * synchronized(同步对象){
 * 
 *      }
 * @author feng
 *
 */
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) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				piao=piao-1;
				System.out.println(name+"卖出一张票,还剩"+piao+"张");
			}else{
				flag=false;
			}
			}
		}
	}
}

java中的多线程3_第2张图片

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

    (2) 同步方法

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

        方法的定义格式:

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

                          }

        同步方法的定义格式:

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

                          }

package com.wangxing.test1;
/**
 * 基于synchronized方法
 * 访问限制修饰符  synchronized  方法返回值类型  方法名称(){}
 * @author feng
 *
 */
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);
		}
	}
	//卖票的同步方法
	//参数为String。以此区别是谁卖的票
	private synchronized void sellpiao(String name){
		if(piao>0){
			//线程休眠1秒
			//模拟--收钱--》出票--》找钱
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			//票数减1
			piao=piao-1;
			System.out.println(name+"卖出一张票,还剩"+piao+"张");
		}else{
			flag=false;
		}
	}
}

  3.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.test1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * 基于Lock接口的线程同步实现
 * Lock接口不能new
 * Lock lock=new ReentrantLock();
 *     void   lock()获得锁
 *     void   unlock()释放锁
 * @author feng
 *
 */
public class LockDemo implements Runnable {
	//定义票数
	private int piao=5;
	private Lock lock=new ReentrantLock();
	@Override
	public void run() {
		//得到线程的名称
		String name=Thread.currentThread().getName();
		boolean flag=true;
		while(flag){
			//void   lock()获得锁
			lock.lock();
			if(piao>0){
				//线程休眠1秒
				//模拟--收钱--》出票--》找钱
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				//票数-1
				piao=piao-1;
				System.out.println(name+"卖出一张票,还剩"+piao+"张");
			}else{
				flag=false;
			}
			//void	unlock() 释放锁。
			lock.unlock();
		}
	}
}

实现线程安全(/线程同步)的Synchronized关键字和Lock接口的区别

synchronized

Lock

关键字

接口

自动锁定资源

手动锁定资源

不灵活

灵活

异常时会自动释放锁

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

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

可以中断锁

你可能感兴趣的:(java)