21、多线程(线程与进程\线程的实现方式\线程的声明周期\线程同步与死锁\生产者消费者问题)
一、线程基础内容
1、程序、进程与线程
1.1、程序:Program,是一个指令的集合
1.2、进程:Process,(正在执行中的程序)是一个静态的概念
- 进程是程序的一次静态态执行过程, 占用特定的地址空间(资源).
- 每个进程都是独立的,由3部分组成cpu,data,code(执行逻辑)
- 缺点:内存的浪费,cpu的负担
1.3、线程:是进程中一个“单一的连续控制流程” (a singlesThread,equential flow of control)/执行路径
- 线程又被称为轻量级进程(lightweight process)。
- Threads run at the same time, independently of one another
- 一个进程可拥有多个并行的(concurrent)线程
- 一个进程中的线程共享相同的内存单元/内存地址空间--》可以访问相同的变量和对象,而且它们从同一堆中分配对象--》通信、数据交换、同步操作
- 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信
- 机制,这就使得通信更简便而且信息传递的速度也更快。
- Java虚拟机启动的时候会有一个进程java.exe,该进程中
- 线程结束,进程未必结束,但进程结束,线程一定结束
- 进程中包含线程,线程是进程的一部分
2、线程的创建和启动
- 在Java中负责线程的这个功能的是Java.lang.Thread 这个类
- 可以通过创建 Thread 的实例来创建新的线程。
- 每个线程都是通过某个特定Thread对象所对应的方法run( )来完成其操作的,方法run( )称为线程体。
- 通过调用Thead类的start()方法来启动一个线程。
一个多线程的程序
public class ThreadDemo01 extends Thread { @Override public void run() { // super.run(); for (int i=0; i<10; i++){ System.out.println(Thread.currentThread().getName()+"--------------"+i); } } public static void main(String[] args) { ThreadDemo01 threadDemo01 = new ThreadDemo01(); threadDemo01.start(); for (int i=0; i<5; i++){ System.out.println(Thread.currentThread().getName()+"==========="+i); } } }
打印结果为:
/* main===========0 Thread-0--------------0 main===========1 Thread-0--------------1 main===========2 Thread-0--------------2 main===========3 Thread-0--------------3 main===========4 Thread-0--------------4 Thread-0--------------5 Thread-0--------------6 Thread-0--------------7 Thread-0--------------8 Thread-0--------------9 */
/** * 实现多线程的时候: * 1、需要继承Thread类 * 2、必须要重写run方法,指的是核心执行的逻辑 * 3、线程在启动的时候,不要直接调用run方法,而是要通过start()来进行调用 * 4、每次运行相同的代码,出来的结果可能不一样,原因在于多线程谁先抢占资源无法进行人为控制 */
另一种方式, 用的更多
public class RunnableDemo implements Runnable { @Override public void run() { for(int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"--------------"+i); } } public static void main(String[] args) { RunnableDemo runnableDemo = new RunnableDemo(); Thread thread = new Thread(runnableDemo); thread.start(); for(int i =0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"==========="+i); } } }
打印结果为:
/* Thread-0--------------0 main===========0 Thread-0--------------1 main===========1 Thread-0--------------2 main===========2 Thread-0--------------3 Thread-0--------------4 Thread-0--------------5 Thread-0--------------6 Thread-0--------------7 Thread-0--------------8 Thread-0--------------9 main===========3 main===========4 Process finished with exit code 0 */
/** * 第二种实现方式:使用了代理设计模式 * 1、实现Runnable接口 * 2、重写run方法 * 3、创建Thread对象,将刚刚创建好的runnable的子类实现作为thread的构造参数 * 4、通过thread.start()进行启动 */
/** * 推荐使用第二种方式, * 1、java是单继承,将继承关系留给最需要的类 * 2、使用runnable接口之后不需要给共享变量添加static关键字,每次创建一个对象,作为共享对象即可 */
思考买票过程: 同一张票可能会被n多个人抢票
版本1:Thread创建
public class TicketThread extends Thread{ private int ticket = 5; // 一共5张票 @Override public void run() { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } public static void main(String[] args) { TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); TicketThread t3 = new TicketThread(); TicketThread t4 = new TicketThread(); t1.start(); t2.start(); t3.start(); t4.start(); } }
public class TicketThread extends Thread{ private int ticket = 5; // 一共5张票 @Override public void run() { for(int i = 0;i<100;i++){ if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } } } public static void main(String[] args) { TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); TicketThread t3 = new TicketThread(); TicketThread t4 = new TicketThread(); t1.start(); t2.start(); t3.start(); t4.start(); } }
运行结果为:
/* Thread-0正在出售第5张票 Thread-3正在出售第5张票 Thread-1正在出售第5张票 Thread-2正在出售第5张票 Thread-1正在出售第4张票 Thread-0正在出售第4张票 Thread-3正在出售第4张票 Thread-0正在出售第3张票 Thread-1正在出售第3张票 Thread-1正在出售第2张票 Thread-1正在出售第1张票 Thread-2正在出售第4张票 Thread-0正在出售第2张票 Thread-3正在出售第3张票 Thread-3正在出售第2张票 Thread-0正在出售第1张票 Thread-2正在出售第3张票 Thread-2正在出售第2张票 Thread-2正在出售第1张票 Thread-3正在出售第1张票 Process finished with exit code 0 */
每个线程都是 5 4 3 2 1
每个对象实例化时都拥有ticket 这样一个属性值, 意味着每个堆空间都有一个5
开启线程后, 每个对象都是操作当前堆空间里的这个5, 所以每个线程都是5 4 3 2 1
static, 静态变量归属于类 而不归属实例对象
public class TicketThread extends Thread{ private static int ticket = 5; // 一共5张票 @Override public void run() { for(int i = 0;i<100;i++){ if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } } } public static void main(String[] args) { TicketThread t1 = new TicketThread(); TicketThread t2 = new TicketThread(); TicketThread t3 = new TicketThread(); TicketThread t4 = new TicketThread(); t1.start(); t2.start(); t3.start(); t4.start(); } }
打印结果为:
4 3 2 1 5, 顺序不对, 有一个数据同步问题
版本2:使用runnable
public class TicketRunnable implements Runnable { private int ticket = 5; @Override public void run() { for (int i = 0; i < 100; i++) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } } } public static void main(String[] args) { TicketRunnable ticket = new TicketRunnable(); Thread t1 = new Thread(ticket); Thread t2 = new Thread(ticket); Thread t3 = new Thread(ticket); Thread t4 = new Thread(ticket); t1.start(); t2.start(); t3.start(); t4.start(); } }
打印结果为:
这里就不用static了, 一个实例,5个线程都是用的这一个ticket
public interface KindWomen { /* * 抛媚眼 * */ public void makeEyesWithMen(); public void playWithMen(); }
王婆是代理人
/* * * 代理人 * */ public class WangPo implements KindWomen { private KindWomen kindWomen; public WangPo(){ this.kindWomen = new PanJinLian(); } public WangPo(KindWomen kindWomen){ this.kindWomen = kindWomen; } @Override public void makeEyesWithMen() { this.kindWomen.makeEyesWithMen(); } @Override public void playWithMen() { this.kindWomen.playWithMen(); } }
潘金莲真正做事的人
public class PanJinLian implements KindWomen{ @Override public void makeEyesWithMen() { System.out.println("潘金莲在抛媚眼"); } @Override public void playWithMen() { System.out.println("潘金莲。。。。。"); } }
贾氏也是真正做事的人
public class JiaShi implements KindWomen{ @Override public void makeEyesWithMen() { System.out.println("贾氏抛媚眼"); } @Override public void playWithMen() { System.out.println("贾事。。。。。"); } }
西门庆是客户
public class XiMenQing { public static void main(String[] args) { // WangPo wangPo = new WangPo(); // wangPo.playWithMen(); // wangPo.makeEyesWithMen(); JiaShi jiaShi = new JiaShi(); WangPo wangPo = new WangPo(jiaShi); wangPo.makeEyesWithMen(); wangPo.playWithMen(); } }
王婆是代理人, 真正做事的是贾氏
3、线程状态
public class ThreadApiDemo implements Runnable{ public static void main(String[] args) { //获取当前线程对象 Thread thread = Thread.currentThread(); //获取当前线程的名称 System.out.println(thread.getName()); //获取线程的id System.out.println(thread.getId()); //获取线程的优先级,在一般系统中范围是0-10的值,如果没有经过设置的话,就是默认值5,有些系统是0-100 System.out.println(thread.getPriority()); //设置线程池的优先级 /* * 优先级越高一定越先执行吗? * 不一定,只是优先执行的概率比较大而已 * */ thread.setPriority(6); System.out.println(thread.getPriority()); ThreadApiDemo threadApiDemo = new ThreadApiDemo(); Thread t1 = new Thread(threadApiDemo); System.out.println(t1.isAlive()); // 判断是否存活 t1.start(); System.out.println(t1.isAlive()); System.out.println(t1.getPriority()); // for(int i = 0;i<5;i++){ // System.out.println(Thread.currentThread().getName()+"-----"+i); // } System.out.println(t1.isAlive()); } @Override public void run() { // for(int i = 0;i<5;i++){ // System.out.println(Thread.currentThread().getName()+"-----"+i); // } } }
join方法:
public class MyRun implements Runnable { @Override public void run() { for(int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"------"+i); } } }
public class JoinTest { public static void main(String[] args) { MyRun run = new MyRun(); Thread thread = new Thread(run); thread.start(); for(int i = 0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"-----------------"+i); if(i==3){ try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
/* main-----------------0 main-----------------1 main-----------------2 main-----------------3 Thread-0------0 Thread-0------1 Thread-0------2 Thread-0------3 Thread-0------4 Thread-0------5 Thread-0------6 Thread-0------7 Thread-0------8 Thread-0------9 main-----------------4 Process finished with exit code 0 */
main i==3 后, 执行join(), 从运行态到阻塞态
MyRun线程开始执行,执行完成后轮到 main继续执行
sleep()方法
public class SleepTest { public static void main(String[] args) { MyRun run = new MyRun(); Thread thread = new Thread(run); thread.start(); for(int i = 0;i<5;i++){ System.out.println(Thread.currentThread().getName()+"=============="+i); if(i==2){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
/* main==============0 main==============1 main==============2 Thread-0------0 Thread-0------1 Thread-0------2 Thread-0------3 Thread-0------4 Thread-0------5 Thread-0------6 Thread-0------7 Thread-0------8 Thread-0------9 main==============3 main==============4 Process finished with exit code 0 */
main 到2 时 , 1sThread都执行完了,继续执行main 3 4
yield()方法:
public class YieldTest { public static void main(String[] args) { MyRun run = new MyRun(); Thread thread = new Thread(run); thread.start(); for(int i = 0;i<5;i++){ if(i==2){ Thread.yield(); // thread.stop(); System.out.println(Thread.currentThread().getName()+"=============="+i+"礼让一次"); }else{ System.out.println(Thread.currentThread().getName()+"=============="+i); } } } }
打印结果为:
/* main==============0 Thread-0------0 main==============1 Thread-0------1 Thread-0------2 Thread-0------3 Thread-0------4 Thread-0------5 main==============2礼让一次 main==============3 Thread-0------6 main==============4 Thread-0------7 Thread-0------8 Thread-0------9 Process finished with exit code 0 */
礼让一次后,又出现了main 3 又去竞争资源并且抢占到了资源
stop()方法: 过时了,不推荐使用
public class YieldTest { public static void main(String[] args) { MyRun run = new MyRun(); Thread thread = new Thread(run); thread.start(); for(int i = 0;i<5;i++){ if(i==2){ // Thread.yield(); thread.stop(); System.out.println(Thread.currentThread().getName()+"=============="+i+"礼让一次"); }else{ System.out.println(Thread.currentThread().getName()+"=============="+i); } } } }
打印结果为:
/* main==============0 main==============1 Thread-0------0 Thread-0------1main==============2礼让一次 main==============3 main==============4 Process finished with exit code 0 */
2 以后强制结束了
父类Object 有几个非常重要的方法
notify() 唤醒
wait() 等待
线程会有些误解, 因为先说的Object类, 前面说notify和wait会在多线程中说
所有的类都有等待和唤醒的方法,也就是说时对象有等待和唤醒的方法,而不是说线程有等待和唤醒方法
在多线程的时候,可以实现唤醒和等待的过程,但是唤醒和等待操作的对应不是thread类, 而是我们设置的共享对象或者共享变量
public class Test1 implements Runnable{ @Override public void run() { for(int i = 0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"====="+i); try { Thread.sleep(1001); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Test1 test1 = new Test1(); Thread thread = new Thread(test1); thread.start(); for(int i=10;i>0;i--){ System.out.println(Thread.currentThread().getName()+"----"+i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
打印结果为:
/* main----10 Thread-0=====0 main----9 Thread-0=====1 main----8 Thread-0=====2 main----7 Thread-0=====3 main----6 Thread-0=====4 main----5 Thread-0=====5 main----4 Thread-0=====6 main----3 Thread-0=====7 main----2 Thread-0=====8 main----1 Thread-0=====9 Process finished with exit code 0 */
二、线程同步
/* * 多线程并发访问的时候回出现数据安全问题: * 解决方式: * 1、同步代码块 * synchronized(共享资源、共享对象,需要是object的子类){具体执行的代码块} * 2、同步方法 * 将核心的代码逻辑定义成一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象 * */ */
买票问题
1、同步代码块
public class TicketRunnable2 implements Runnable { private int ticket = 5; @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (this){ if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } } } } public static void main(String[] args) { TicketRunnable2 ticket = new TicketRunnable2(); Thread t1 = new Thread(ticket,"A"); Thread t2 = new Thread(ticket,"B"); Thread t3 = new Thread(ticket,"C"); Thread t4 = new Thread(ticket,"D"); t1.start(); t2.start(); t3.start(); t4.start(); } }
打印结果为:
/* D正在出售第5张票 C正在出售第4张票 A正在出售第3张票 B正在出售第2张票 A正在出售第1张票 Process finished with exit code 0 */
2、同步方法
public class TicketRunnable3 implements Runnable { private int ticket = 5; @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } this.sale(); } } /* * 使用同步方法解决多线程数据安全的问题 * */ public synchronized void sale() { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "张票"); } } public static void main(String[] args) { TicketRunnable3 ticket = new TicketRunnable3(); Thread t1 = new Thread(ticket, "A"); Thread t2 = new Thread(ticket, "B"); Thread t3 = new Thread(ticket, "C"); Thread t4 = new Thread(ticket, "D"); t1.start(); t2.start(); t3.start(); t4.start(); } }
打印结果为:
/* B正在出售第5张票 C正在出售第4张票 D正在出售第3张票 A正在出售第2张票 D正在出售第1张票 Process finished with exit code 0 */
三、死锁
四、线程的生产者与消费者
/* * * 生产产品,将产品放置到共享空间中 * * */ public class Producer implements Runnable { }
/* * * 从共享空间中取走产品 * */ public class Consumer implements Runnable { }
public class Goods { private String brand; // 品牌 private String name; // 名称 public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
1、版本一
保证生产者先生产产品
public class Producer implements Runnable { private Goods goods; public Producer(Goods goods) { this.goods = goods; } @Override public void run() { for (int i = 0; i < 10; i++) { if (i % 2 == 0) { goods.setBrand("娃哈哈"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } goods.setName("矿泉水"); } else { goods.setBrand("旺仔"); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } goods.setName("小馒头"); } System.out.println("生产者生产了" + this.goods.getBrand() + "--" + this.goods.getName()); } } }
/* * * 从共享空间中取走产品 * */ public class Consumer implements Runnable { private Goods goods; public Consumer(Goods goods) { this.goods = goods; } @Override public void run() { for(int i = 0;i<10;i++){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费者取走了"+this.goods.getBrand()+"----"+this.goods.getName()); } } }
/* * 多线程访问的时候出现了数据安全的问题 * 1、生产者没有生产商品,消费者就可以获取 * 2、商品的品牌和名称对应不上 * * */ public class Test { public static void main(String[] args) { Goods goods = new Goods(); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); Thread t1 = new Thread(producer); Thread t2 = new Thread(consumer); t1.start(); t2.start(); } }
打印结果为:
2、版本二
同步方法解决, 不需要指定共享对象
public class Goods { private String brand; private String name; public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getName() { return name; } public void setName(String name) { this.name = name; } //消费者获取商品 public synchronized void get(){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName()); } //生产者生产商品 public synchronized void set(String brand,String name){ this.setBrand(brand); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } this.setName(name); System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName()); } }
/* * * 生产产品,将产房放置到共享空间中 * * */ public class Producer implements Runnable { private Goods goods; public Producer(Goods goods) { this.goods = goods; } @Override public void run() { for (int i = 0; i < 10; i++) { if (i % 2 == 0) { goods.set("娃哈哈","矿泉水"); } else { goods.set("旺仔","小馒头"); } } } }
/* * * 从共享空间中取走产品 * */ public class Consumer implements Runnable { private Goods goods; public Consumer(Goods goods) { this.goods = goods; } @Override public void run() { for(int i = 0;i<10;i++){ goods.get(); } } }
public class Test { public static void main(String[] args) { Goods goods = new Goods(); Producer producer = new Producer(goods); Consumer consumer = new Consumer(goods); Thread t1 = new Thread(producer); Thread t2 = new Thread(consumer); t1.start(); t2.start(); } }
打印结果为:
还是存在问题,下面都是消费者,但是品牌和名称已经对应起来了
刚刚生产过程不是一个原子操作,把生产过程已经放到了set了
这里是一个原子操作了,加了synchronized,这里是不会发生中断的,品牌和名称要么全部成功,要么全部失败
所以品牌和名称会全部对应起来,已经解决了一个问题
3、版本三
我们要改变的只有goods方法
先判断只有生产者先生产消费者才能消费
public class Goods { private String brand; private String name; //默认是不存在商品的,如果值等于true的话,代表有商品 private boolean flag = false; public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getName() { return name; } public void setName(String name) { this.name = name; } //消费者获取商品 public synchronized void get(){ /* * 如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态,等待生产者生产,当 * 有商品之后,再开始消费 * */ if (!flag){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName()); flag = false; //唤醒生产者去进行生产 notify(); } //生产者生产商品 public synchronized void set(String brand,String name){ //当生产者抢占到cpu资源之后会判断当前对象是否有值,如果有的话,以为着消费者还没有消费,需要提醒消费者消费,同时 //当前线程进入阻塞状态,等待消费者取走商品之后,再次生产,如果没有的话,不需要等待,不需要进入阻塞状态,直接生产即可 if(flag){ try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.setBrand(brand); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } this.setName(name); System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName()); //如果代码执行到此处,意味着已经生产完成,需要将flag设置为true flag = true; //唤醒消费者去进行消费 notify(); } }
打印结果为:
4、版本4
上面基本是完成了生产者消费者
我们代码里就设置了一个Goods对象,对象只有 1 个
在现实中,共享空间不可能只有 1 个, 肯定不合适
要放一个集合在这
集合该如何处理???
集合判断一堆的等待一堆的唤醒,非常麻烦
这样就有了JUC
java util 下有一个concurrent
这里面就帮我们解决并发问题
队列的方式
import java.util.concurrent.BlockingQueue; public class ProducerQueue implements Runnable { private BlockingQueue<Goods> blockingQueue; public ProducerQueue(BlockingQueue blockingQueue) { this.blockingQueue = blockingQueue; } @Override public void run() { for(int i = 0;i<10;i++){ Goods goods = null; if(i%2==0){ goods = new Goods("娃哈哈","矿泉水"); }else{ goods = new Goods("旺仔","小馒头"); } System.out.println("生产者开始生产商品:"+goods.getBrand()+"--"+goods.getName()); try { blockingQueue.put(goods); } catch (InterruptedException e) { e.printStackTrace(); } } } }
import java.util.concurrent.BlockingQueue; public class ConsumerQueue implements Runnable { private BlockingQueue<Goods> blockingQueue; public ConsumerQueue(BlockingQueue blockingQueue) { this.blockingQueue = blockingQueue; } @Override public void run() { for(int i = 0;i<10;i++){ try { Goods goods = blockingQueue.take(); System.out.println("消费者消费的商品是:"+goods.getBrand()+"--"+goods.getName()); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } }
public class Goods { private String brand; private String name; public Goods(String brand, String name) { this.brand = brand; this.name = name; } public String getBrand() { return brand; } public void setBrand(String brand) { this.brand = brand; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Test { public static void main(String[] args) { BlockingQueue<Goods> queue = new ArrayBlockingQueue<Goods>(5); ProducerQueue producerQueue = new ProducerQueue(queue); ConsumerQueue consumerQueue = new ConsumerQueue(queue); new Thread(producerQueue).start(); new Thread(consumerQueue).start(); } }
打印结果为:
高级GUC
跳转路径