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());
		
		}
	}
}


原文地址:https://www.cnblogs.com/jiading/p/11705846.html