java线程、进程

进程

进程是程序在计算机上的一次执行活动,及正在运行中的应用程序,通常称为进程。当你运行一个程序,你就启动了一个进程。每个进程都有自己独立的地址空间(内存空间),每当用户启动一个进程时,操作系统就会为该进程分配一个独立的内存空间,让应用程序在这个地里的内存空间中运行。

在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多进程,也称多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。

多任务带来的好处是明显的,比如你可以边听mp3边上网,与此同时甚至可以将下载的文档打印出来,而这些任务之间丝毫不会相互干扰。

线程

线程多线程简介

java中所有的代码都是靠线程运行的,包括main方法。

线程是一个轻量级的子进程,是最小的处理单元;是一个单独的执行路径。线程从属于进程。进程中包含一个或多个执行单元称为线程。进程还有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。线程只能归属于一个进程。且它只能访问该进程所拥有的资源(运行时建立的变量、数据。。)

操作系统创建一个进程后,进程会自动申请一个名为主线程或首要线程的线程
一个进程至少有一个线程,否则操作系统就会杀掉这个进程。

多线程的使用场合

多线程通常在一个程序中同时完成多个任务。即多段代码同时进行。

在单一线程虽然可以完成,但是多线程可以更快的完成的情况。如下载文件。

并发与并行

并发:多个线程"同时"与运行。但实际上只是我们感官上的一种表现。实际上是单一运行的。OS将时间划分为很多时间片段,并尽可能均匀的分配给每一个线程,只有获取时间片段的线程才被CPU运行,而其他线程全部等待,所以微观上是“走走停停”的,因为计算机运行速度非常快所以宏观上看上去是同时运行的。这种现象叫做并发,但不是真正的同时运行。

并行:当系统有一个以上的CPU时,则线程的操做有可能非并发。当一个CPU执行一个线程时,另一个CPU可以去执行另一个线程,两个线程互不抢占CPU资源,是真正意义上的同时进行,这种方式我们称之为并行(Parallel)。

创建线程

创建线程有两种方式:1.继承Thread类并重写run方法。2.实现Runnable接口并重写run方法,单独定义线程任务。

继承Thread类并重写run方法

实现步骤:

1.自定义类继承Thread类并重写run方法。

2.在run方法中添加要并发执行的代码。

