java回忆录——进程和线程

Posted minigeek

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java回忆录——进程和线程相关的知识,希望对你有一定的参考价值。

今天我们来讲解java中的第二大块——多线程。

要想了解多线程,必须先了解线程,而要想了解线程,必须先了解进程,因为线程是依赖于进程而存在的。

什么是进程?

通过任务管理器我们就看到了进程的存在。

而通过观察,我们发现只有运行的程序才会出现进程。

进程:就是正在运行的程序

 进程是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。

多进程有什么意义?

单进程的计算机只能做一件事情,而我们现在的计算机可以做多件事情

也就是说现在的计算机都是支持多进程的,可以在同一时间内执行多个任务

例子:

一边玩游戏,一边听音乐是同时进行的吗?(进程是并行)

答:不是,因为单cpu在某一个时间点是只能做一件事情
而我们在玩游戏或者听音乐的时候,是cpu在做着程序间的高效切换。

什么是线程?

在一个进程内又可以执行多个任务。每一个任务就可以看做是一个线程。

   线程是程序的执行单元。

   单线程:如果程序只有一条执行路径

   多线程:如果程序有多条执行路径

多线程有什么意义?

多线程的存在,不是提高程序的执行速度,其实是为了提高应用程序的使用率

   程序的执行其实就是再抢CPU的资源,CPU的执行权

多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多,就会有更高的几率抢到CPU的执行权

我们是不能保证每一个进程或者每一个线程在每个时刻抢到,所以线程的执行有随机性。

并行和并发

前者是逻辑上同时发生,指在某一个时间内同时运行多个程序。(多进程)

后者是物理上同时发生,指在某一个时间点同时运行多个程序。(多线程)

Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。

该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。

问题:jvm虚拟机的启动是单线程的还是多线程的?

答:多线程的。

原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。

现在的垃圾回收线程加上前面的主线程,最少启动了两个线程,所以,jvm的启动其实是多线程的。

多线程的实现方案

方案1、继承Thread类

A:自定义类MyThread继承Thread类。        

B:MyThread类里面重写run()。

C:创建对象

D:启动线程

方案2、实现Runnable接口

A:自定义类MyRunnable实现Runnable接口

B:创建MyRunnable对象

C:创建线程对象,并传入Runnable对象

D:启动线程

问题:

实现接口方式的有什么好处?

答:

1.可以避免由于Java单继承带来的局限性。

2.适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想

问题:

1、myThread.run()和myThread.start()方法的区别?

run():仅仅是封装被线程执行的代码,直接调用是普通方法

start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

2、线程能不能多次启动?

答:不能

如何获取和设置线程名称

可以看到Thread的方法:

public final String getName()

public final void setName(String name)

所以我们可以通过调用Thread的setName(String name)方法来设置线程名字。

当然我们也可以在创建线程对象的时候传入一个参数(该参数即为线程名字)

如:MyThread myThread = new MyThread(“线程一”);

问题:

如何获取main方法所在的线程名称呢?

答:public static Thread currentThread()

public final String getName()

这样就可以获取任意方法所在的线程名称

线程调度

假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

线程有两种调度模型:

   分时调度模型   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

   抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 

   Java使用的是抢占式调度模型。

如何设置和获取线程优先级

public final int getPriority()
public final void setPriority(int newPriority)

    线程默认优先级是5。

    线程优先级的范围是:1-10。

    线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

线程控制

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

线程加入,等待该线程终止。
public final void join()

线程礼让
public static void yield()
让多个线程的执行更和谐,但是不能靠它保证一人一次。

后台线程
public final void setDaemon(boolean on)
按道理的确是主线程结束,守护线程也都结束,但主线程销毁运行时环境,kill掉守护线程这都是需要时间的

中断线程
public final void stop()
让线程停止,过时了,但是还可以使用。

public void interrupt()
中断线程。 把线程的状态终止,并抛出一个InterruptedException。

线程的生命周期

例子:

多个窗口(4个窗口模拟4个线程)售票。

SellTicketRunnable.java

public class SellTicketRunnable implements Runnable

    private int ticket = 100;
    @Override
    public void run() 
        while (true) 

                if (ticket > 0) 
                    System.out.println(Thread.currentThread().getName()+"正在售第"+ticket--+"张票");
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                

        
    

SellTicketRunnableTestDemo.java

public class SellTicketRunnableTestDemo 

    public static void main(String[] args) 

        SellTicketRunnable str = new SellTicketRunnable();
        Thread t1 = new Thread(str,"A售票口");
        Thread t2 = new Thread(str,"B售票口");
        Thread t3 = new Thread(str,"C售票口");
        Thread t4 = new Thread(str,"D售票口");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    

结果:

我们可以看到不用的窗口正在销售相同的票,这显然是不对,本来只有100张票,这样一来就有400张票了。我们希望的状态是:当有窗口在售票的时候,其他窗口不能访问到正在销售的票。

其实还会出现这样的情况:可能会正在出售负数的票。

那我们来分析一下为什么会这样子呢?

首先我们得知道:CPU的一次操作必须是原子性的(最简单基本的)

这个程序模拟售票的过程可以分为这样:

1、先拿到Ticket

2、输出以前的值

3、接着Ticket–

4、将–后的Ticket的值赋给原来的Ticket

为什么会发生相同的票出现多次?

答:线程具有随机性。在步骤2的时候线程t1、t2都进来了,所以t1、t2就售相同的票了。

为什么会发生负数的票出现?

答:在步骤3的时候线程t1、t2、t3、t4都进来了,

//窗口t1正在出售第1张票,tickets=0
//窗口t2正在出售第0张票,tickets=-1
//窗口t3正在出售第-1张票,tickets=-2
//窗口t4正在出售第-2张票,tickets=-3

注意:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

解决线程安全问题的基本思想

首先想为什么出现问题?

   是否是多线程环境;   是

   是否有共享数据;     是

   是否有多条语句操作共享数据;   是

如何解决多线程安全问题呢?

第一个条件为操作系统环境,我们不能改变,第二个条件需求就是有共享的数据,我们也不能改变,所以我们只能看看能不能在第三个条件上动点手脚,让操作共享数据的多条语句执行过程中不受干扰。

解决思路:

把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。

解决办法:

同步代码块
格式:
    synchronized(对象)需要同步的代码;
    同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

注意:多个线程使用的是同一个锁对象

同步的好处

   同步的出现解决了多线程的安全问题。

同步的弊端

   当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

那改后的代码为:

public class SellTicketRunnable implements Runnable

    private int ticket = 100;   //票的数量
    private Object obj = new Object();  //同步锁对象,必须为同一锁对象
    @Override
    public void run() 
        while (true) 
            synchronized (obj) 
                if (ticket > 0) 
                    System.out.println(Thread.currentThread().getName()+"正在售第"+ticket--+"张票");
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                
            
        
    

以上是关于java回忆录——进程和线程的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程-从基础到深入理解-01

java 22 - 11 多线程之模拟电影院售票口售票

Java多线程实现简单的售票程序

JAVA多线程售票问题

java面试 啥是多线程

Java基础_通过模拟售票情景解决线程不安全问题