zoukankan      html  css  js  c++  java
  • 第十五章、多线程

    多线程

    1. 进程与线程初识

    1.1 进程

    • 进程是程序的一次动态执行过程, 占用特定的地址空间。

    • 每个进程由3部分组成:cpu、data、code。每个进程都是独立的,保有自己的cpu时间,代码和数据,即便用同一份程序产生好几个进程,它们之间还是拥有自己的这3样东西,这样的缺点是:浪费内存,cpu的负担较重。

    • 多任务(Multitasking)操作系统将CPU时间动态地划分给每个进程,操作系统同时执行多个进程,每个进程独立运行。以进程的观点来看,它会以为自己独占CPU的使用权。

    • 进程的查看:

      • Windows系统: Ctrl+Alt+Del,启动任务管理器即可查看所有进程。

      • Unix系统: ps or top。

    1.2 线程

    • 一个进程内部的一个执行单元,它是程序中的一个单一的顺序控制流程。

    • 一个进程可拥有多个并行的(concurrent)线程。

    • 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且它们从同一堆中分配对象并进行通信、数据交换和同步操作。

    • 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。

    • 线程的启动、中断、消亡,消耗的资源非常少。

    1.3 线程和进程的区别

    • 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
    • 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
    • 线程和进程最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
    • 多进程: 在操作系统中能同时运行多个任务(程序)。
    • 多线程: 在同一应用程序中有多个顺序流同时执行。
    • 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
    • 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
    • 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。

    2. 多线程实现方案

    2.1 继承Thread类实现

    1. 实现步骤

      • 自定义类线程类(例:MyThread),继承Thread类

      • MyThread重写run方法

      • 创建线程对象

      • 启动线程

    2. 线程方法

      run()				线程执行和普通方法一致
      start()				开启线程
      setName()			设置当前线程名
      getName()			获取当前线程名
      currentThread() 	获取当前主线程
      
    3. 代码示例

      public class TestThread extends Thread {//自定义类继承Thread类
          //run()方法里是线程体
          public void run() {
              for (int i = 0; i < 10; i++) {
                  System.out.println(this.getName() + ":" + i);//getName()方法是返回线程名称
              }
          }
       
          public static void main(String[] args) {
              TestThread thread1 = new TestThread();//创建线程对象
              thread1.setName("线程1")//设置线程名称 
              thread1.start();//启动线程
              TestThread thread2 = new TestThread();
              thread2.setName("线程2")
              thread2.start();
          }
      }
      

    2.2 Runnable接口实现

    1. 实现步骤

      • 自定义类线程类(例:MyRunnable),实现Runnable接口

      • MyRunnable重写run方法

      • 创建线程对象

      • 启动线程

    2. 线程方法:和继承Thread类实现多线程一致

    3. 代码示例

      public class TestThread2 implements Runnable {//自定义类实现Runnable接口;
          //run()方法里是线程体;
          public void run() {
              for (int i = 0; i < 10; i++) {
                  System.out.println(Thread.currentThread().getName() + ":" + i);
              }
          }
          public static void main(String[] args) {
              //创建线程对象,把实现了Runnable接口的对象作为参数传入;
              Thread thread1 = new Thread(new TestThread2());
              thread1.setName("线程1")//设置线程名称 
              thread1.start();//启动线程;
              Thread thread2 = new Thread(new TestThread2());
              thread2.setName("线程1")//设置线程名称 
              thread2.start();
          }
      }
      

    3. 线程调度和线程控制

    • 线程休眠 / 睡眠
      • public static void sleep(long millis)
    • 线程加入 / join线程(放在start()方法后调用)
      • public final void join() 等待线程终止,才继续执行下一个线程
    • 线程礼让 线程让步
      • public static void yield() 放在线程体中操作
      • 暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),并执行其他线程。让多个线程的执行更和谐,但是不能靠他保证一人一次。
    • 后台线程 / 守护线程
      • public final void setDaemon(boolean on)
      • 将指定线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
    • 中断线程
      • public final void stop() 让线程停止,过时了,但是还可以使用。
      • public void interrupt() 中断线程。 把线程的状态终止,并抛出一个InterruptedException。

    4. 线程生命周期

    图11-4 线程生命周期图.png

    5. 线程同步

    • 概念:处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

    • synchronized 方法实现

      • 代码实现

        package demo6;
        
        public class MyCinema implements Runnable {
        
        	static int poll = 100;
        	Object obj = new Object();
        
        	@Override
        	public void run() {
        		while (true) {
        			// synchronized (obj) {
        			// synchronized (this) { 等同于 同步方法
        			// synchronized (MyCinema.class) { 等同有静态同步方法
        			show();
        			// }
        		}
        
        	}
        
        	// 同步方法就是把同步方法关键字加到方法上
        	// 同步方法锁对象是谁 锁对下那个就是this
        	// 静态方法所对象是谁 MyCinema.class
        	public synchronized void show() {
        		if (poll > 0) {
        			try {
        				Thread.sleep(100);
        			} catch (InterruptedException e) {
        				// TODO Auto-generated catch block
        				e.printStackTrace();
        			}
        			System.out.println(Thread.currentThread().getName() + "正在出售第" + (poll--) + "张票。");
        		}
        
        	}
        
        }
        
        package demo6;
        
        /*
         * 需求:
         * 		某电影院目前正在上映喜剧大片,共有100张票,而它有3个售票窗口售票,设计一个程序模拟该电影院售票
         */
        public class TestMyCinema {
        	public static void main(String[] args) {
        
        		MyCinema my = new MyCinema();
        
        		Thread t1 = new Thread(my, "张三");
        		Thread t2 = new Thread(my, "李四");
        		Thread t3 = new Thread(my, "王五");
        
        		t1.start();
        		t2.start();
        		t3.start();
        
        	}
        }
        
    • Lock类实现

      • 代码实现

        package demo7;
        
        import java.util.concurrent.locks.Lock;
        import java.util.concurrent.locks.ReentrantLock;
        
        /*
         * 虽然我们可以理解同步代码块合同部方法的锁对象问题,但是我们并没有直接看到在哪里加锁
         * 在哪里释放锁,为了更加清晰的表达如何加锁和释放锁,JDK5以后的版本提供了一个新的锁对象Lock
         * 		Lock  接口
         * 			void lock()    加锁
         * 			void unlock()  释放锁
         * 		ReentrantLock   实现类
         */
        
        public class MyLock implements Runnable {
        
        	static int poll = 100;
        	Object obj = new Object();
        
        	// 声明lock锁
        	Lock lock = new ReentrantLock();
        
        	@Override
        	public void run() {
        		// TODO Auto-generated method stub
        		while (true) {
        			// synchronized (obj) {
        			// 加锁
        			lock.lock();
        			if (poll > 0) {
        				try {
        					Thread.sleep(100);
        				} catch (InterruptedException e) {
        					e.printStackTrace();
        				}
        				System.out.println(Thread.currentThread().getName() + "正在出售第" + (poll--) + "张票。");
        			}
        			// 释放锁
        			lock.unlock();
        			// }
        		}
        	}
        }
        
        package demo7;
        
        public class TestMyLock {
        	public static void main(String[] args) {
        
        		MyLock my = new MyLock();
        
        		Thread t1 = new Thread(my, "张三");
        		Thread t2 = new Thread(my, "李四");
        		Thread t3 = new Thread(my, "王五");
        
        		t1.start();
        		t2.start();
        		t3.start();
        
        	}
        }
        

    6. 死锁

    • 概念: 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。

    • 代码实现

      package demo8;
      
      public class DeadLock extends Thread {
      
      	private boolean flg;
      
      	public DeadLock(boolean flg) {
      		this.flg = flg;
      	}
      
      	@Override
      	public void run() {
      		// flg = true 中国人;flg = false 美国人
      		if (flg) {
      			synchronized (LockObject.obj1) {
      				System.out.println("一根筷子");
      				synchronized (LockObject.obj2) {
      					System.out.println("一把叉子");
      				}
      			}
      		} else {
      			synchronized (LockObject.obj2) {
      				System.out.println("一把刀");
      				synchronized (LockObject.obj1) {
      					System.out.println("一根筷子");
      				}
      			}
      		}
      	}
      }
      
      package demo8;
      
      public class LockObject {
      	static Object obj1 = new Object();
      	static Object obj2 = new Object();
      
      }
      
      package demo8;
      
      /*
       * 死锁:
       * 		美国人吃饭用一把叉子和一把刀子
       * 		中国人吃饭用一双筷子
       * 	
       * 		现在的情况是:	
       * 			美国人手里一把叉子和一根筷子
       * 			中国人手里一根筷子和一把刀子
       * 
       *		两者相互等待对方将东西交出来,却没有人主动交,所以陷入死锁
       */
      public class TestLock {
      	public static void main(String[] args) {
      
      		DeadLock d1 = new DeadLock(true); // 中国人
      		DeadLock d2 = new DeadLock(false); // 美国人
      
      		Thread t1 = new Thread(d1, "锁1");
      		Thread t2 = new Thread(d2, "锁2");
      
      		t1.start();
      		t2.start();
      
      		/*
      		 * DeadLock d1 = new DeadLock(true); // 中国人 DeadLock d2 = new
      		 * DeadLock(false); // 美国人
      		 * 
      		 * d1.start(); d2.start();
      		 */
      	}
      }
      

    7. 线程通信(生产者消费者模型)

    7.1 相关概念

    • 生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程)。

    • 消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程)。

    • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

    图11-17 生产者消费者示意图.png

    7.2 相关方法

    表11-2 线程通信常用方法.png

    • 以上方法均是java.lang.Object类的方法,这些方法只能在同步方法或同步代码中使用,否则抛异常。

    7.3 代码实现

    // 生产对象
    package demo9;
    
    public class Book {
    
    	private String name;
    	private int price;
    
    	// 如果为true说明仓库里面有书,如果为false说明仓库里面没有书
    	// 如果有书,就通知消费者去消费
    	// 如果没有书,就通知生产者取生产
    	public boolean flag;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getPrice() {
    		return price;
    	}
    
    	public void setPrice(int price) {
    		this.price = price;
    	}
    
    }
    
    // 消费者
    package demo9;
    
    public class Consumption implements Runnable {
    
    	private Book book;
    
    	public Consumption(Book book) {
    		this.book = book;
    	}
    
    	@Override
    	public void run() {
    
    		// 为了模拟一直消费
    		while (true) {
    			synchronized (book) {
    				if (!book.flag) {
    					try {
    						book.wait(); // 等待,如果生产者那边有书就继续往下走
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    				} else {
    					System.out.println(
    							Thread.currentThread().getName() + "买了" + book.getName() + "价格是" + book.getPrice());
    					book.flag = false;
    					book.notify();
    				}
    			}
    		}
    	}
    }
    
    // 生产者
    package demo9;
    
    public class Production implements Runnable {
    
    	private Book book;
    
    	int num = 0;
    
    	public Production(Book book) {
    		this.book = book;
    	}
    
    	@Override
    	public void run() {
    		// 为了模拟一直生产
    		while (true) {
    			synchronized (book) {
    				if (book.flag) {
    					try {
    						book.wait(); // 如果有书,生产者线程就进入等待,不生产了
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				if (num % 2 == 0) {
    					book.setName("不能承受生命之轻");
    					book.setPrice(123);
    					System.out.println(
    							Thread.currentThread().getName() + "生产了" + book.getName() + "价格是" + book.getPrice());
    				} else {
    					book.setName("作为表象与意志的世界");
    					book.setPrice(99);
    					System.out.println(
    							Thread.currentThread().getName() + "生产了" + book.getName() + "价格是" + book.getPrice());
    				}
    				num++;
    				book.flag = true;
    				book.notify(); // 唤醒消费线程
    
    			}
    		}
    
    	}
    
    }
    
    // 实现类
    package demo9;
    
    /*
     * 生产者:
     * 		先看是否有数据,有就等待,没有就生产,生产完成后就通知消费者过来消费
     * 消费者:
     * 		先看是否有数据,悠久消费,没有就等待,通知生产者过来生产
     * 
     * 		
     * 		等待唤醒
     * 			wait()		等待
     * 			notify()    唤醒单个线程
     * 			ntifyAll()	唤醒所有等待的线程
     * 		
     * 		wait和sleep的区别?
     * 			wait等待需要被唤醒
     * 			sleep休眠,一定时间会自动苏醒
     */
    public class TestThread {
    	public static void main(String[] args) {
    
    		Book b = new Book();
    
    		Consumption c = new Consumption(b);
    		Production p = new Production(b);
    
    		Thread t1 = new Thread(c, "消费者"); // 消费者
    		Thread t2 = new Thread(p, "生产者"); // 生产者
    
    		t1.start();
    		t2.start();
    
    	}
    }
    

    8.线程组

    • 概念:对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

    • 代码实现

      package demo10;
      
      public class MyRunnable implements Runnable {
      
      	@Override
      	public void run() {
      		for (int i = 0; i < 100; i++) {
      			try {
      				Thread.sleep(100);
      			} catch (InterruptedException e) {
      				// TODO Auto-generated catch block
      				e.printStackTrace();
      			}
      			System.out.println(Thread.currentThread().getName() + "  " + i);
      		}
      	}
      }
      
      package demo10;
      
      /*
       * 	线程组,把多个线程组合到一起
       * 	他可以对一批线程进行分类管理,Java允许程序直接对线程进行控制
       */
      public class ThreadGroupDemo {
      	public static void main(String[] args) {
      		// 我们如何修改线程所在的线程组呢?
      		// 创建一个线程组
      		// 创建其他线程的时候,把其他线程的组制定为我们新建的线程组
      		// ThreadGroup(String name) 构造一个新线程组
      		ThreadGroup tg = new ThreadGroup("这是一个新线程组");
      
      		MyRunnable my = new MyRunnable();
      
      		// 如果想指定下列线程属于某个组,怎么办?
      		Thread t1 = new Thread(tg, my, "线程1");
      		Thread t2 = new Thread(tg, my, "线程2");
      
      		// 线程类中方法:getThreadGroup() 返回该线程所属的线程组
      		ThreadGroup threadGroup1 = t1.getThreadGroup();
      		ThreadGroup threadGroup2 = t2.getThreadGroup();
      
      		// 线程组方法:getName()
      		String name1 = threadGroup1.getName();
      		String name2 = threadGroup2.getName();
      
      		// 通过结果我们知道,线程默认情况下属于main线程组
      		System.out.println(name1);
      		System.out.println(name2);
      
      		tg.setDaemon(true); // 通过组名设置后台线程,表示该线程组的线程都是后台线程
      	}
      }
      

    9. 线程池

    9.1 线程池的优势

    • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
    • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
    • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))
    • 提供更强大的功能,延时定时线程池

    9.2 线程池流程

    img

    • 判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则
    • 判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。
    • 判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。

    9.3 Executors实现线程池

    方法名 功能
    newFixedThreadPool(int nThreads) 创建固定大小的线程池
    newSingleThreadExecutor() 创建只有一个线程的线程池
    newCachedThreadPool() 创建一个不限线程数上限的线程池,任何提交的任务都将立即执行
    • 具体方法

      • submit() 创建线程对象
      • shutdown() 结束线程池
    • 代码实现

      package demo11;
      
      public class MyRunnable implements Runnable {
      
      	@Override
      	public void run() {
      		for (int i = 0; i < 100; i++) {
      			System.out.println(Thread.currentThread().getName() + "  " + i);
      
      		}
      	}
      
      }
      
      package demo11;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      /*
       * 	线程池的好处:线程池里的每一个线程代码结束后,并不会死亡,而是再次返回到线程池中成为空闲状态,等待下一个对象来使用
       * 
       * 		如何实现线程池代码?
       * 			1.创建线程池对象,控制要创建几个线程对象
       * 				public static ExecutorsService new FixedThreadPool(int nThreads)
       * 			2.线程池的线程可以执行
       * 				可以执行Runnable对象或者Callable对象代表的线程
       * 			3.调用方法如下:
       * 				Future<?> submit(Runnable task)
       * 				<T> Future submit((Callable)<T> task)
       */
      public class ExecutorsDemo {
      	public static void main(String[] args) {
      
      		// 创建线程池对象,控制要创建几个线程对象
      		ExecutorService pool = Executors.newFixedThreadPool(2);
      
      		pool.submit(new MyRunnable());
      		pool.submit(new MyRunnable());
      		pool.submit(new MyRunnable());
      
      		// shutdown() 启动一次,顺序关闭,执行以前提交的任务,但不能接收新的任务
      		// 结束线程
      		pool.shutdown();
      	}
      }
      

    10. 定时器

    • java.util.Timer

      • 概念:Timer类作用是类似闹钟的功能,也就是定时或者每隔一定时间触发一次线程。其实,Timer类本身实现的就是一个线程,只是这个线程是用来实现调用其它线程的。
    • java.util.TimerTask

      • 概念:TimerTask类是一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run方法内部,然后通过Timer类启动线程的执行。
    • 具体方法

      • schedule() 添加计时器
      • cancel() 结束计时器
    • 代码实现

      package demo12;
      
      import java.util.Timer;
      import java.util.TimerTask;
      
      /*
       * 定时器:可以让我们在指定的时间内做某件事情,还可以重复地做某件事情
       * 
       * 依赖timer和TImerTask这里两个类
       * 		timer:定时
       * 			public Timer()
       * 			public void schedule(TimerTask task,long delay) 在指定延迟后执行指定任务
       * 			public void schedule(TimerTask task,long delay,long period)  安排指定的任务(task)从指定的延迟后(delay)开始进行重复与固定的延时时间	(period)	
       *			cancel()  终止此计时器,丢弃所有当前已安排的任务
       *
       *		TimerTask:任务
       */
      public class TimerDemo1 {
      	public static void main(String[] args) {
      		// 创建定时器对象
      		Timer t = new Timer();
      
      		// 三秒后上厕所
      		t.schedule(new MyTask(), 3000);
      
      		// 结束任务
      		t.schedule(new MyTask1(t), 3000);
              
              // 设置循环执行时间
              t.schedule(new MyTask2(), 3000, 2000);
      
      	}
      }
      
      class MyTask1 extends TimerTask {
      
      	private Timer t;
      
      	public MyTask1(Timer t) {
      		this.t = t;
      	}
      
      	@Override
      	public void run() {
      		System.out.println("上厕所中....");
      		t.cancel();
      	}
      
      }
      
  • 相关阅读:
    容器之队列的使用
    容器之栈的使用
    rapidxml的使用
    C++判断文件夹是否存在并创建文件夹
    VS2017,不能将const char *转为char *
    CSS_day01_选择器
    HTML_day02_列表
    HTML_day01基本标签
    python_day3
    python_day2
  • 原文地址:https://www.cnblogs.com/borntodie/p/14141114.html
Copyright © 2011-2022 走看看