Java 线程详解(一)

为了9月份的秋招呢,现在开始进行多线程以及并发相关知识的学习。。加油加油

一、基本的概念

1.线程和进程

  进程:正在运行的程序,是系统进行资源分配的独立单位。

  线程:进程的执行路径,调度和执行的单位,单个路径单线程,多个路径多线程。

以上的解释太“官方”了,在《Java多线程编程核心技术》里,把进程理解成我们打开的每个程序,而线程则是程序里每一个子任务。 

比如,我们打开WeChat.exe运行,此时WeChat.exe就可以理解成一个进程,而你用微信和别人视频,拿来传输文件,发送信息等等就有很多子任务,其中每一个任务就可以理解成线程

其中多线程就是指,一个进程里面有多个线程,其优点在于解决了多部分同时运行的问题,提高效率

而缺点是1.当线程太多会导致效率的降低,因为线程的执行依靠的是CPU的来回切换,而这个切换时非常损耗性能,过于频繁反而无法发挥出多线程编程的优势(通常减少上下文切换可以采用无锁并发编程,CAS算法,使用最少的线程和使用协程)

       2.临界区线程安全问题,解决方法:(https://www.jianshu.com/p/959cf355b574

2.同步和异步(Asynchronized和Synchronised)

多线程同步:就是在发出一个功能请求时,在没有得到结果之前,就不能发送下一个请求。好比所有跑道只剩一条的时候,运动员在一条跑道上跑完一个到一个

好处:解决了线程的安全问题。(线程的安全问题和非线程安全问题有区别吗??还没解决)

弊端:每次都有判断锁,浪费时间降低了效率。但是在安全与效率之间,首先考虑的是安全。

多线程异步:则是在发出一个功能请求时,不需要等待返回,随时可以再发送下一个请求。好比训练的时候,运动员在各自的跑道上跑自己的步。

好处:并发好,cpu利用率高。

弊端:要考虑线程间同步互斥问题等。

3.并发和并行

并发指的是多个任务交替进行,而并行则是指真正意义上的“同时进行”。

实际上,如果系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

4.阻塞和非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响,比如一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起,这种情况就是阻塞,而非阻塞就恰好相反,它强调没有一个线程可以阻塞其他线程,所有的线程都会尝试地往前运行。

5.临界区

又可以称之为互斥区,用来表示一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

如synchronize可以在任意对象及方法上加锁,而加锁的这段代码就被称为临界区。

二、如何创建一个线程

在Java中实现多线程有两种方法,1.继承Thread类,2.实现Runnable接口。(至于还有别的线程池或者是callable接口是这个基础上的增强版??之后会说到)

1.Thread类和Runnable接口的结构

public class Thread extends Object implements Runnable

Thread类实现Runnable接口,它们具有多态关系

可以传递Runnable接口的对象,而Runnable的设计是为了解决Java中单继承的限制。

还可以看出可以传递Thread类的对象,说明可以将一个Thread对象中的run()方法交给别的线程调用。

Runnable中只有一个run()方法

2.继承Thread类

1)创建一个新类继承Thread类。

2)重写Thread类中的public void run()方法,将线程的代码封装到run()里。

3)  创建该新类对象,创建线程。

4)对象调用start(),启动线程(即调用run方法)。

class MyThread extends Thread {  
    // 重载run函数  
    public void run() {  
        System.out.println("MyThread");
    }  
  
    public static void main(String argv[]) {  
        MyThread td = new MyThread(); // 创建,并初始化MyThread类型对象td  
        td.start(); // 调用start()方法执行一个新的线程  
        System.out.println("运行结束") 
    }  
} 
输出:运行结束
        MyThread
  因为“运行结束”是主线程main里的,所以是先执行

3.实现Runnable接口

1)定义一个新类,实现Runnable接口。

2)重写接口中run()方法,将线程的代码封装到run()里。

3)创建Runnable接口的子类对象。

4)将Runnabl接口的子类对象作为参数传递给Thread类的构造函数,创建Thread类对象(原因:线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务)。

  反正就是Runnable没有start()方法,需要通过传对象参数形式用线程类去启用它的run方法。

5)Thread类对象调用start(),启动线程(即调用run方法)。

class MyRunnable implements Runnable {  
    // 重载run函数  
    public void run() {  
        System.out.println("运行中!");
    }  
  
    public static void main(String argv[]) {  
        MyRunnable rb = new MyRunnable(); // 创建,并初始化MyRunnable对象rb  
        Thread td = new Thread(rb); // 通过Thread创建线程  
        td.start(); // 启动线程td  
        System.out.pritnln("运行结束");
    }  
}  
输出:运行结束   
        运行中!

   注意:调用线程的 run()方法是通过启动线程的start()方法来实现的。 因为线程在调用start()方法之后,系统会自动调用 run()方法。与一般方法调用不同的地方在于一般方法调用另外一个方法后,必须等被调用的方法执行完毕才能返回,而线程的 start()方法被调用之后,系统会得知线程准备完毕并且可以执行run()方法,start()方法就返回了,start()方法不会等待run()方法执行完毕。  

4.两种区别

两种创建线程性质都是一样的,但是通常建议创建线程是实现Runnable接口

优势是:1.可以支持多继承

       2.适合多个相同的程序代码的线程去处理同一个资源

    3.线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类

三、线程的各个状态

理解下面的图!

1、新建状态(New):新创建了一个线程对象。

2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码

4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)

5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

四、Thread类的常用API方法

1.currentThread()(静态)

public static Thread currentThread()

 返回对当前正在执行的线程对象的引用。注意是静态

通常用作,Thread.currentThread.getName()返回线程名。

2.isAlive()

public final boolean isAlive()

 测试线程是否处于活动状态。如果线程已经启动且尚未终止,则为活动状态。

 返回:如果该线程处于活动状态,则返回 true;否则返回 false

3.sleep()(静态)

public static void sleep(long millis) throws InterruptedException

public static void sleep(long millis, int nanos) throws InterruptedException

在指定的毫秒数(或者加指定的纳秒数)内让当前正在执行的线程休眠(暂停执行)。注意是个静态方法

参数含义:millis - 以毫秒为单位的休眠时间。nanos - 要休眠的另外 0-999999 纳秒。

为什么要用sleep,主要是为了暂停当前线程把cpu片段让出给其他线程减缓当前线程的执行。 

(后面再说和wait()的区别)

3.getId()

public long getId()

返回该线程的唯一标识。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变。线程终止时,该线程 ID 可以被重新使用。

4.interrupt(),interrupted()(静态),isInterrupted()

public void interrupt()
public static boolean interrupted(){
        return currentThread().isInterrupted(true);
}
public boolean isInterrupted(){
        return isInterrupted( false);//至于为什么是false请看下面链接
}

interrupt():对线程进行中断操作。

isInterrupted():测试线程Thread对象是否已经是中断状态,但不清除状态标志,如果该线程已经中断,则返回 true;否则返回 false

interrupted():对当前线程进行中断操作(由上面源代码可知,就算当前是main线程用别的对象线程调用该方法,也还是判断返回main是否被中断),该方法会清除中断标志位为false。需要注意的是,当抛出InterruptedException时候,会清除中断标志位,也就是说此时再调用isInterrupted会返回false。

详细讲解这三个方法:https://www.cnblogs.com/w-wfy/p/6414801.html

猜你喜欢

转载自www.cnblogs.com/furaywww/p/8859140.html