复习:进程与线程

复习:进程与线程

进程

概念:

进程:在操作系统中,能够独立运行,并且作为资源分配的基本单位。它表示运行中的程序。

时间片:

操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。

任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。

并发与并行:

并发:多个进程在一 个CPU下采用时间片轮转的方式,在一段时间之内,让多个进程都得以推进,称之为并发。

表现形式:人肉眼看起来,所有进程都是在“同时”执行。但是,在CPU层面看到的是,进程不停的轮流执行的(时间片轮转调度的方式)--------(假同时)【单个cpu执行多任务在一个时间范围以时间片轮转调度方式】

并行:多个进程在多个CPU下分别,同时进行运行,这称之为并行。

表现形式:在一个时间点上,可以同时运行多个进程-------(真同时)】多CPU-个时间点同时执行多任务】

内核态、用户态

一般的操作系统(如Windows、Linux)对执行权限进行分级:用户态和内核态。
在这里插入图片描述

  • 操作系统内核作为直接控制硬件设备的底层软件,权限最高,称为内核态,或核心态。 CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡.
  • 用户程序的权限最低,称为用户态。 CPU可以访问内存所有数据, 包括外围设备, 例如硬盘, 网卡.

进程状态圈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R6R0TFcg-1595152599115)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1595143660535.png)]

  • 就绪:进程处于可运行的状态,只是CPU时间片还没有轮转到该进程,则该进程处于就绪状态。
  • 运行:进程处于可运行的状态,且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 {@Overridepublic void run() { 

		System.out.println(Thread.currentThread().getName() + "这里是线程运行的代码"); 

  	 } 

} 

Thread t = new Thread(new MyRunnable());  

t.start(); // 线程开始运行


线程api

start()和run()方法:

  1. run方法直接调用,不会启动线程,只是在当前main线程中,调用了run方法
  2. 线程启动时通过start方法启动的
public class Main{
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        //myThread.start();
        //run方法直接调用,不会启动线程,只是在当前main线程中,调用了run方法
        myThread.run();
        //new MyThread().start();
        new Thread(new MyThread()).start();//线程启动时通过start方法启动的
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        //run是线程运行的时候执行的代码块
        System.out.println(Thread.currentThread().getName());
    }
}

进程的退出:

  1. 至少有一个非守护线程没有被销毁,进程就不会退出。
  2. 非守护线程一般可以成为工作线程,守护线程可以称为后台线程

setDaemon(true):设置线程为守护线程

public class Daemon {
    public static void main(String[] args) {
        Thread t=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(9999999999L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //设置线程为守护线程
        t.setDaemon(true);
        //t.start();
    }
}

线程让步–yield()方法

将当前线程由运行态—>就绪态

public class ThreadYield {
    public static void main1(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();
//        while (Thread.activeCount()>1){
//            Thread.yield();//将当前线程有运行态--->就绪态
//        }
        System.out.println(Thread.currentThread().getName());
    }
}

线程的等待–join()方法:

当前线程:代码执行时,所在的线程

当前线程阻塞(运行态–>阻塞态)等待(满足一定条件),t线程(不做限制,自由调度)一定条件

以下条件哪个先执行完,就满足

  1. 传入时间(时间值+时间单位毫秒)
  2. 线程引用对象执行完毕
public static void without() throws InterruptedException {
    Thread t=new Thread(new Runnable() {
        @Override
        public void run() {
                System.out.println(Thread.currentThread().getName());
        }
    });
    t.start();
    t.join();//当前线程等待,直到t线程执行完毕
    System.out.println(Thread.currentThread().getName());
}
public static void with() throws InterruptedException {
    Thread t=new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(5000);
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.start();
    t.join(2000);
    //当前线程main线程等待2秒钟就往下执行了(t线程等待时间更长)
    System.out.println(Thread.currentThread().getName());
}

线程的等待–结合activeCount()+yield()方法:

//等待new Thread所有线程执行完毕,否则一直等待    
while (Thread.activeCount()>1){
        Thread.yield();//将当前线程有运行态--->就绪
}

线程中断:

