Java多线程基础知识回顾与总结

时间:2023-01-21 17:30:59

1. 单核CPU和多核CPU与多线程的关系

1.1 如何查看自己PC的CPU是几核的:

(1):启动任务管理器 ==》点性能选项卡,可以看到CPU使用记录,下面有几个框就代表有几个核心。如下双核和四核的示例:
Java多线程基础知识回顾与总结

Java多线程基础知识回顾与总结

(2): 从设备管理器中查看,

Java多线程基础知识回顾与总结

(3):更多的查看方式,读者可以参考: 如何查看自己笔记本的CPU是几核

1.2 为什么多核处理器逐渐替代了单核处理器

在说这个问题时候 我们结合处理器的发展历程进行简单的说明:
==》 1971年,英特尔推出的全球第一颗通用型微处理器4004,由2300个晶体管构成。在一块芯片上集成的晶体管数目越多,意味着运算速度即主频就更快。今天英特尔的奔腾(Pentium)四至尊版840处理器,晶体管数量已经增加至2.5亿个,相比当年的4004增加了10万倍。其主频也从最初的740kHz(每秒钟可进行74万次运算),增长到现在的3.9GHz(每秒钟运算39亿次)以上。
==》但到了2005年,当主频接近4GHz时,英特尔和AMD发现,速度也会遇到自己的极限:那就是单纯的主频提升,已经无法明显提升系统整体性能,
因为:(1) 以英特尔发布的采用NetBurst架构的奔腾四CPU为例,Netburst微架构的好处在于方便提升频率,可以让产品的主频非常高。但性能提升并不明显,频率提高50%,性能提升可能微不足道。由于流水线过长,使得单位频率效能低下,加上由于缓存的增加和漏电流控制不利造成功耗大幅度增加,3.6GHz奔腾四芯片在性能上反而还不如早些时推出的3.4GHz产品。所以,Prescott产品系列只达到3.8G,就戛然而止。
(2)随着功率增大,散热问题也越来越成为一个无法逾越的障碍。据测算,主频每增加1G,功耗将上升25瓦,而在芯片功耗超过150瓦后,现有的风冷散热系统将无法满足散热的需要。
==》 因此 在“纵向扩充”得不到满足时,人民自然想到了“横向扩充”,这样多核的时代就悄然而至,但真正的“双核元年”,则被认为是2006年。这一年的7月23日,英特尔基于酷睿(Core)架构的处理器正式发布。
多核处理器 指在一枚处理器中集成两个或多个完整的计算引擎(内核),此时处理器能支持系统总线上的多个处理器,由总线控制器提供所有总线控制信号和命令信号。
多核处理器是单枚芯片(也称为“硅核”),能够直接插入单一的处理器插槽中,但操作系统会利用所有相关的资源,将每个执行内核作为分立的逻辑处理器。通过在两个执行内核之间划分任务,多核处理器可在特定的时钟周期内执行更多任务。

1.3 多核处理器的优势

  • 对于多线程开发的程序而言:
    程序采用了线程级并行编程,那么这个程序在运行时可以把并行的线程同时交付给两个核心分别处理,因而程序运行速度得到极大提高。对于这类程序,两个物理核心和两颗处理器基本上是等价的,所以,这些程序往往可以不作任何改动就直接运行在双核电脑上。

    例如:
    (1):打开IE浏览器上网。看似简单的一个操作,实际上浏览器进程会调用代码解析、Flash播放、多媒体播放、Java、脚本解析等一系列线程,这些线程可以并行地被双核处理器处理,因而运行速度大大加快。
    (2):对于这些单线程的程序例如一些文件压缩软件、部分游戏软件等等。单独运行在多核处理器上与单独运行在同样参数的单核处理器上没有明显的差别。但是,由于日常使用的最最基本的程序——操作系统——是支持并行处理的,所以,当在多核处理器上同时运行多个单线程程序的时候,操作系统会把多个程序的指令分别发送给多个核心,从而使得同时完成多个程序的速度大大加快。

1.4 技术关键

  1. 核结构研究: 同构 VS 异构
  2. 程序执行模型:程序执行模型是编译器设计人员与系统实现人员之间的接口,编译器设计人员决定如何将一种高级语言程序按一种程序执行模型转换成一种目标机器语言程序;
    系统实现人员则决定该程序执行模型在具体目标机器上的有效实现。
  3. Cache设计:处理器和主存间的速度差距对CMP来说是个突出的矛盾,因此必须使用多级Cache来缓解。
  4. 核间通信技术:目前比较主流的片上高效通信机制有两种,一种是基于总线共享的Cache结构,一种是基于片上的互连结构。
  5. 总线设计:当多个CPU核心同时要求访问内存或多个CPU核心内私有Cache同时出现Cache不命中事件时,BIU对这多个访问请求的仲裁机制以及对外存储访问的转换机制的效率决定了CMP系统的整体性能。
  6. 操作系统设计:任务调度、中断处理、同步互斥。

    在此声明: 上面关于多核处理器的介绍是总结和简化百度百科关于多核处理器的内容,原文写的相当不错,建议有耐心的同志们好好看看:多核处理器


