一,单 CPU 如何同时运行多个程序
假设我们同时开了多个程序:Word,IE,QQ,Winamp,对于操作系统来说,这意味着
有四个进程要同时运行。为了解决这个问题,计算机规定了:让这四个进程轮流使用 CPU,
每个进程用一小会儿。这个“一小会儿”,往往是若干个毫秒,这段时间被称为一个“CPU
时间片”。这样,每一秒钟可能有成百上千个 CPU 时间片。从微观上来看,每一个特定的时刻,
CPU 上只有一个程序在运行,宏观上并行,微观上串行。
二,CPU 核心数
核心数是主板上实际插入的 cpu 硬件个数,一般指物理核心数,也就是硬件上存在有几个核心,也被称为内核。双核就是包括 2 个独立的 CPU 核心单元组,一核 CPU 相当于 1 个人处理数据,双核 CPU 相当于 2 个人处理同一个数据,因此处理核心数越多,CPU 的工作效率也就越高。
三,CPU 线程数
线程是程序运行流的最小单元,一个程序是由一个或多个线程组成。源于多任务处理的需要,线程数越多,越有利于同时运行多个程序,因为线程数等同于在某个瞬间 CPU 能同时并行处理的任务数。
四,CPU 核心数和线程数有什么作用
多核心和多线程的作用主要是为了满足各类程序多任务需求,核心数和线程数量越多,那么越有利同时运行多个程序,CPU 能够并行处理多个任务数量,说白点就是核心数和线程数量越多,越适合多开软件或者游戏,打开的任务越多,除了多开程序,还有渲染需求,核数和线程数越多,越有利,
一个核心一个线程,自超线程技术问世后,一个核心可以同时 2 个线程了。使 CPU 性能上升百分之 40
五,线程运行
CPU、代码、数据,是线程运行时所需要的三大条件。在这三大条件中,CPU 是操作系统分配的,Java 程序员无法控制;数据这部分,需要把握住“堆空间共享,栈空间独立”的概念;
六, Thread 类与 Runnable 接口
class MyThread1 extends Thread 重写run方法,调用start启动线程
class MyRunnable2 implements Runnable 重写run方法,调用start启动线程
七,线程状态
初始状态:当我们创建了一个线程对象,而没有调用这个线程对象的 start()方法时,此
时线程处于初始状态。
可运行状态:处于可运行状态下的线程,具有这样的特点:线程已经为运行做好了完全
的准备,只等着获得 CPU 来运行。也就是说,线程是万事俱备,只欠 CPU。
运行状态:处于这种状态的线程获得了 CPU 时间片,正在执行代码。由于系统中只有
一个 CPU,因此,同时只能有一个线程处于运行状态。当 CPU 时间片到期而线程没有执行完毕时,
操作系统会把处于运行状态的线程转换成可运行状态,然后再重新从可运行状态中选取一个
线程,为其分配 CPU 时间片。这就是运行状态和可运行状态之间的转换。
终止状态:当一个线程执行完了 run()方法中的代码,该线程就会进入终止状态。
八,sleep()与阻塞:
线程运行不仅需要 CPU 进行运算,还需要一些别的条件,例
如等待用户输入、等待网络传输完成,等等。如果线程正在等待其他的条件完成,在这种情
况下,即使线程获得了 CPU 时间片,也无法真正运行。因此,在这种情况下,为了能够不
让这些线程白白占用 CPU 的时间,会让这些线程会进入阻塞状态。最典型的例子就是等待
I/O,也就是说,如果线程需要与 JVM 外部进行数据交互的话(例如等待用户输入、读写文
件、网络传输等),这个时候,当数据传输没有完成时,线程即使获得 CPU 也无法运行,因
此就会进入阻塞状态。
如上图所示,除了等待 I/O 会进入阻塞状态之外,还可以调用 Thread 类的 sleep 方法进
入阻塞状态。
public static void sleep(long millis) throws InterruptedException
join():除了使用 sleep()和等待 IO 之外,还有一个方法会导致线程阻塞,这就是线程的 join()
方法
public final void join() throws InterruptedException //有可能出现无线等待情况
public final void join(long millis) throws InterruptedException //阻塞一定事件继续执行
九,线程同步
临界资源与数据不一致
多个线程并发访问同一个对象,如果破坏了不可分割的操作,则有可能产生数据不一致的情况。
这其中,有两个专有名词:被多个线程并发访问的对象,也被称之为“临界资源”;而
不可分割的操作,也被称之为“原子操作”。产生的数据不一致的问题,也被称之为“同步”
问题。要产生同步问题,多线程访问“临界资源”,破坏了“原子操作”,这两个条件缺一不
可。
十,synchronized 与同步代码块
Java 中采取了类似的机制,也采用锁来保护临界资源,防止数据不一致的情况产生。
在 Java 中,每个对象都拥有一个“互斥锁标记”,这就好比是我们说的挂锁。这个锁标
记,可以用来分给不同的线程。之所以说这个锁标记是“互斥的”,因为这个锁标记同时只
能分配给一个线程。光有锁标记还不行,还要利用 synchronized 关键字进行加锁的操作。synchronized 关键字有两种用法,我们首先介绍第一种:synchronized + 代码块。
这种用法的语法如下:
synchronized(obj){
代码块…
}
synchronized 关键字后面跟一个圆括号,括号中的是某一个引用,这个引用应当指向某一个对象。后面紧跟一个代码块,这个代码块被称为“同步代码块”。这种语法的含义是,如果某一个线程想要执行代码块中的代码,必须要先获得 obj 所指向对象的互斥锁标记。也就是说,如果有一个线程 t1 要想进入同步代码块,必须要获得 obj对象的锁标记;而如果 t1 线程正在同步代码块中运行,这意味着 t1 有着 obj 对象的互斥锁标记;而这个时候如果有一个 t2 线程想要访问同步代码块,会因为拿不到 obj 对象的锁标记而无法继续运行下去。
class MyStack{
char[] data = {'A', 'B', ' '};
int index = 2;
private Object lock = new Object();
//存入数据 push和pop不会同时执行,只有一个线程可以获得lock对象的锁
public void push(char ch){
synchronized(lock){
data[index] = ch;
try{
Thread.sleep(1000);
}catch(Exception e){}
index ++;
}
}
//取出数据
public void pop(){
synchronized(lock){
index --;
data[index] = ' ';
}
}
}
同步方法
死锁
wait 与 notify