• JAVA线程


    JAVA线程

    线程

    串行和并发

    进程之间资源不共享,所以在程序中一般不单独开辟进程

    线程是一个任务执行的最小单元

    线程的并发和进程是一样的,也是CPU通过中断进行“假并发”

    多个线程同时访问的资源叫临界资源


    线程的状态

    题外话:时间片

    时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。时间片通常很短(在Linux上为5ms-800ms)

    时间片由操作系统内核的调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片(在Linux系统中,初始时间片也不相等,而是各自父进程的一半),然后每个进程轮番地执行相应的时间,当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片,如此往复。

    系统通过测量进程处于“睡眠”和“正在运行”状态的时间长短来计算每个进程的交互性,交互性和每个进程预设的静态优先级Nice值)的叠加即是动态优先级,动态优先级按比例缩放就是要分配给那个进程时间片的长短。一般地,为了获得较快的响应速度,交互性强的进程(即趋向于IO消耗型)被分配到的时间片要长于交互性弱的(趋向于处理器消耗型)进程。

    抢占式多任务处理(Preemption)是计算机操作系统中,一种实现多任务处理(multi task)的方式,相对于协作式多任务处理而言。协作式环境下,下一个进程被调度的前提是当前进程主动放弃时间片;抢占式环境下,操作系统完全决定进程调度方案,操作系统可以剥夺耗时长的进程的时间片,提供给其它进程。

    • 每个任务赋予唯一的一个优先级(有些操作系统可以动态地改变任务的优先级);
    • 假如有几个任务同时处于就绪状态,优先级最高的那个将被运行;
    • 只要有一个优先级更高的任务就绪,它就可以中断当前优先级较低的任务的执行;

    1568035912437
    1568036274048

    线程的概念

    一个线程就是一个程序内部的顺序控制流

    线程和进程的区别

    1568032609559
    1568032651986
    java.lang.Thread模拟CPU实现线程,所要执行的代码和处理的数据要传递给Thread类。

    1568032763234

    构造线程的方法:线程实例化的两种方式

    • 法一:线程实例化
    1. 继承Thread类,做一个线程子类(自定义的线程类)

    2. 重写run方法,将需要并发执行的任务写到run中

    3. 线程开辟需要调用start方法,使线程启动,来执行run中的逻辑(如果直接调用run方法,这不会开辟新线程,或者线程不会进入就绪态,没有实现多线程)

      多线程的效果:

      1568037231469
      主线程先结束,再执行逻辑

      这种方式可读性更强

      缺点:由于JAVA的单继承,则该类无法再继承其他类,会对原有的继承结构造成影响

    • 法二:通过Runnable接口

      1568037356734
      1568037439950
      用Runnable”声明“了对象之后,要真正的实例化Thread对象,即Thread t2=new Thread(r1);这个语句

      不影响原有继承关系

      缺点:可读性较差


    线程的常用操作

    1. 线程的命名

      1. 法一:使用setName命名

        1568040448103

      2. 法二:在构造方法中直接命名

        1568040497545

      3. 自定义一个类,继承Thread,添加一个包括名字参数的构造方法

      1568079671069

    2. 线程的休眠

      线程休眠方法:

      Thread.sleep();//参数是以毫秒为单位的时间差

      1568079865226
      休眠会抛出一个InterruptedException异常,我们需要捕获它

      线程休眠可以使一个线程由运行状态变成阻塞态,休眠时间结束后回到就绪态,如果成功获得时间片就变成运行状态


    线程的优先级

    设置线程的优先级,只是修改这个线程可以去抢到CPU时间片的概率,并不是说优先级高的线程就一定能抢到CPU时间片

    ? 优先级是一个0-10的整数,默认是5

    ? 设置优先级必须放到这个线程开始执行(即Start)之前

    ? 题外话:Thread.currentThread().getName();//获取当前线程

    ? 线程之间都是交替执行的,并不是优先级高的先执行完再执行优先级低的

    1568081094353
    在主方法中:

    1568081122773

    线程的礼让(Yield)


    礼让:由执行到就绪态,即让当前运行状态的线程释放自己的CPU资源

    注意,放弃当前CPU时间片后,该线程和其他线程一起争抢时间片,依然有可能抢到,所以礼让的结果不一定是运行其他线程

    yield是一个静态方法,直接Thread.yield();就可

    示例:

    1568081705417
    1568081759667

    注意看,无论是线程1还是线程2,执行到3时都进行了礼让,让另一个线程执行(这是比较极端的状态)

    线程中的临界资源问题


    被多个线程要同时访问的资源是临界资源

    多个线程同时访问一个临界资源会出现临界资源问题,即临界资源调用出错

    错误示例:

    1568082371677
    1568082336910
    执行结果:

    1568082388365
    可以看到,这里对于restCount的调用是有问题的

    错误原因

    每个线程在字符串拼接、输出之前时间片就被抢走,另一个线程访问的是原来的线程做减法之后的restCount,最后谁先输出不一定。

    1568082814212
    我们可以看到,这种差异是非常明显的:当线程3做完减法变成0时,线程2还停留在78上,卡的时间可够长的

    解决临界资源问题

    不让多个线程同时访问--引入锁的概念,为临界资源加锁。当其他线程访问、看到有锁时,就知道有其他线程正在访问该资源,要等待该线程访问完毕(解锁)再访问

    ? 常用的锁:

    1. 同步代码段
    
    2. 同步方法
    3. 同步锁
    

    同步代码段

    在Java程序运行时环境中,JVM需要对两类线程共享的数据进行协调:
    1)保存在堆中的实例变量
    2)保存在方法区中的类变量

    这两类数据是被所有线程共享的。

    (程序不需要协调保存在Java 栈当中的数据。因为这些数据是属于拥有该栈的线程所私有的。)

    再复习一下堆栈:

    方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来。

    栈:在Java中,JVM中的栈记录了线程的方法调用。每个线程拥有一个栈在某个线程的运行过程中,如果有新的方法调用,那么该线程对应的栈就会增加一个存储单元,即帧(frame)[帧也就是你学过的活动记录]。在frame中,保存有该方法调用的参数、局部变量和返回地址。

    堆是JVM中一块可自由分配给对象的区域。当我们谈论垃圾回收(garbage collection)时,我们主要回收堆(heap)的空间。

    深入类锁和对象锁

    请参考:https://blog.csdn.net/javazejian/article/details/72828483

    synchronized(""){
    ;
    }
    

    类锁实际上用对象锁来实现。当虚拟机装载一个class文件的时候,它就会创建一个java.lang.Class类的实例。当锁住一个对象的时候,实际上锁住的是那个类的Class对象。

    一个线程可以多次对同一个对象上锁。对于每一个对象,java虚拟机维护一个加锁计数器,线程每获得一次该对象,计数器就加1,每释放一次,计数器就减 1,当计数器值为0时,锁就被完全释放了。

    1568083352926
    可以看到,这样就解决了资源冲突,但是出现了负数

    原因;进入循环后等待锁,资源已经执行到0了,解锁后依然执行

    解决方法:

    ? 在锁内部再做一个判断,将这种情况筛出去

    1568083589402
    等待的线程在锁池里面。解锁后第一个拿到锁标记的线程变成就绪状态,再和其他处于就绪状态的线程争抢CPU时间片

    锁的类型:

    	* 对象锁
    	* 类锁
    

    多个线程看到的锁需要时同一把,例如Synchronized()括号中不能写New对象


    同步方法

    用关键字synchronized修饰的方法就是同步方法

    更适用于逻辑比较复杂的情况

    方法一:将逻辑独立出去成为方法,在同步代码段中调用该方法

    1568085004277
    真正的同步方法:

    1568085262822
    添加synchronized关键字

    同步方法中的锁:

    ? 静态方法:同步锁就是类锁:当前类.class

    ? 非静态方法:同步锁是this

    显式锁


    1. 实例化锁对象

      ReentranLock lock=new ReentranLock();

    2. 上锁和解锁

      lock.lock();//上锁
      ···//这里写要执行的逻辑
      lock.unlock();//解锁
      

    死锁


    使用锁时不要产生死锁

    死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁,都在等待对方释放自己所需要的锁标记

    1568125678548
    运行结果:

    1568125721034
    没有线程可以同时持有A和B。另外,程序到目前为止还是没有停止的!说明这两个线程依然在执行!

    但依然有不被锁住的可能性:一个线程在获得第一个锁、抢夺第二个锁的过程中,另外一个线程一直没有获得时间片,这样就有一个线程可以执行完成了

    尽量不要让线程持有了一个锁标记后再去抢夺另外的锁

    但如果真的有这样的需求的话:

    1. wait方法:

      wait是Object类中的一个方法,表示让当前的线程释放自己的锁标记并让出CPU资源,使得当前的线程进入等待队列

    2. notify(通知)方法:

      唤醒等待队列中的一个线程,使这个线程进入锁池。但是具体唤醒哪一个线程不能由软件编写者决定,而要由CPU决定

    3. notifyAll方法

      与2相比的区别是唤醒等待队列中所有等待该锁的线程

    "A".wait();//释放已经持有的A锁标记并进入等待队列

    如果该线程没有A锁,会爆出异常:

    1568126823695
    上面的语句会出异常,应该catch:

    1568126605458
    这样可以使一个线程完成,但另外一个却不能

    解决方法是在完成的那个线程的逻辑中加入"A".notify();将等待的那个线程唤醒


    多线程环境中的单例设计模式

    单例设计模式在多线程环境下会出问题!

    懒汉式方式会出问题:多个对象会被创建而不是一个

    原因:实例化之前失去时间片,另外一个线程又创建了一遍

    解决方法1:在实例化之前加一个线程锁(对象锁)

    1568131498106
    解决方法2:将方法该我同步方法(类锁)

    1568131567224

    设计模式之三:生产者-消费者设计模式

    应用:购票-票库、就餐-后厨

    临界资源:产品

    两个重要角色:生产者与消费者

    • 生产者:生产逻辑:通过一个生产标记,判断是否要生产产品。如果要生产,则生产并通知消费者消费;如果不需要,则等待
    • 消费者:消费逻辑:通过判断是否有足够的产品可以消费,如果可以,就获取;否则就等待

    生产过程和消费过程是同时执行的,所以需要使用多线程

    1. 做一个产品类
    2. 做一个产品池类:将所有产品进行统一管理,消费和生产都要管理。存储所有的产品的集合。最后要对产品池进行实例化.临界资源就是产品池
    3. 涉及到临界资源读取的方法要改为同步方法
    4. 做一个生产者和消费者类

    代码如下:

    Program.java:

    package com.jiading;
    /*
    *@author JiaDing
    */
    public class Program {
    	public static void main(String[]args) {
    		ProductPool  pool=new ProductPool(15);
    		new Productor(pool).start();
    		new Customer(pool).start();
    	}
    }
    
    

    Product.java:

    package com.jiading;
    /*
    *@author JiaDing
    */
    public class Product {
    	private String name;
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name=name;
    	}
    	public Product(String name) {
    		this.name=name;
    	}
    	
    }
    
    

    ProductPool.java:

    package com.jiading;
    
    import java.util.LinkedList;
    import java.util.List;
    
    /*
    *@author JiaDing
    */
    public class ProductPool {
    	private List<Product> productList;
    	private int maxSize=0;
    	public ProductPool(int maxSize) {
    		this.productList=new LinkedList<Product>();
    		this.maxSize=maxSize;
    	}
    	public synchronized void push(Product product) {
    		if(this.productList.size()==maxSize) {
    			try {
    				this.wait();
    			}catch(InterruptedException e){
    				e.printStackTrace();
    			}
    		}
    		this.productList.add(product);
    		//通知其他人,有产品可以消费了
    		this.notifyAll();
    	}
    	public synchronized Product pop() {
    		if(this.productList.size()==0) {
    			try {
    				this.wait();
    			}catch(InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		Product product=this.productList.remove(0);
    		this.notifyAll();
    		return product;
    	}
    	
    }
    
    

    Productor.java:

    package com.jiading;
    /*
    *@author JiaDing
    */
    /*
     * 继承自Thread类,实现多线程
     */
    public class Productor extends Thread{
    	private ProductPool pool;
    	public Productor(ProductPool pool) {
    		this.pool=pool;
    	}
    	@Override
    	public void run() {
    		while(true) {
    			String name=(int)(Math.random()*100)+"号产品";
    			Product product=new Product(name);
    			this.pool.push(product);
    			System.out.println("生成了一件产品:"+name);
    		}
    	}
    }
    
    

    Customer.java:

    package com.jiading;
    /*
    *@author JiaDing
    */
    public class Customer extends Thread{
    	private ProductPool pool;
    	public Customer(ProductPool pool) {
    		this.pool=pool;
    	}
    	@Override
    	public void run() {
    		while(true) {
    			Product product=this.pool.pop();
    			System.out.println("消费者消费了一件产品:"+product.getName());
    		
    		}
    	}
    }
    
    

  • 相关阅读:
    C#实现函数根据返回类型重载
    自己动手实现Expression翻译器 – Part Ⅱ
    ld编译链接时默认搜索路径
    出游
    常用网络命令(转贴)
    redhat6.3企业版安装oracle11g过程
    sqlserver2000版本识别
    考IQ的推断题-生日几何?
    Microsoft Visual Studio .NET 系统必备
    101~200之间的素数
  • 原文地址:https://www.cnblogs.com/jiading/p/11705846.html
Copyright © 2020-2023  润新知