2. Java线程的几种状态

2.1 sleep() join() yield() 和 wait() 的比较分析

  • sleep() 和 join() 类似,我们在此只对 sleep() 方法进行分析: sleep(time)停止当前线程,但是并不释放该线程所占有的资源,尤其是对锁标识的占有,也就是说当线程调用了sleep(time)后,线程进入阻塞状态,但是线程不会释放对锁标识的占有,当线程
    睡完后,如果获得cpu的执行权,则继续向下执行,即这个阻塞状态是:具有执行资格(没有释放资源),但没有cpu的执行权(让出了cpu的执行权)。

  • wait(): 线程进入阻塞状态进入等待池中等待,并且释放资源(也就是释放了对锁标识的占有),等待notify() 的唤醒,唤醒后线程进入锁标识池重新对锁标识进行竞争。由于wait() 和
    notify()是对锁的占有和释放,因此必须出现在sychronized中,

sychronized(Lock.class){//某一线程获得锁标识,
Lock.class.wait(); //wait()是线程释放锁标识进入阻塞状态
}
Lock.class.notify(); //唤醒线程让线程重新去竞争锁标识

Java多线程基础知识回顾与总结

2.2 线程的五种状态,

参考了好多资料有说4中状态又说5种状态甚至8种状态,本人按自己的粗浅理解将线程归结为以下5种状态:

  • 创建状态 (New Thread)

在 Java 语言中使用 new操作符创建一个线程后,该线程仅仅是一个空对象,它具备了线程的一些特征,但此时系统没有为其分配资源,这时的线程处于创建状态。

  • 可运行状态 (Runnable)

使用 start()方法启动一个线程后,系统为该线程分配了除 CPU 外的所需资源,使该线程处于就绪状态。此外,如果某个线程执行了 yield() 方法,那么该线程会被暂时剥夺 CPU 资源,重新进入就绪状态。

  • 运行状态 (Running)

Java运行系统通过调度选中一个处于就绪状态的线程,使其占有 CPU 并转为运行状态。此时,系统真正执行线程的 run() 方法。

  • 阻塞状态 (Blocked)

一个正在运行的线程因某些原因不能继续运行时,它就进入阻塞状态。
这些原因包括:
当执行了某个线程对象sleep()等阻塞类型的方法时,该线程对象会被置入一个阻塞集(Blocked Pool)内,超时而时自动苏醒;
当多个线程试图进入某个同步区域(synchronized)时,没能进入该同步区域的线程会被置入锁定集(Lock Pool),直到获得该同步区域的锁,进入就绪状态;
当线程执行了某个对象的 wait() 方法时,线程会被置入该对象的等待集(Wait Pool)中,直到执行了该对象的 notify()方法,wait()/notify()方法的执行要求线程首先获取到该对象的锁。

  • 死亡状态 (Dead)

线程在 run() 方法执行结束后进入死亡状态。此外,如果线程执行了 interrupt() 或 stop() 方法,那么它也会以异常退出的方式进入死亡状态。

Java多线程基础知识回顾与总结


3. 死锁原因即死锁示例:

锁:一种sychronized 一种Lock, 以sychronized为例,要想产生死锁,肯定是多个线程相互争抢对方所持有的锁,争抢僵持,导致死锁:

  • 线程ThreadA先获取锁ThreadA.classs,然后在同步块里嵌套竞争锁ThreadB.class。而线程ThreadB先获取锁ThreadB.class,然后在同步块里嵌套竞争锁ThreadA.class.

  • 此时锁ThreadA.classs已经被线程ThreadA拥有,而ThreadA在等待ThreadB.classs,而ThreadB.classs被ThreadB拥有,ThreadB在等待ThreadA.classs……无线循环)。

示例:

package dead.lock;

/*
* 请写一个死锁实例:
* 产生死锁的前提:
* 多个线程操作,在sychronized中锁嵌套
* 产生死锁的原因:
* 两个或两个以上线程彼此等待拿到对方的锁,
* */


class ThreadA extends Thread{

ThreadA(String name){
super(name);
}

@Override
public void run() {
while(true){
synchronized(ThreadA.class){
System.out.println(Thread.currentThread().getName()+ " hava lock ThreadA.class");
synchronized(ThreadB.class){
System.out.println(Thread.currentThread().getName()+" hava lock ThreadB.class");
System.out.println(Thread.currentThread().getName()+" free Lock A and B");
}
}
System.out.println(Thread.currentThread().getName()+".....runing");
}
}

}

