操作系统进程与线程(多线程).

操作系统

操作系统(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()方法就是使线程停止运行。

  1. 方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法是用来将当前线程 置入“预执行队列”中,并且在wait()所在的代码处停止执行,直到接到通知或被中断为止。
  2. wait()方法只能在同步方法中或同步块中调用。如果调用wait()时,没有持有适当的锁,会抛出异常。
  3. wait()方法执行后,当前线程释放锁,线程与其它线程竞争重新获取锁。

wait()与sleep()的比较:

1.wait 之前需要请求锁,而wait执行时会先释放锁,等被唤醒时再重新请求锁。这个锁是 wait 对像上的 monitor lock
2.sleep 是无视锁的存在的,即之前请求的锁不会释放,没有锁也不会请求。
3.wait 是 Object 的方法
4. sleep 是 Thread 的静态方法

notify()方法:
notify方法就是使停止的线程继续运行。

  1. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个 呈wait状态的线程。
  2. 在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为每隔这么久执行一次
    }
}

线程与线程的对比

线程的优点:

  1. 创建一个新线程的代价要比创建一个新进程小得多
  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  3. 线程占用的资源要比进程少很多
  4. 能充分利用多处理器的可并行数量
  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

区别:

1). 进程是系统进行资源分配和调度的一个独立单位,线程是程序执行的最小单位。
2). 进程有自己的内存地址空间,线程只独享指令流执行的必要资源,如寄存器和栈。
3). 由于同一进程的各线程间共享内存和文件资源,可以不通过内核进行直接通信。
4). 线程的创建、切换及终止效率更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值