  1. 通过 thread 对象调用 interrupt() 方法通知该线程停止运行 (初始为false)
  2. thread 收到通知的方式有两种:
    1. 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除 中断标志
    2. 否则,只是内部的一个中断标志被设置,thread 可以通过
      • Thread.interrupted() 判断当前线程的中断标志被设置,重置中断标志
      • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

不是真实的直接中断,而是告诉某个线程,需要进行中断,具体是否要中断,由该线程自己来决定

    public static void main1(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
        //线程运行时,需要自行判断线程中断标志位
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println();
                }
                while (!Thread.interrupted()){
                    System.out.println(Thread.currentThread().getName());
                }
            }
        });
        thread.start();//中断标志位=false
        thread.interrupt();//中断标志位=true
    }
Thread thread=new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().isInterrupted());
            Thread.sleep(30000);
            //线程调用wait()/join()/sleep()方法阻塞当前线程,会直接抛出异常
            //阻塞状态是,通过捕获及处理异常,来处理线程的中断逻辑
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
            //捕获到异常之后,线程的中断标志位,被重置
            System.out.println(Thread.currentThread().isInterrupted());
        }
    }
});
thread.start();//中断标志位=false
thread.interrupt();//中断标志位=true

获取当前线程的引用:public static Thread currentThread();

休眠线程:public static void sleep(long millis) throws InterruptedException

线程安全

不安全的原因:

1.原子性

  • 我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还 没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
  • 那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样 就保证了这段代码的原子性了。
  • 一条 java 语句不一定是原子的,也不一定只是一条指令
  • 比如我们看到的 n++,其实是由三步操作组成的:
    1. 从内存把数据读到 CPU
    2. 进行数据更新
    3. 把数据写回到 CPU
  • 比如new对象操作:
    1. 分配对象的内存
    2. 初始化对象
    3. 将对象赋值给变量

2.可见性

​ 为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线 程之间不能及时看到改变,这个就是可见性问题。
在这里插入图片描述

3.代码顺序性

一段代码是这样的:

  1. 去前台取下 U 盘
  2. 去教室写 10 分钟作业
  3. 去前台取下快递

如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑 一次前台。这种叫做指令重排序

synchronize
  1. synchronized的底层是使用操作系统的mutex lock实现的
  2. 当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
  3. 运行的线程数量越多,性能下降越快(归还对象锁的时候,就有越多的线程不停的在被唤醒、阻状态切换)
  4. 同步代码执行时间越短,性能下降也较快
volatile
  1. 保证可见性
  2. 保证有序性

使用场景:可以结合线程加锁的一些手段,提高线程效率

private static  volatile int count1=0;
public static void main(String[] args) {

    //同时启动20个线程,每个线程对同一个变量执行操作,循环10000次,每次循环++操作
    for(int i=0;i<20;i++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int j=0;j<10000;j++){

                        synchronized (volatileThread.class){
                            if(count<100000){
                                count++;
                            }
                        }

                }
            }
        }).start();
    }
    while (Thread.activeCount()>1){
        Thread.yield();
    }
    System.out.println(count);
}

线程通信

  1. wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
  2. notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的 线程。
  3. wait(long timeout)让当前线程处于“等待(阻塞)状态”,直到其他线程调用此对象的notify()方法或 notifyAll()方法,或者超过指定的时间量,当前线程被唤醒(进入“就绪状态”)。

wait()方法

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

notify()方法

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

  1. 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对 其发出通知notify,并使它们重新获取该对象的对象锁。如果有多个线程等待,则有线程规划器随机挑选出一个呈wait状态的线程。
  2. 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

notifyAll()方法

  • 使用notifyAll()方法唤醒所有等待线程

代码举例:

1.while来判断,不要使用if(因为在判断代码中进行wait释放锁以后,其他线程会修改变量,再次wait被通知恢复的时候,条件已经不满足了)
2.使用notifyAll方法,通知所有wait被阻塞的线程

public class Sington {
    //库存面包数量:上限100
    public static volatile int sum;

