操作系统
操作系统(Operating System,简称OS)是管理计算机硬件与软件资源的计算机程序。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
简单来说:操作系统就是多个基本程序的集合,包括内核(进程管理,内存管理,文件管理和驱动管理)和其他程序
进程
对操作系统来说,一个任务就是一个进程(Process).
进程是担当分配系统资源(CPU时间,内存)的实体,是具有动态特性的.
是系统资源分配和调度的基本单位.
这其中每一个都代表一个进程,java虚拟机本质上也就是一个进程而已.
时间片
操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时 间后强制暂停去执行下一个任务,每个任务轮流执行。
任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个 任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。
并发与并行
并发:多个进程在一个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进,称之为并 发。
并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。
假设每个鱼篓称为每个进程,每个鱼竿称为处理任务的cpu,只用一根鱼竿将所有鱼篓同时装满为并发,每个鱼竿负责将每个鱼篓装满,同时进行叫并行.
状态
一般的操作系统(如Windows、Linux)对执行权限进行分级:用户态和内核态。
用户态: 用户程序的权限最低,称为用户态.
内核态:操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态
进程的上下文
上下文简单说来就是一个环境,进程在时间片轮转切换时,由于每个进程运行环境不同,就涉及到转换前后的上下文 环境的切换.就是一个进程在执行的时候,CPU的所有寄存器中的值、进程的状态以及堆栈上的内容。 切换时需要保存当前进程的所有状态,即保存当前进程的进程上下文,以便再次执行该进程时,能够恢复切换 时的状态,继续执行。
进程的状态
就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
运行:进程处于可运行的状态,且CPU时间片轮转到该进程,该进程正在执行代码,则该进程处于运行状态。
阻塞:进程不具备运行条件,正在等待某个事件的完成。
线程与多线程
线程是任务调度和执行的基本单位.
每个进程中至少有一个线程.称为主线程.一个进程中的多个线程是资源共享的.
线程与进程基本相似,只是进程基于操作系统而言,线程基于进程而言
创建线程
1.继承Thread类
可以通过继承 Thread 来创建一个线程类,该方法的好处是 this 代表的就是当前线程,不需要通过 Thread.currentThread() 来获取当前线程的引用。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
}
}
MyThread t = new MyThread();
t.start(); // 线程开始运行
2.实现Runnable接口
通过实现 Runnable 接口,并且调用 Thread 的构造方法时将 Runnable 对象作为 target 参数传入来创建线程对象。 该方法的好处是可以规避类的单继承的限制;但需要通过 Thread.currentThread() 来获取当前线程的引用
class MyRunnable implements Runnable {
@Override
public void run() { System.out.println(Thread.currentThread().getName() + "这里是线程运行的代码");
}
}
Thread t = new Thread(new MyRunnable());
t.start(); // 线程开始运行
3.实现callable接口
通过实现callable接口的方式,可以创建一个线程,需要重写其中的call方法。启动线程时,需要新建一个Callable的实例,再用FutureTask实例包装它,最终,再包装成Thread实例,调用start方法启动
public static void test1()throws ExecutionException,InterruptedException {
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+":call");
return 1;
}
};
FutureTask<Integer> task=new FutureTask<>(callable);
new Thread(task).start();
三种方法方法的不同
1:需要实现的方法名称不一样:两个是run方法,一个call方法
2:返回值不同:两个是void无返回值,一个带有返回值的。其中返回值的类型和泛型V是一致的。
3:异常:两个是无需抛出异常,一个需要抛出异常。
Thread类及常用方法
常见构造方法
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread(“这是我的名字”);
Thread t4 = new Thread(new MyRunnable(), “这是我的名字”);
常见获取属性
启动一个线程:
start();
中断一个线程:
一. 通过 thread 对象调用 interrupt() 方法通知该线程停止运行.
二.thread 收到通知的方式有两种:
1).如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除 中断标志
2).否则,只是内部的一个中断标志被设置,thread 可以通过
(1).Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志
(2).Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志
这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
public static void main(String[] args)throws InterruptedException {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
try {
//调用sleep(),wait(),join()方法时,线程进入阻塞状态
//此时也是可以中断的,中断后抛出InterruptedException
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
//优势在于可以中断wait(),join(),sleep()的阻塞线程
thread.interrupt();
}
结果:
public static void main(String[] args)throws InterruptedException {
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
boolean b=Thread.interrupted();
//获取当前标志位
// boolean b=Thread.currentThread().isInterrupted();
System.out.println(b);
}
}
});//线程创建之后的标志位为false
thread1.start();
thread1.interrupt();//中断线程 //修改标志位为true
}
结果:当前线程被中断设置,清楚中断标志位
Thread thread1=new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
// boolean b=Thread.interrupted();
//获取当前标志位
boolean b=Thread.currentThread().isInterrupted();
System.out.println(b);
}
}
});//线程创建之后的标志位为false
thread1.start();
thread1.interrupt();//中断线程 //修改标志位为true
}
结果:当前线程被中断设置,中断标志位不清除
等待一个线程:
join() 等待线程结束
join(long milis)等待线程结束,最多等多少秒
获取当前线程应用:
currentThread() 获取当前线程引用
休眠当前线程:
sleep(long milis) 休眠线程多少毫秒
yield()方法:
使当前线程让出cpu,又去后面排队获取cpu
线程安全问题
线程安全简单的来说就是:如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
那么造成线程不安全的原因有:
1.原子性问题
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还 没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样 就保证了这段代码的原子性了。
有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
一条 java 语句不一定是原子的,也不一定只是一条指令
比如刚才我们看到的 n++,其实是由三步操作组成的: 1. 从内存把数据读到 CPU 2. 进行数据更新 3. 把数据写回到 CPU
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
2.可见性问题
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线 程之间不能及时看到改变,这个就是可见性问题
3.顺序性(指令重排序)
在多线程中,当一个线程正要执行一条指令时,另外一个线程又对其做了改变
例如:两个读指令对一个常量进行读操作,这样结果不会改变.但如果有一个是写操作呢.
当一个线程对一个常量(a=0)进行写a=2,此时在另一个线程要将a赋值给b,则结果b的值就为2,不为0;
synchronized关键字:
synchronized的底层是使用操作系统的mutex lock实现的。
当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主内存中
当线程获取锁时,JMM会把该线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主内存中读取共享变量
synchronized用的锁是存在Java对象头里的。
synchronized同步快对同一条线程来说是可重入的,不会出现自己把自己锁死的问题;
同步块在已进入的线程执行完之前,会阻塞后面其他线程的进入。
public class SynchronizedDemo {
public synchronized void methond() { }
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method(); // 进入方法会锁 demo 指向对象中的锁;出方法会释放 demo 指向的对象中的锁
} }
public class SynchronizedDemo {
public void methond() { // 进入代码块会锁 SynchronizedDemo.class 指向对象中的锁;出代码块会释放 SynchronizedDemo.class 指向的对象中的锁
synchronized (SynchronizedDemo.class) {
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
}
synchronized 是具有原子性,可见性,有序性的
synchronized可以修饰静态方法、成员函数,同时还可以直接定义代码块,但是归根结底它上锁的资源只有两类:一个是对象,一个是类。
volatile关键字:
修饰的共享变量,可以保证可见性,部分保证顺序性(禁止指令重排序,建立内存屏障)
内存屏障和volatile什么关系?
如果你的字段是volatile,Java内存模型将在写操作后插入一个写屏障 指令,在读操作前插入一个读屏障指令。这意味着如果你对一个volatile字段进行写操作,你必须知道:1、一旦你完成写入,任何访问这个字段的线程将 会得到最新的值。2、在你写入前,会保证所有之前发生的事已经发生,并且任何更新过的数据值也是可见的,因为内存屏障会把之前的写入值都刷新到缓存
wait()方法:
其实wait()方法就是使线程停止运行。
- 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程 置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。
- wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
- wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。
wait()与sleep()的比较:
1.wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对像上的 monitor lock
2.sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求。
3.wait 是 Object 的方法
4. sleep 是 Thread 的静态方法
notify()方法:
notify方法就是使停止的线程继续运行。
- 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个 呈wait状态的线程。
- 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出 同步代码块之后才会释放对象锁。
notifyAll()方法:
唤所有在等待的线程.
定时器
简单使用:
(1)第一步:创建一个Timer。
(2)第二步:创建一个TimerTask
(3)第三步:使用Timer执行TimerTask
public class TimerTest {
public static void main(String[] args) {
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("哈哈");
}
},3000,3000);//第一个3000为首次执行时间,后面3000为每隔这么久执行一次
}
}
线程与线程的对比
线程的优点:
- 创建一个新线程的代价要比创建一个新进程小得多
- 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
- 线程占用的资源要比进程少很多
- 能充分利用多处理器的可并行数量
- 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
- 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
- I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。
区别:
1). 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
2). 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
3). 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
4). 线程的创建、切换及终止效率更高。