java并发编程——多线程

本章主要介绍java中多线程并发编程基础知识,包括的内容有: 

  • 进程,线程,协程的区别
  • 多线程的实现方式
  • 线程中断和优先级
  • 线程状态的切换

一、进程、线程和协程

1.1 进程、线程和协程

进程是资源分配的最小单位,操作系统会以进程为单位,分配系统资源(CPU时间片、内存等资源)。进程拥有自己独立的堆和栈,既不共享堆,也不共享栈。

线程是CPU调度的最小单位,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,共享堆不共享栈。

协程是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制(也就是在用户态执行)。这样带来的好处就是性能得到了很大的提升,不会像线程切换那样消耗资源。

【区别】

  • 调度性:进程是操作系统资源分配的单位,线程作为调度和分配的基本单位。
  • 并发性:进程可以并发进行,同一个进程内的线程也可以并发进行;
  • 资源分配:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
  • 系统开销:进程的创建和销毁,涉及到操作系统资源的分配和回收,系统开销要大于线程。
  • 健壮性:进程之间相对独立,一个进程崩溃后,在保护模式下不会对其它进程产生影响。线程是一个进程中的不同执行路径,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮。

【联系】

  • 一个线程只能属于一个进程,一个进程至少拥有一个线程
  • 资源分配给进程,同一进程内的线程共享该进程的所有资源
  • 处理机分配给线程,CPU调度是分配给线程的

与多线程相比,协程具有的优势特点是:

  • 线程是用户程序控制的,协程切换没有线程切换的开销,和多线程相比,线程数量越多,协程的性能优势越明显。
  • 协程中控制共享资源不需要加锁,也不存在同时写变量的冲突,执行效率比线程要高。

1.2 并行和并发:

并行是时间上各不影响的同时执行,而并发在时间上是相互应用时间片段,宏观上看是并行的。

1.3 阻塞和非阻塞:

形容多线程之间的相互影响,当一个线程独占临街资源时,其他线程需要等待,导致线程挂起,这就是阻塞。

1.4 临界区:

表示一种公共资源,可以被多个线程使用,但是每次只允许一个线程独占。

1.5 死锁、活锁

死锁:指两个或多个线程在执行的过程中,由于资源相互竞争造成阻塞的现象。若无相关操作,他们将被无限期阻塞下去。死锁是一个静态问题,进程会被卡死,但是不会占用cpu。

活锁:是一个动态过程,比如:线程A、B都需要临界资源a和临界资源b,线程A占用a,需要b,线程B占用b,需要a,放弃了资源以后,A又获得了b资源,B又获得了a资源,如此反复,则发生了活锁。

1.6 饥饿:

指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。

二、多线程的实现方式

创建多线程,可以通过以下几种方式:

  • 继承Thread,重写run方法
  • 实现runnable接口,重写run方法
  • 实现callable接口,重写run方法,并把任务提交给线程池
  • 继承FutureTask,FutureTask提供Callable和Runnable接口的构造函数,
  • 通过ForkJoin框架并行执行任务,其内部的ForkJoinTask继承Future接口。

三、线程的中断和优先级

3.1 线程的优先级

java 中线程默认的优先级有10级,MAX_PRIORITY和MIN_PRIORITY分别是最高级10和最低级1,当然还有默认级别是5;线程优先级的一些特性:

  • 线程优先级的继承特性:也就是如果线程A启动线程B,那么线程A和B的优先级是一样的;
  • 线程优先级的规则性:即线程会优先级的大小顺序执行,但不一定是优先级较大的先执行完,因为线程的优先级还有下面第三个特性:
  • 线程优先级的随机特性;

可以通过Thread.setPriority(level)来实现对线程优先级的控制。

java 中有两种线程:用户线程守护线程,可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。

当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:
(01) 调用了exit()方法,并且exit()有权限被正常执行。
(02) 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。

每一个线程都被标记为“守护线程”或“用户线程”。当只有守护线程运行时,JVM会自动退出。

3.2 线程的中断

线程中断:中断是一种协作机制,调用线程的interrupt方法不一定会中断正在运行的线程,它会要求线程在合适的时机结束自己。每个线程都会有一个boolean的中断状态,interrupt方法只是将状态设置为true,对于非阻塞的线程,只是状态进行了改变,并不一定会立即停止。如果线程上使用了Thread.sleep(), Object.wait(), Thread.join(),这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false。

Thread.stop():会释放掉所有的监管monitor,无论线程执行到哪里,都会立即停止线程,不推荐使用。

wait和notify区别

共同点: 
1)都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。 
2)wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 

不同点:

1)sleep和yield方法都是Thread类方法,wait和notify,notifyAll都是object的方法;

2)sleep睡眠时,保持对象锁,仍然占有该锁;而wait睡眠时,释放对象锁。

3)sleep可以在任何地方使用,并且需要捕获编译异常;wait和notify,notifyAll需要在同步块中使用。

四、线程的基本状态

1)新建状态:被new()出来

2)runnable状态:线程start

3)running状态:线程获得时间片,正执行

4)等待状态:线程被wait,释放掉锁的状态

5)等锁状态:线程被notify后,进入等锁池

6)阻塞状态:线程sleep,join或者Io阻塞

7)dead:线程执行完

线程之间的扭转关系可有:

java并发编程——多线程_第1张图片

五、多线程中的happen-before

Java语言中有一个“先行发生”(happen—before)的规则,它是Java内存模型中定义的两项操作之间的偏序关系。Java内存模型中的八条可保证happen—before的规则,它们无需任何同步器协助就已经存在。

  • 程序顺序原则:一个线程内保证语义的串行性
  • volatile规则:volatile变量的写,先发生于读,这保证了volatile变量的可见性
  • 锁规则:解锁(unlock)必然发生在随后的加锁(lock)前
  • 传递性:A先于B,B先于C,那么A必然先于C
  • 线程的start()方法先于它的每一个动作
  • 线程的所有操作先于线程的终结(Thread.join())
  • 线程的中断(interrupt())先于被中断线程的代码
  • 对象的构造函数执行结束先于finalize()方法
public class HappenBeforeTest extends Thread {
    private boolean flag;

    @Override
    public void run(){
        while(!flag){
            System.out.println("thread run");
        }
        System.out.println("thread stop");

    }

    public boolean getStop(){
        flag = true;
        return flag;
    }

    public static void main(String[] args) throws InterruptedException {
        HappenBeforeTest test = new HappenBeforeTest();
        test.start();
        Thread.sleep(1000);
        test.getStop();
        Thread.sleep(1000);
    }
}

这个程序在client模式下能够正常的打印thread run和thread stop。但是在Server模式下可能将是无限循环。因为虽然getStop函数设定了结束标识,但是线程不一定能取到值,甚至会运行抛出异常。

参考文献:进程、线程和协程之间的区别和联系_~青萍之末~的博客-CSDN博客_进程线程协程

参考文献:http://www.cnblogs.com/GarfieldEr007/p/5746362.html

   

你可能感兴趣的:(Java基础知识巩固,java,多线程,高并发)