class ThreadB extends Thread{

ThreadB(String name){
super(name);
}

public void run() {
while(true){
synchronized(ThreadB.class){
System.out.println(Thread.currentThread().getName()+ " hava lock ThreadB.class");
synchronized(ThreadA.class){
System.out.println(Thread.currentThread().getName()+" hava lock ThreadA.class");
System.out.println(Thread.currentThread().getName()+" free Lock B and A");
}
}
System.out.println(Thread.currentThread().getName()+".....runing");
}
}

}

public class DeadLockDemo {

public static void main(String[] args) {
new ThreadA("Thread A-1 ").start();
new ThreadA("Thread A-2 ").start();
new ThreadB("Thread B-1 ").start();
new ThreadB("Thread B-2 ").start();
}

}

输出结果:

Thread A-1  hava lock ThreadA.class
Thread A-1 hava lock ThreadB.class
Thread A-1 free Lock A and B
Thread A-1 .....runing
Thread A-2 hava lock ThreadA.class
Thread A-2 hava lock ThreadB.class
Thread A-2 free Lock A and B
Thread A-2 .....runing
Thread A-2 hava lock ThreadA.class
Thread B-1 hava lock ThreadB.class

4. 生产者消费者示例分析: 死锁,代码优化

package dead.lock;

/**
* 生产和消费公共操纵的资源,并且假设生产一个消费一个
* @author Administrator
*
*/

class Resource{
private int count=1;
private boolean flag = true;//用于判断仓库中是否有产品真:有空位

public synchronized void pro(){
//while(!flag)//与notifyAll对应
if(!flag)

try {
this.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+" pro Interrupted");
}
try {
Thread.sleep(10);
flag = false;
System.out.println(Thread.currentThread().getName()+"生产了商品 .."+ count++);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒消费者线程
this.notify();//只是唤醒了最先等待的线程,不一定能唤醒对方;
//this.notifyAll();//只是唤醒了所有等待的线程
}

public synchronized void cus(){
//while(flag)//与notifyAll()对应
if(flag)
try {
this.wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+" cus Interrupted");
}
try {
Thread.sleep(5);
flag = true;
System.out.println(Thread.currentThread().getName()+"消费了商品 ...."+ (count-1));
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒生产者线程进行生产
this.notify();
//this.notifyAll();
}
}


public class ProAndCusDemo {

public static void main(String[] args) {
Resource r = new Resource();
for(int i=0;i<3;i++){
new Thread("proTh-"+i){
public void run(){
while(true)
r.pro();
}
}.start();

new Thread("cusTh-"+i){
public void run(){
while(true)
r.cus();
}
}.start();
}

}

}

输出结果:

Java多线程基础知识回顾与总结

结果分析:

Java多线程基础知识回顾与总结


5. 通过Lock 和 Condition对上述代码 sychronized 和 wait notify进行改写;

5.1 Lock 替换 synchronized

在Java1.5以后 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。我们用Lock来代替了synchronized.

需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。
如下两种代码是等价的:

class LockDemo {    
private Lock lock = new ReentrantLock();// 锁对象

public void method(int size) {
lock.lock(); // 得到锁
try {
for(int i = 0; i < size; i++) {
System.out.print("LockDemo method content!");
}
} finally {
lock.unlock();// 释放锁
}
}
}
class SynchronizedDemo {    
public void method(int size) {
sychronized(SynchronizedDemo.class) // 得到锁
{
for(int i = 0; i < size; i++) {
System.out.print("SynchronizedDemo method content!");
}
} //执行完成自动释放锁。
}
}

5.2 Condition的await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

对上例 生产消费示例进行更改如下:

package dead.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 生产和消费公共操纵的资源,并且假设生产一个消费一个
* @author Administrator
*
*/

class Resource2{
private int count=1;
private boolean flag = true;//用于判断仓库中是否有产品真:有空位

//增加的代码
Lock lock = new ReentrantLock();
Condition conPro = lock.newCondition(),
conCus = lock.newCondition();

public void pro(){
lock.lock();//关键代码
try {
while (!flag)
conPro.await();//关键代码1
flag = false;
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"生产了商品 .."+ count++);
conCus.signal();//关键代码2
} catch (InterruptedException e1) {
System.out.println(Thread.currentThread().getName()+" pro Interrupted");
} finally {
lock.unlock();//关键代码
}
}

public void cus(){
lock.lock();//关键代码
try{
while(flag)
conCus.await();//关键代码1
flag = true;
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"消费了商品 ...."+ (count-1));
conPro.signal();//关键代码2
} catch(InterruptedException e1){
System.out.println(Thread.currentThread().getName()+" cus Interrupted");
} finally{
lock.unlock();//关键代码
}
}
}


public class ProAndCusDemo2 {

public static void main(String[] args) {
Resource2 r = new Resource2();
for(int i=0;i<3;i++){
new Thread("proTh-"+i){
public void run(){
while(true)
r.pro();
}
}.start();

new Thread("cusTh-"+i){
public void run(){
while(true)
r.cus();
}
}.start();
}
}

}