18.2 线程安全、线程同步(同步代码块、同步方法、Lock锁)、线程状态、线程通信、线程调度常用方法、atm机取钱实例、医生看病实例、多种方法实现老板卖包子顾客吃包子方法

目录

1 线程安全

列:模拟买票,三个线程同时对共享的票进行售卖 

2 线程同步

2.1 同步代码块

2.1.1 列:使用同步代码块实现买票

2.2 同步方法

 2.2.1  列:使用同步方法块实现买票

2.2.2 列:某银行卡账号上有1000元现金。一个人拿着存折去取钱,同时另一个人拿着卡去ATM上取钱,各自取钱400元。要求取钱过程中不能出现资源竞争:比如400元被取出两次、银行卡的账目不能小于0等。

2.3 Lock锁

2.3.1 列:Lock锁实现售票

3 线程状态

4 线程通信

4.1 为什么要处理线程间通信

4.2 如何保证线程间通信有效利用资源:

4.3 什么是等待唤醒机制

4.3.1 列:生产者消费者模式: 

4.3.2 列:实现老板卖包子,顾客吃包子

4.3.3 列:实现老板卖包子,2个顾客吃包子

线程调度常用方法

列:某科室一天需看普通号50个,特需号10个 ,特需号看病时间是普通号的2倍,开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高,当普通号叫完第10号时,要求先看完全部特需号,再看普通号,使用多线程模拟这一过程

 


1 线程安全

什么是线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的

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

列:模拟买票,三个线程同时对共享的票进行售卖 

此时线程不安全,因为有三个线程同时访问1个对象

public class RunnableImpl implements Runnable {
    private int ticket = 20;//1 定义票数量
    @Override
    public void run() {
        while (true){
            if (ticket > 0) {
                try {
                   Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第" + ticket + "票卖掉了\t" + Thread.currentThread().getName());
                ticket--;
            }
        }
    }
}
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread = new Thread(runnable, "--a窗口");
        Thread thread1 = new Thread(runnable, "--b窗口");
        Thread thread2 = new Thread(runnable, "--b窗口");
        thread.start();
        thread1.start();
        thread2.start();
    }

 结果:

2 线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。 要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。有三种方式完成同步操作: 1. 同步代码块。 2. 同步方法。 3. 锁机制。

2.1 同步代码块

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

synchronized(同步锁){
     需要同步操作的代码 
}

同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.(锁对象: 可以是任意类型),多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

2.1.1 列:使用同步代码块实现买票

public class RunnableImpl implements  Runnable{
    private int ticket=100;
    //创建一个锁对象
    Object obj=new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){
                if(ticket>0){
                    try {
                        Thread.sleep(300);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("第"+ticket+"张票卖掉了\t"+Thread.currentThread().getName());
                    ticket--;
                }
            }
        }
    }
}
public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread thread = new Thread(r, "a窗口");
        Thread thread1 = new Thread(r, "b窗口");
        Thread thread2 = new Thread(r, "c窗口");
        thread.start();
        thread1.start();
        thread2.start();
    }
}

 

结果:部分代码图 

2.2 同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

public synchronized void method(){ 
    可能会产生线程安全问题的代码 
}

 2.2.1  列:使用同步方法块实现买票

    public void payTicket() {
        synchronized (this) {
            if (ticket > 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("第" + ticket + "张票卖掉了\t" + Thread.currentThread().getName());
                ticket--;
            }
        }
    }

    /*
    静态的同步方法
    锁对象是谁?
    不能是this
    this是创建对象之后产生的,静态方法优先于对象
    静态方法的锁对象是本类的class属性-->class文件对象(反射)
 */
    public static /*synchronized*/ void payTicketStatic(){
        synchronized (RunnableImpl.class){
            //先判断票是否存在
            if(t>0){
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //票存在,卖票 ticket--
                System.err.println(Thread.currentThread().getName()+"-->正在卖第"+t+"张票");
                t--;
            }
        }
    }
}
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread thread = new Thread(r,"a窗口");
        Thread thread2 = new Thread(r,"b窗口");
        Thread thread3 = new Thread(r,"c窗口");
        thread.start();
        thread2.start();
        thread3.start();
    }

2.2.2 列:某银行卡账号上有1000元现金。一个人拿着存折去取钱,同时另一个人拿着卡去ATM上取钱,各自取钱400元。要求取钱过程中不能出现资源竞争:比如400元被取出两次、银行卡的账目不能小于0等。

public class Bank {
	private int money;// 存款金额

	public Bank(int money) {
		this.money = money;
	}

	public int getMoney() {
		return money;
	}

	public void setMoney(int money) {
		this.money = money;
	}

