如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。实现Runnable接口或callable接口,适合多个相同或不同的程序代码的线程去共享同一个资源。
多个线程共享数据分两种情况:
1、如果多个线程执行同一个Runnable实现类中的代码,此时共享的数据放在Runnable实现类中;
2、如果多个线程执行不同的Runnable实现类中的代码,此时共享数据和操作共享数据的方法封装到一个对象中,在不同的Runnable实现类中调用操作共享数据的方法。
一、 相同程序代码的多个线程共享一个资源
如果有多个线程在同时运行同一段段代码,可以使用同一个Runnable
实现类,多个线程可以共享一个实现类对象,共享数据作为这个Runnable实现类
的全局变量
卖票案例:
public class Demo08 { public static void main(String[] args) { //创建线程任务对象 Ticket ticket = new Ticket(); //创建三个窗口对象 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); //卖票 t1.start(); t2.start(); t3.start(); } static class Ticket implements Runnable { //Object lock = new Object(); ReentrantLock lock = new ReentrantLock(); private int ticket = 10; public void run() { String name = Thread.currentThread().getName(); while (true) { sell(name); if (ticket <= 0) { break; } } } private synchronized void sell(String name) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }if (ticket > 0) { System.out.println(name + "卖票:" + ticket); ticket--; } } } }
ticket就是全局变量,作为三个线程的共享数据。
二、 不同程序代码的多个线程共享一个资源
如果每个线程执行的代码不同,将共享数据和操作共享数据的方法封装在一个对象中,在不同的Runnable实现类调用操作共享数据的方法。
银行转账案例:
创建一个对象,对象中有操作共享数据的方法
public class Bank { private volatile int count =0;//账户余额 //存钱 public void addMoney(int money){ count +=money; System.out.println(System.currentTimeMillis()+"存进:"+money); } //取钱 public void subMoney(int money){ if(count-money < 0){ System.out.println("余额不足"); return; } count -=money; System.out.println(+System.currentTimeMillis()+"取出:"+money); } //查询 public void lookMoney(){ System.out.println("账户余额:"+count); } }
创建两个线程,线程中分别调用共享数据所在对象中不同的方法,
public class SyncThreadTest { public static void main(String args[]){ final Bank bank=new Bank(); Thread tadd=new Thread(new Runnable() { public void run() { // TODO Auto-generated method stub while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } bank.addMoney(100); bank.lookMoney(); System.out.println("\n"); } } }); Thread tsub = new Thread(new Runnable() { public void run() { // TODO Auto-generated method stub while(true){ bank.subMoney(100); bank.lookMoney(); System.out.println("\n"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }); tsub.start(); tadd.start(); } }
如果一个类继承Thread,则不适合资源共享。并不是不可以实现资源共享
生产者与消费者问题
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false),吃货线程等待,包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。
![](https://img2020.cnblogs.com/blog/2138872/202102/2138872-20210225173736033-224102897.png)
![](https://img2020.cnblogs.com/blog/2138872/202102/2138872-20210225173801828-203365045.png)
![](https://img2020.cnblogs.com/blog/2138872/202102/2138872-20210225173839449-1708619871.png)
实现Runnable接口比继承Thread类所具有的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。