JAVA线程
线程
串行和并发
进程之间资源不共享,所以在程序中一般不单独开辟进程
线程是一个任务执行的最小单元
线程的并发和进程是一样的,也是CPU通过中断进行“假并发”
多个线程同时访问的资源叫临界资源
线程的状态
题外话:时间片
时间片(timeslice)又称为“量子(quantum)”或“处理器片(processor slice)”是分时操作系统分配给每个正在运行的进程微观上的一段CPU时间(在抢占内核中是:从进程开始运行直到被抢占的时间)。时间片通常很短(在Linux上为5ms-800ms)
时间片由操作系统内核的调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片(在Linux系统中,初始时间片也不相等,而是各自父进程的一半),然后每个进程轮番地执行相应的时间,当所有进程都处于时间片耗尽的状态时,内核会重新为每个进程计算并分配时间片,如此往复。
系统通过测量进程处于“睡眠”和“正在运行”状态的时间长短来计算每个进程的交互性,交互性和每个进程预设的静态优先级(Nice值)的叠加即是动态优先级,动态优先级按比例缩放就是要分配给那个进程时间片的长短。一般地,为了获得较快的响应速度,交互性强的进程(即趋向于IO消耗型)被分配到的时间片要长于交互性弱的(趋向于处理器消耗型)进程。
抢占式多任务处理(Preemption)是计算机操作系统中,一种实现多任务处理(multi task)的方式,相对于协作式多任务处理而言。协作式环境下,下一个进程被调度的前提是当前进程主动放弃时间片;抢占式环境下,操作系统完全决定进程调度方案,操作系统可以剥夺耗时长的进程的时间片,提供给其它进程。
- 每个任务赋予唯一的一个优先级(有些操作系统可以动态地改变任务的优先级);
- 假如有几个任务同时处于就绪状态,优先级最高的那个将被运行;
- 只要有一个优先级更高的任务就绪,它就可以中断当前优先级较低的任务的执行;
线程的概念
一个线程就是一个程序内部的顺序控制流
线程和进程的区别
java.lang.Thread模拟CPU实现线程,所要执行的代码和处理的数据要传递给Thread类。
构造线程的方法:线程实例化的两种方式
- 法一:线程实例化
-
继承Thread类,做一个线程子类(自定义的线程类)
-
重写run方法,将需要并发执行的任务写到run中
-
线程开辟需要调用start方法,使线程启动,来执行run中的逻辑(如果直接调用run方法,这不会开辟新线程,或者线程不会进入就绪态,没有实现多线程)
多线程的效果:
主线程先结束,再执行逻辑这种方式可读性更强
缺点:由于JAVA的单继承,则该类无法再继承其他类,会对原有的继承结构造成影响
-
法二:通过Runnable接口
用Runnable”声明“了对象之后,要真正的实例化Thread对象,即Thread t2=new Thread(r1);这个语句不影响原有继承关系
缺点:可读性较差
线程的常用操作
-
线程的命名
-
法一:使用setName命名
-
法二:在构造方法中直接命名
-
自定义一个类,继承Thread,添加一个包括名字参数的构造方法
-
-
线程的休眠
线程休眠方法:
Thread.sleep();//参数是以毫秒为单位的时间差
休眠会抛出一个InterruptedException异常,我们需要捕获它线程休眠可以使一个线程由运行状态变成阻塞态,休眠时间结束后回到就绪态,如果成功获得时间片就变成运行状态
线程的优先级
设置线程的优先级,只是修改这个线程可以去抢到CPU时间片的概率,并不是说优先级高的线程就一定能抢到CPU时间片
? 优先级是一个0-10的整数,默认是5
? 设置优先级必须放到这个线程开始执行(即Start)之前
? 题外话:Thread.currentThread().getName();//获取当前线程
? 线程之间都是交替执行的,并不是优先级高的先执行完再执行优先级低的
在主方法中:
线程的礼让(Yield)
礼让:由执行到就绪态,即让当前运行状态的线程释放自己的CPU资源
注意,放弃当前CPU时间片后,该线程和其他线程一起争抢时间片,依然有可能抢到,所以礼让的结果不一定是运行其他线程
yield是一个静态方法,直接Thread.yield();就可
示例:
注意看,无论是线程1还是线程2,执行到3时都进行了礼让,让另一个线程执行(这是比较极端的状态)
线程中的临界资源问题
被多个线程要同时访问的资源是临界资源
多个线程同时访问一个临界资源会出现临界资源问题,即临界资源调用出错
错误示例:
执行结果:
可以看到,这里对于restCount的调用是有问题的
错误原因
每个线程在字符串拼接、输出之前时间片就被抢走,另一个线程访问的是原来的线程做减法之后的restCount,最后谁先输出不一定。
我们可以看到,这种差异是非常明显的:当线程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时,锁就被完全释放了。
可以看到,这样就解决了资源冲突,但是出现了负数
原因;进入循环后等待锁,资源已经执行到0了,解锁后依然执行
解决方法:
? 在锁内部再做一个判断,将这种情况筛出去
等待的线程在锁池里面。解锁后第一个拿到锁标记的线程变成就绪状态,再和其他处于就绪状态的线程争抢CPU时间片
锁的类型:
* 对象锁
* 类锁
多个线程看到的锁需要时同一把,例如Synchronized()括号中不能写New对象
同步方法
用关键字synchronized修饰的方法就是同步方法
更适用于逻辑比较复杂的情况
方法一:将逻辑独立出去成为方法,在同步代码段中调用该方法
真正的同步方法:
添加synchronized关键字
同步方法中的锁:
? 静态方法:同步锁就是类锁:当前类.class
? 非静态方法:同步锁是this
显式锁
-
实例化锁对象
ReentranLock lock=new ReentranLock();
-
上锁和解锁
lock.lock();//上锁 ···//这里写要执行的逻辑 lock.unlock();//解锁
死锁
使用锁时不要产生死锁
死锁:多个线程彼此持有对方所需要的锁对象,而不释放自己的锁,都在等待对方释放自己所需要的锁标记
运行结果:
没有线程可以同时持有A和B。另外,程序到目前为止还是没有停止的!说明这两个线程依然在执行!
但依然有不被锁住的可能性:一个线程在获得第一个锁、抢夺第二个锁的过程中,另外一个线程一直没有获得时间片,这样就有一个线程可以执行完成了
尽量不要让线程持有了一个锁标记后再去抢夺另外的锁
但如果真的有这样的需求的话:
-
wait方法:
wait是Object类中的一个方法,表示让当前的线程释放自己的锁标记并让出CPU资源,使得当前的线程进入等待队列
-
notify(通知)方法:
唤醒等待队列中的一个线程,使这个线程进入锁池。但是具体唤醒哪一个线程不能由软件编写者决定,而要由CPU决定
-
notifyAll方法
与2相比的区别是唤醒等待队列中所有等待该锁的线程
"A".wait();//释放已经持有的A锁标记并进入等待队列
如果该线程没有A锁,会爆出异常:
上面的语句会出异常,应该catch:
这样可以使一个线程完成,但另外一个却不能
解决方法是在完成的那个线程的逻辑中加入"A".notify();
将等待的那个线程唤醒
多线程环境中的单例设计模式
单例设计模式在多线程环境下会出问题!
懒汉式方式会出问题:多个对象会被创建而不是一个
原因:实例化之前失去时间片,另外一个线程又创建了一遍
解决方法1:在实例化之前加一个线程锁(对象锁)
解决方法2:将方法该我同步方法(类锁)
设计模式之三:生产者-消费者设计模式
应用:购票-票库、就餐-后厨
临界资源:产品
两个重要角色:生产者与消费者
- 生产者:生产逻辑:通过一个生产标记,判断是否要生产产品。如果要生产,则生产并通知消费者消费;如果不需要,则等待
- 消费者:消费逻辑:通过判断是否有足够的产品可以消费,如果可以,就获取;否则就等待
生产过程和消费过程是同时执行的,所以需要使用多线程
- 做一个产品类
- 做一个产品池类:将所有产品进行统一管理,消费和生产都要管理。存储所有的产品的集合。最后要对产品池进行实例化.临界资源就是产品池
- 涉及到临界资源读取的方法要改为同步方法
- 做一个生产者和消费者类
代码如下:
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());
}
}
}