	public int getmoney(double money2) throws Exception {
		// System.out.println(this.money);
		synchronized (this) {
			if ((int) money2 <= 0) {
				System.out.println("你输入的金额有误");
				return -1;
			} else if (money2 > this.money) {
				System.out.println("你的余额不足,请重新输入");
				return -2;
			} else {
				// 模拟取钱过程
				Thread.sleep(2000);
				this.money -= money2;
				System.out.println("取钱成功,你的余额为:" + this.money);
				return (int) money2;
			}
		}
	}
}
public class BankThread extends Thread {
	private Bank bank;
	private double money = 400;// 取款金额

	public BankThread(Bank bank, String name) {
		super(name);
		this.bank = bank;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		try {
			System.out.println("你的存款为:" + bank.getMoney());
			System.err.println("取款" + money + "元");
			System.err.println(this.getName() + "取款金额为:" + this.bank.getmoney(money));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
public class BankTest {

	public static void main(String[] args) throws Exception {
		// TODO Auto-generated method stub
		Bank bank = new Bank(1000);
		BankThread bt1 = new BankThread(bank, "在ATM");
		bt1.start();
		System.out.println("--------------");
		BankThread bt2 = new BankThread(bank, "银行柜台");
		bt2.sleep(5000);
		bt2.start();
	}

}

2.3 Lock

java.util.concurrent.locks.Lock(接口) 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。Lock锁也称同步锁

  Lock l=new ReentrantLock();
l.lock();//public void lock()//加同步锁。 
l.unlock();public void unlock(); //释放同步锁。

2.3.1 列:Lock锁实现售票

public class RunnableImpl implements Runnable{
    private int ticket=20;
    //1 创建ReentrantLock对象
   Lock l=new ReentrantLock();
    @Override
    public void run() {
        while (true){
          l.lock();//2 在可能出现安全问题的代码前调用Lock接口中lock()方法获取锁
            if(ticket>0){
                try {
                    Thread.sleep(100);
                    System.out.println("第"+ticket+"张票卖出了\t---窗口:"+Thread.currentThread().getName());
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    l.unlock();//3 释放锁
                }
            }
        }
    }
}
    public static void main(String[] args) {
        RunnableImpl r = new RunnableImpl();
        Thread thread = new Thread(r, "a窗口");
        Thread thread1 = new Thread(r, "b窗口");
        Thread thread2 = new Thread(r, "c窗口");
        thread.start();
        thread1.start();
        thread2.start();
    }

 

3 线程状态

在API中 java.lang.Thread.State 这个枚举中给出了五种线程状态

<1>.新建状态 (New Thread)
在 Java 语言中使用 new 操作符创建一个线程后,该线程仅仅是一个空对象,它具备了线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。

<2>.就绪状态 (Runnable)
使用 start() 方法启动一个线程后,系统为该线程分配了除 CPU 外的所需资源,使该线程处于就绪状态。

<3>.运行状态 (Running)
Java 运行系统通过调度选中一个处于就绪状态的线程,使其占有 CPU 并转为运行状态。此时,系统真正执行线程的 run() 方法。

<4>.阻塞状态 (Blocked) 一个正在运行的线程因某些原因不能继续运行时,就进入阻塞状态。比如,线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

<5>.死亡状态 (Dead) 线程在 run() 方法执行结束后进入死亡状态。

当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了notify()方法,那么就会将无限等待的A线程唤醒。注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。

 

 

4 线程通信

概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同

4.1 为什么要处理线程间通信

多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们 希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据

4.2 如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。

4.3 什么是等待唤醒机制

就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify();在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。

Java提供了3个重要方法巧妙解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。

  • 调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。
  • 调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。
  • 调用notifyAll()使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态

4.3.1 列:生产者消费者模式: 

用生产者消费者模式,模拟存取苹果的过程,其中有Apple类,Basket类,每一秒放一个苹果,篮子中只能存放5个苹果,放满以后取出依次取出5个苹果,一共有20个苹果,模拟这个存取过程。

public class Apple {
	private int id;

	public Apple(int id) {
		this.id = id;
	}
    ... ...
	@Override
	public String toString() {
		return "苹果 [id=" + id + "]";
	}
}
import java.util.LinkedList;
import java.util.List;

public class Basket {
	// list定义装苹果的容器 先摘的苹果 后卖 先卖
	private LinkedList<Apple> list = new LinkedList<Apple>();
	// 定义苹果容器的最大容量
	private int maxCount = 5;

	/**
	 * //1.生产苹果的方法 一次生产一个 1s
	 * 
	 * @param apple
	 * @throws Exception
	 */
	public void addAppler(Apple apple) throws Exception {
		if (list.size() == maxCount) {// 满了
			wait(); // 停止生产的线程
		}
		// 提醒消费定义消费的时间
		notify();// 唤醒消费的线程
		Thread.sleep(1000);// 模拟消费的时间
		// 消费了之后就可以生产了
		((LinkedList<Apple>) list).addFirst(apple);
		System.out.println("存放了:" + apple);
	}

	/**
	 * 2.消费苹果的方法 一次消费一个 1s
	 * 
	 * @throws Exception
	 */
	public void reduceApple() throws Exception {
		if (list.size() == 0) {
			wait();
		}
		notify();
		Thread.sleep(1000);
		Apple apple = ((LinkedList<Apple>) list).removeFirst();
		System.err.println("消费了:" + apple);
	}

	/**
	 * 放苹果的方法 20个 再生产的时候不允许消费
	 */
	public synchronized void putApple() throws Exception {
		for (int i = 1; i <= 20; i++) {
			Apple apple = new Apple(i);
			addAppler(apple);
		}
	}

	// 4.取苹果的方法 20个
	public synchronized void outApple() throws Exception {
		for (int i = 1; i <= 20; i++) {
			reduceApple();
		}
	}
}
public class ProducterThread extends Thread{
	private Basket basket;
	public ProducterThread(Basket basket) {
		this.basket=basket;
	}
	@Override
	public void run() {
		try {
			this.basket.putApple();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
public class ComsumerThread extends Thread{
	private Basket basket;
	public ComsumerThread(Basket basket) {
		this.basket=basket;
	}
	@Override
	public void run() {
		try {
			this.basket.outApple();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Basket basket=new Basket();
		ProducterThread pt=new ProducterThread(basket);
		ComsumerThread ct=new ComsumerThread(basket);
		pt.start();
		ct.start();
	}

}

4.3.2 列:实现老板卖包子,顾客吃包子

public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        //1 创建锁对象,保证唯一
        Object o = new Object();
        //2 创建顾客线程,顾客吃包子
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        System.out.println("顾客要一个包子");
                        try {
                            o.wait();//顾客等待老板做包子
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("顾客吃包子");
                    }
                }
            }
        }.start();

        //3 老板做包子
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (o) {
                        System.out.println("老板5秒钟之后做好包子,告知顾客,可以吃包子了");
                        System.out.println("------------------------");
                        o.notify();
                    }
                }
            }
        }.start();
    }
}