    public static void main(String[] args) {
        //启动5个生产者,生产面包
        for(int i=0;i<5;i++){
            new Thread(new Producer(),"面包师傅"+i).start();

        }
        //启动消费者线程,消费面包
        for(int i=0;i<20;i++){
            new Thread(new Consumer(),"消费者"+i).start();
        }
    }
    //默认生产者:面包师傅生产面包:一次生产3个面包,每个面包师傅生产20次
    private static class Producer implements Runnable{
        @Override
        public void run() {
            for(int i=0;i<20;i++){
                synchronized (Sington.class){
                    //生产完后,库存大于100不行,所以库存在97以上不能生产
                    while (sum+3>100){
                        //释放对象锁,需要让其他线程进入同步代码块,当前线程需要进入阻塞状态
                        try {
                            Sington.class.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sum+=3;//生产面包
                    Sington.class.notify();
                    System.out.println(Thread.currentThread().getName()+",生产了,库存为:"+sum);
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //默认消费者:消费面包:一次消费一个面包,消费者一直消费
    private static class Consumer implements Runnable{
        @Override
        public void run() {
            while (true){
                synchronized (Sington.class){
                    //库存为0,不能继续消费,阻塞当前线程
                    //不能用if判断,
                    while (sum==0){
                        try {
                            Sington.class.wait();
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    sum--;
                    //notify和notifyAll都是通知调用wait()被阻塞的线程
                    //notify随机唤醒一个wait()阻塞的线程
                    //notifyAll唤醒全部wait()阻塞的线程
                    //在synchronized代码块结束,也就是释放对象锁之后,才会唤醒
                    //等于说,synchronized结束之后,wait()和synchronized代码行阻塞的线程,都会被唤醒

                    Sington.class.notifyAll();
                    //最好是notifyAll,唤醒所有
                    System.out.println(Thread.currentThread().getName()+",消费了,库存为:"+sum);
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

单例模式

单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

饿汉模式

class Singleton {  

 	private static Singleton instance = new Singleton();  

 	private Singleton() {}  

 	public static Singleton getInstance() {  

 		return instance;  

	}  

}

懒汉模式

class Singleton {  

     private static Singleton instance = null;  

     private Singleton() {}  

     public static Singleton getInstance() {  

         if (instance == null) {  

         instance = new Singleton();  

         }  

         return instance;  

     }  

} 

懒汉模式—双重校验锁

class Singleton {  
    private static volatile Singleton instance = null;  
    private Singleton() {}  
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                if (instance == null) {  
                    instance = new Singleton();  
                }  
            }  
        } 
        return instance;  
    }  
} 

线程池

线程池最大的好处就是减少每次启动、销毁线程的损耗

创建线程池:

1.ThreadPoolExecutor()方法参数意义:

ExecutorService pool = new ThreadPoolExecutor(//线程池---快递公司
                3,// 核心线程数(正式员工):创建好线程池,正式员工就开始取快递

                // 临时工雇佣:正式员工忙不过来,就会创建临时工
                // 临时工解雇:空闲时间超出设置的时间范围,就解雇
                5,// 最大线程数(最多数量的员工:正式员工+临时工)

                30,// 时间数量
                TimeUnit.SECONDS,// 时间单位(时间数量+时间单位表示一定范围的时间)

                // 阻塞队列:存放包裹的仓库(存放任务的数据结构)
                new ArrayBlockingQueue<>(1000),

                // (了解)线程池创建Thread线程的工厂类。没有提供的话,就使用线程池内部默认的创建线程的方式
//                new ThreadFactory() {
//                    @Override
//                    public Thread newThread(Runnable r) {
//                        return null;
//                    }
//                },

                // 拒绝策略:
                // CallerRunsPolicy:谁(execute代码行所在的线程)让我(快递公司)送快递,不好意思,你自己去送
                // AbortPolicy:直接抛出异常RejectedExecutionException
                // DiscardPolicy:从阻塞队列丢弃最新的任务(队尾)
                // DiscardOldestPolicy:从阻塞队列丢弃最旧的任务(队首)
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
  1. Executors.newxxx()方法
    ExecutorService pool = Executors.newSingleThreadExecutor();//线程池的员工就是1个
    ExecutorService pool = Executors.newFixedThreadPool(4);//正式员工数量为4,没有临时工
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(4);//正式员工
    ExecutorService pool = Executors.newCachedThreadPool();//正式员工为0,临时工数量不限制

线程的优点

  1. 创建一个新线程的代价要比创建一个新进程小得多

  2. 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多

  3. 线程占用的资源要比进程少很多

  4. 能充分利用多处理器的可并行数量

  5. 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务

  6. 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现

  7. I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值