3.主函数中实例化自定义的类,再通过引用变量调用start方法来启动线程。start方法会自动调用run方法。

 1     public static void main(String[] args) {
 2         MyThread1 t1 = new MyThread1();
 3         MyThread2 t2 = new MyThread2();
 4         //启动线程要调用start方法,而不是直接调用线程的run方法。run方法会在start方法调用后自动调用
 5         t2.start();
 6         t1.start();
 7         t1.interrupt();         
10     }    
12 }
13 
14 class MyThread1 extends Thread{
15     public void run() {
16         for(int i=0;i<1000;i++) {
17             System.out.println("我是你爹");
18         }
19     }
20 }
21 class MyThread2 extends Thread{
22     public void run() {
23         for(int i=0;i<1000;i++)
24             System.out.println("你是谁啊");
25     }

优点:使用方法简单。

缺点:1.由于java不支持多继承,所以继承了Thread类后就不能继承其他类了。而在实际开发中我们经常回去继承某个             吵了来服用其中的方法。

       2.继承线程后重写run方法来定义任务会导致任务直接定义在线程上,使得线程只能做该任务无法执行其他任                  务。这使得我们代码耦合性强,重用性差

实现Runnable接口,重写run方法。

实现步骤:

1.自定义类实现Runnable接口,重写run方法。

2.在run方法中添加向要并行执行的代码。

3.实例化自定义的类,实例化任务,即想要并行的代码。

4.实例化Thread类,并将自定义类的引用传进去,创建线程。

5.调用start方法启动线程。

 1 public static void main(String[] args) {
 2      //实例化人物
 3      Runnable r1 = new MyRunnable1(); //因为它是Runnable的子类所以可以造型
 4      Runnable r2 = new MyRunnable2();
 5         
 6      //创建线程
 7      Thread t1 = new Thread(r1);
 8      Thread t2 = new Thread(r2);
 9         
10       t1.start();
11       t2.start();
12       for (int i = 0; i < 10; i++) {
13          System.out.println(1);
14       }
15 }18 
19 class MyRunnable1 implements Runnable{
20     @Override
21     public void run() {
22         for(int i=0;i<1000;i++)
23             System.out.println("你是谁");
25     }
27 }
28 class MyRunnable2 implements Runnable{
29     @Override
30     public void run() {
31         for(int i=0;i<1000;i++)
32             System.out.println("我是你爹");
33     }

优点:1.因为是实现Runnable接口的形式来创建线程,所以不妨碍实现其他接口,和继承其他类了。

      2.线程和任务没有必然的关系,这种方法是在实例化的时候才把任务交给线程,而不是在定义Thread同时就已                经把这个任务给他了。Thread本身就是一个有完整功能的类,可以直接实例化使用它

多线程运行过程

即多线程正常生命周期过程(两个线程为例)

1.首先new出两个线程,线程只new出来是没有并发能力的。

2.然后调用start方法之后,线程就被纳入到线程调度程序中去了。(线程调度是JVM的一个线程并发机制。线程调度给cpu进行规划去运行哪个线程的代码运行多久,这一操作称为cpu时间片即允许该线程运行的时间

3.线程被纳入到线程调度之后它们的状态时Runnable(可运行状态),这时线程调度程序就会给这条线程随机分配cpu时间片,这个线程就进入到Running(运行状态),紧接着在调用这个线程的run方法执行我们想要并发的程序。

4.如果当前线程的时间片用完了,则会把当前线程运行到哪了做一个标记,再到线程调度程序中去随机分配cpu时间片。若一条线程中run方法中的内容全都执行完了,则这条线程就会进入Dead状态,等待被GC回收。

注:线程调度程序分配cpu时间片给哪条线程,分配多少时间是随机的,所以可能出现连续调用同一线程的情况。线程是无法干预线程调度程序的,只能被动的接收。

                                                                    线程运行状态图

注:一个线程进入阻塞状态(Block)时,cpu会立刻释放去执行其他线程。直到该线程解除阻塞状态为止。

Thread类

构造器

Thread t = new Thread();
Thread t = new Thread(String str); //参数为创建线程的名字。不写系统默认为Thread-0、Thread-1。。。

常见API

void start()  //开启线程

static Thread currentThread()  //是一个静态方法,使用时用类名去调用。用于获取运行当前代码片段的线程。

long getId()  //返回该线程的标识符。指唯一标识,相当于身份证号。

String getName()  //获取该线程的名称。

int getPriority()  //返回线程的优先级  线程是有优先级的 1-10级别。由低到高,5代表默认值

final void setPriority(int newPriority)   //设置线程优先级。参数为设置的等级(1-10)

boolean isAlive()  //测试线程是否处于活动状态

boolean isDaemon()   //测试线程是否为守护线程。

boolean isIntereupted()  //测试线程是否中断

static void sleep(long ms)  //使运行这个方法的线程处于阻塞状态指定的毫秒,超时后此线程回到Runnable状态,等待再次获取时间片。                                     

void interrupt()  //中断线程。实际上中断的是当前线程阻塞状态。

final void setDaemon(boolean on)  //在线程启动之前进行设置,即在调用start方法之前。参数为是否设置为守护线程。

public static native void yield()  //强制使线程发生切换。当线程运行到yield方法时,这个线程会立刻释放cpu时间。native本地方法,代表是其他的语言实现的(如c或c++)。

线程优先级

由于我们没有权利主动的获取cpu时间片来使某个线程执行的多一些。改变线程的优先级是唯一可以干涉线程调度工作的方式,最大程度的改善获取cpu时间片的次数
理论上现成的优先级越高,获取的cpu时间片的次数越多。理论上是但是不保证一定是这样。线程由低到高一共有1-10级。

final void setPriority(int newPriority)   //设置线程优先级。参数为设置的等级(1-10)

阻塞线程

一个线程进入阻塞状态时,cpu会立刻释放去执行其他线程。直到该线程解除阻塞状态为止。

static void sleep(long ms)  //使运行这个方法的线程处于阻塞状态指定的毫秒,超时后此线程回到Runnable状态,等待再次获取时间片。

若设置等待1s,真正内部并不是严格的1s,因为在阻塞解除以后回到Runnable状态时是1s,但是线程分配cpu时间片给我们的这一段时间是不确定的。即Runnabel到Running状态会有误差的。

void interrupt()  //中断线程。实际上中断的是当前线程阻塞状态。

若一个线程调用sleep方法处于阻塞状态时,另一线程调用该线程的中断方法,那么该线程的sleep会立即抛出中断异常InterruptedException,并打断睡眠阻塞,而不是中断这个线程。

守护线程(后台线程)

main()函数即主函数,就是一个前台线程,jvm的垃圾回收器其实就是一个后台线程。前台进程是程序中必须执行完成的,而后台线程则是java中所有前台结束后结束,不管有没有完成,后台线程主要用与内存分配等方面。

final void setDaemon(boolean on)  //在线程启动之前进行设置,即在调用start方法之前设置。参数为是否设置为守护线程。

守护线程又称后台线程,默认创建出来的线程都是普通线程或称为前台线程。和进程共存亡,只有调用setDaemo方法后才会将线程设置为守护线程。

守护线程使用上与前台线程一样,但是在结束时机上有点不同。当进程结束时,即所有普通线程都结束时,所有正在运行的守护线程都会被强制中断。进程的结束:当一个进程没有前台线程时即结束。

主线程是前台线程。当有多个线程时,先结束的是主线程。程序结束取决于进程什么时候结束,进程什么时候结束取决于是否还有前台线程。

守护线程的用途

一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息。前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系统资源进行扫描的程序。JVM 中的垃圾回收线程就是典型的守护线程,如果GC不是守护线程就会发生当 JVM 要退出时,由于垃圾回收线程还在运行着,导致程序无法退出,这就很尴尬了!!!由此可见,守护线程的重要性了。

同步、异步运行

public final void join()  //join方法可以协调线程之间的同步运行。把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。

同步运行:代码有先后顺序。异步运行:代码之间没有先后顺序各干各的

多线程本身执行就是异步运行的。一般情况下单线程时是同步运行的。join方法也是阻塞在一个线程run方法中调用另一个线程的join方法时,会使当前线程进入阻塞状态,直到另一个线程完全执行完毕才会解除阻塞。

例如:扫地墩地的时候,是要先扫地后墩地的,这时就需要同步运行。看淘宝时是有异步有同步的,同时加载图片和文字情况下,它是先加载文字再加载图片的。 他并不是完全异步也不是完全同步的。

锁机制关键字synchronized

synchronized是同步锁以是互斥锁

多线程并发的安全问题

当多个线程并发操作同一资源的时候,由于线程切换实际的不确定性和不可控性,会导致操作该资源的代码执行逻辑可能会为按照设计的要求去运行,会出现操作混乱。严重时可能会导致系统瘫痪,所以多个线程处理同一资源时需要有先后顺序,这时就用到了锁机制。

同步锁的情况

Java提供了锁机制,同步锁强制多个线程同步运行一个方法。

使用方法一:
方法前使用关键synchronized修饰。此时该方法称为同步方法,多个线程不能同时在方法内部运行。一个线程运行synchronized时其他线程只能阻塞等待该线程运行完。因此程序的执行效率会下降

 

使用方法二:
同步块。使用这种方式可以有效的缩小同步范围,可以再保证并发安全的前提下尽可能的提高并发效率

语法:synchronized(同步监视器对象){
需要同步运行的代码片段
}

 

同步块有一个要求,多个线程看到的同步监视器对象必须是同一个否则没有同步运行的效果,具体哪个对象可以结合将来实际开发需求而定。

直接在方法上使用synchronized关键字,那么保护监视器对象就是当前方法所属对象,即方法中看到的this。

如果用synchronized修饰 一个静态方法,那么不同线程调用这个方法时一定具有同步效果。因为静态方法是从属于类的,存储在方法区,即在内存中只有一份。此时的同步监视器对象是类对象

类对象:java专门有一个类叫做Class,它的每一个实例都是用来描述被JVM加载的一个类,且每个类只有唯一的一个实例。 

互斥锁的情况

synchronized还可以做互斥锁。当synchronized同时锁定多段代码片段的时候,并且指定的同步监视器对象是同一个,那么这些代码片段之间就是互斥的。即:多个线程不能同时执行这些代码片段。

例如:给同一个类中a、b两个方法用synchronized修饰,则当一个线程调用a方法时,其他线程是不能调用a或b方法的。

synchronized锁定的是一段代码则是同步锁,锁定多段代码就是互斥锁。

 线程池

当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源以及过度切换现成的危险,从而导致系统崩溃。为此我们应使用线程池来解决这个问题。ExecutorService是java提供的用于管理线程池的接口类。

线程池主要有两个主要作用:控制线程数量 、重用线程

管理线程池的API和常用的API

static ExecutorService newCachedThreadPool(ThreadFactory threadFactory)  //创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们。弹性很好,不控制线程数量。

static ExecutorService newFixedThreadPool(int nThreads)  //最最最常用的,创建一个可重用固定线程数量集合的线程池,以共享的无界队列方式来运行这些线程。固定大小的线程池

static ExecutorService newScheduledThreadPool(int corePoolSize)  //创建一个线程池,它可安排在给定延迟后运行命令或者定期执行。

static ExecutorService newSingleThreadExecutor()  //创建一个使用单个worker线程的Executor,以无界队列方式来运行该线程

void execute(Runnable command)  //启动线程池。参数为一个线程。调用它以后,无需手动开启线程了 即无需Thread t = new Thread(handler); t.start();

void shutdown()  //终止当前线程池 。并不是一调完这个方法,所有线程就直接停止,而是不再接受新的任务,并将调用之前给的任务执行完以后才终止所有线程。

void shutdownNow  //强行终止线程线程池,无论是否之前的任务执行完否。

常见实例化

懒汉模式:只有有任务时才会创建
饿汉模式:有无任务都会创建

ExecutorService threadPool = Executors.newFixedThreadPool(2)  //刚开始创建出来线程池的时候,里面实际上一条线程都没有。因为使用的是懒汉模式。

posted @ 2020-09-23 22:55  降温。  阅读(199)  评论(0编辑  收藏  举报