结果:

 

4.3.3 列:实现老板卖包子,2个顾客吃包子

void notifyAll() 唤醒在此对象监视器上等待的所有线程。
public class Demo02WaitAndNotify {
    public static void main(String[] args) {
        //1 创建同步锁对象
        Object obj=new Object();

        //2 创建顾客
        new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (obj){
                        System.out.println("顾客1告知老板要的包子的种类和数量");
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("顾客1吃包子");
                    }
                }
            }
        }.start();

        //2 创建顾客
        new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (obj){
                        System.out.println("顾客2告知老板要的包子的种类和数量");
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("顾客2吃包子");
                    }
                }
            }
        }.start();

        //3 创建老板线程
        new Thread(){
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(5000);//老板做包子
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("老板做好包子,告知顾客\n----------------------");
                    synchronized (obj){
                        obj.notifyAll();//唤醒顾客线程
                    }
                }
            }
        }.start();
    }
}

结果:

5 线程调度常用方法

线程调度:按照特定机制为多个线程分配CPU的使用权

  1. setPriority(int  newPriority) 更改线程的优先级
  2. static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
  3. void join()  等待该线程终止 使当前线程暂停执行,等待其他线程结束后再继续执行本线程
  4. static void yield() 暂停当前正在执行的线程对象,并执行其他线程,但是不能保证一定会实现礼让
  5. void interrupt() 中断线程
  6. boolean isAlive() 测试线程是否处于活动状态

5.1 列:某科室一天需看普通号50个,特需号10个 ,特需号看病时间是普通号的2倍,开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高,当普通号叫完第10号时,要求先看完全部特需号,再看普通号,使用多线程模拟这一过程

public class SpecialThread extends Thread {
	int num = 10;// 设置每天特殊号人数有10人看病

	public SpecialThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= num; i++) {
			System.err.println(this.getName() + i + "号这个在给人看病");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}
	}
}
public class GeneralThread extends Thread {
	int num = 50;

	public GeneralThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 1; i <= num; i++) {
			System.out.println(this.getName() + i + "号正在看病");

			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
			}
			if (num == 10) {
				try {
					this.join(2000); // 礼让资源给其他线程使用
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GeneralThread gt = new GeneralThread("普通号");
		SpecialThread st = new SpecialThread("特殊号");
        //特需号概率比   普通号大
		gt.setPriority(Thread.MIN_PRIORITY);// 1
		st.setPriority(Thread.MAX_PRIORITY);// 10
		gt.start();
		st.start();
	}

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值