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) //刚开始创建出来线程池的时候,里面实际上一条线程都没有。因为使用的是懒汉模式。