Java多线程-零基础学习

 

多线程

  1. 程序、进程、线程:
    1. 程序Program:指令集,静态概念。
    2. 进程Process:操作系统,调度程序,动态概念。
      • 进程是程序的一次动态执行过程,占用特定的地址空间
      • 每个进程都是独立的,由三部分组成:cpu,data,code
      • 一个程序就是一个进程。
    3. 线程Thread:在进程内,多条执行路径。(不同的执行路径)
      • 一个进程可以拥有多个并行的(concurrent)线程。
      • 一个进程中的线程共享相同的内存单元/内存地址空间->可以访问相同的变量和对象,而且它们从同一个堆中分配对象->通信、数据交换、同步操作。
      • 会造成并发的问题。
  2. 线程和进程的区别:
    1. 进程:作为资源分配的单位
    2. 线程:调度和执行的单位
    3. 包含关系:
      • 没有线程的进程,可以看做单线程的,如果一个进程内部拥有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的。
  3. 线程的基本概念
    1. 线程是一个程序内部的顺序控制流。
    2. 线程和进程的区别
      • 每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销。
      • 线程可以看成时轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换的开销小。
      • 多进程: 在操作系统中能同时运行多个任务(程序)
      • 多线程: 在同一应用程序中有多个顺序流同时执行
      • Java的线程是通过java.lang.Thread类来实现的。
    3. 扩充
      • 一个进程是一个正运行的应用程序的实例 。它由两个部分组成:一个是操作系统用来管理这个进程的内核对象。另一个是这个进程拥有的地址空间。从执行角度方面看,一个进程由一个或多个线程组成。
      • 一个线程是一个执行单元,它控制CPU执行进程中某一段代码段。
      • 一个线程可以访问这个进程中所有的地址空间和资源。
      • 一个进程最少包括一个线程来执行代码,这个线程又叫做主线程。
      • 线程除了能够访问进程的资源外,每个线程还拥有自己的栈
  4. 线程的创建
    1. 第一种方式
      • 继承Thread类,重写run方法。
    2. 第二种
      • 实现Runnable接口,重写run方法。
  5. 第二种方式代码示例:
    1. public class Demo2 {
      	public static void main(String[] args) {
      		//1、创建真是角色
      		Programme p1 = new Programme();
      		//2、创建代理角色+真实角色引用
      		Thread thread = new Thread(p1);
      		//3、代理角色调用start方法开启线程
      		thread.start();
      
      		for (int i = 0; i < 2000; i++) {
      			System.err.println("一边查文档");
      		}
      	}
      }
      /**
       * @author Johnny
       * @category 实现了Runnable接口
       */
      class Programme implements Runnable {
      
      	public void run() {
      		for (int i = 0; i < 2000; i++) {
      			System.out.println("一边写代码");
      		}
      	}
      
      }

       

  6. 实现Runnable接口优点:
    1. 可以同时实现继承。实现Runnable接口方式要通用一些。
    2. 避免单继承。
    3. 方便共享资源,同一份资源,多个代理访问。
  7. 线程的五个状态
    1. 新生状态:
      • 用new关键字和Thread类或其子类建立一个线程对象后,访问线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)
    2. 就绪状态:
      • 处于就绪状态的线程已经具备了运行条件,但还没有分配到cpu,处于线程就绪队列,等待系统为其分配cpu。等待状态并不是执行状态。
    3. 运行状态:
      • 在运行状态和线程执行自己的run方法中代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而终止。
    4. 阻塞状态:
      • 处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
    5. 终止状态:
      • 终止状态是线程生命周期中的最后一个阶段。线程终止的原因有两个,一个是正常运行的线程完成了它的全部工作;另一种是线程被强制执行的终止,如:通过执行stop或destory方法来终止一个线程(前者会产生异常,后者是强制终止不会释放锁,都不推荐)
  8. 线程的常用方法:
    1. isAlive()
      • 判断线程是否处于活动状态,即线程是否还未终止。
    2. getPriority()
      • 获得线程的优先级数值
    3. setPriority()
      • 设置线程的优先级数值
    4. Thread.sleep()
      • 将当前线程睡眠指定毫秒数
    5. join()
      • 调用某线程的该方法,将当前线程与该线程“合并”,即等待该线程结束,再恢复当前线程的运行。
    6. yield()
      • 让出CPU,当前线程进入就绪队列等待调度。
    7. wait()
      • 当前线程进入对象的wait pool。
    8. notify()/notifyAll()
      • 唤醒对象的wait pool中的一个/所有等待线程。
    9. static void sleep(long millis)
      •  在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
  9. interrupt
    1. 中断线程的方法。
    2. 中断线程同时抛出一个异常:InterruptedException。
    3. InterruptedException:该异常是编译异常(也称受检查异常)。
  10. join()
    1. 实现了“整体插队”的效果,将当前加入的线程执行完毕,然后继续执行外面的线程。
    2. thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
      • 比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
  11. yield()
    1. yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
    2. 结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
  12. 两种线程模式:
    1. 协作式:一个线程保留对处理器的控制直到它自己决定放弃
      • 速度快、代价低
      • 用户编程非常麻烦
    2. 抢先式:系统可以任意的从线程中夺回对CPU的控制权,再把控制权分给其它的线程 。
      • 两次切换之间的时间间隔就叫做时间片
      • 效率不如协作式高 ,OS核心必须负责管理线程
      • 简化编程,而且使程序更加可靠
    3. 多数线程的调度是抢先式的。
  13. synchronized
    1. 由于同一个进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。
    2. java语言提供了专门机制以解决这种冲突,有效的避免了同一个数据被多个线程同时访问。
    3. 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,
    4. 这套机制就是synchronized关键字,它包括两种用法:synchronized方法和synchronized块确保资源安全,线程安全。
    5. 关键字synchronized 来与对象的互斥锁联系。当某个对象synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
  14. Web12306线程安全代码:
    1. public class Web12306XCAQ implements Runnable{
      
      	private int num = 20;//共享资源
      	private boolean flag = true;
      	
      	@Override
      	public void run() {
      		while(flag){
      			method2();
      		}
      	}
      
      	public synchronized void method1(){
      		if(num<1){
      			flag=false;
      		}else{
      			try {
      				Thread.sleep(200);
      			} catch (InterruptedException e) {
      				e.printStackTrace();
      			}
      			//说出当前线程的名称,谁抢了第?张票
      			System.out.println(
      			Thread.currentThread().getName()+"抢了第"+num--+"张票");
      		}
      	}
      	
      	public void method2(){
      		synchronized (this) {
      			if(num<1){
      				flag=false;
      			}else{
      				try {
      					Thread.sleep(200);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      				//说出当前线程的名称,谁抢了第?张票
      				System.out.println(
      				Thread.currentThread().getName()+"抢了第"+num--+"张票");
      			}
      		}
      	}
      }

       

  15. 线程死锁
    1. 过多的同步容易造成死锁
    2. 一个线程中锁定了另一个线程需要的对象,而另一个线程锁定了这个线程需要的对象。
    3. 也就是说:两个线程互相锁定对方所需要的对象。
  16. synchronized总结
    1. 无论synchronized关键字加在方法上还是对象上,它取得的锁都是锁在了对象上,而不是把一段代码或函数当作锁,而且同步方法很可能还会被其他线程的对象访问。
    2. 每个对象只有一个锁(lock)与之相关联。
    3. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
    4. 搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。
  17. wait() :等待、释放锁
  18. notify()、notifyAll() :唤醒
    1. 他们三个都是在同步中使用
    2. 生产者缴费者模式,解决线程死锁问题(是一个解决方案)
    3. 该问题的关键就是要保证生产者不会再缓冲区满时加入数据,消费者也不会再缓冲区空时消耗数据。
    4. 通常常用的方法有信号灯法。
  19. Wait sleep区别
    1. 来源不同
      • Sleep是Thread提供的方法
      • Wait来自Object
    2. 代码位置不同
      • Wait需要写在Synchronize语句块里面
    3. 是否释放锁定对象
    4. 调用wait方法,释放锁定该对象
    5. Sleep时别的线程也不可以访问锁定对象

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章