1 进程与线程,同步与死锁,生产者消费者模式,等待wait与唤醒notify
《开实》:
线程的5中状态:创建new,就绪start(),运行run(),阻塞,死亡
进程就是程序的一次执行,而线程可以理解为进程中的执行的一段程序片段。用一点文词说就是,每个进程都有独立的代码和数据空间(进程上下文);而线程可以看成是轻量级的进程。一般来讲(不使用特殊技术),同一进程所产生的线程共享同一块内存空间。
同一进程中的两段代码是不可能同时执行的,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。
进程和线程都可以有优先级。
Java应用程序的入口就是main方法()
启动一个Java应用程序,就是要运用它的main方法,这时候就启动了一个Java虚拟机------在Windows下查看任务管理器,就会发现多出了一个javaw.exe的进程,这个就是java虚拟机!而main()方法其实是Java虚拟机这个进程的一个主线程(默认至少还有一个垃圾回收器的守护线程,所以运行一个java应用程序,启动了一个java进程的同时,至少又启动了2个java线程)!main方法执行结束,java虚拟机自动就销毁了,进程就结束啦!
所以,我们同时运行了2个java类的main()方法,这时候操作系统会启动2个java进程。
但是,Java进程间是不能直接通信的
进程,是针对于操作系统而言的
线程,是java中一个重要的类,主要用来多个方法一起执行
进程,是针对于操作系统而言的,重量级,而且进程堆栈都是独立的,
线程是进程内部的单一的顺序流,堆共享,栈独立
Java是多线程的编程语言,所以java程序运行时也是以线程的方式。每当使用java命令执行一个类是,就启动了一个JVM(每个JVM相当于在操作系统中启动了一个进程),JVM本身有垃圾回收机制,所以Java运行时至少启动两个线程:main线程(主函数也是一个线程)和垃圾回收。主方法也是一个线程,线程的默认优先级是NORMAL_PRIORITY(为5)。
线程的生命周期。如何停止一个线程的执行
实现多线程的两种方法:继承Thread类。调用时用start()。重复调用start()会抛出一个IllegalThreadStateException。实际调用的是start0()函数,这个函数是用native声明的,表明调用本机的操作系统函数,因为多线程的实现需要依靠底层操作系统,这样的技术称为JNI技术(java Native Interface);实现Runnable接口。本质还是依靠Thread,Thread有能接受Runnable子类的实例的构造函数。所以实例化Runnable子类实例对象,传入Thread,使用start()。Thread类也是Runnable接口的子类:public class Thread extends Object implements Runnable。Thread类中没有完全实现run(),而是调用的Runnable接口中的run()。
Java中Runnable和Thread的区别:点击打开链接
1、操作系统在执行一个程序时,会自动建立一个‘进程’,这个进程中,至少包含一个线程(这个线程被称为主线程),来作为程序入口。
2、操作系统运行中,会产生很多个‘进程’,这些进程不共享内存;而每个进程占有的内存,却可以被这个进程中不同的‘线程’所共享;另外,这些线程不仅共享这段内存,每个线程还可有一个属于它自己的内存空间——线程栈,这是线程建立时,操作系统分配的,用来保存线程数据。
3、线程总是属于某个进程,进程中的多个线程共享进程的内存。
4、进程被分为多个线程后,这些线程可以并发执行。
《核心》:
线程同步的方法(防止代码块受并发访问的干扰):sychronized、lock、reentrantLock等。
锁对象:
锁的等级:方发锁、对象锁、类锁。
类锁:在代码的方法上加了static synchronized或者synchronized(xxx.class)的代码块。
对象锁:在代码的方法上加了synchronized或者synchronized(this)的代码块。类锁和对象锁不会产生竞争。
java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,知道线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的
如果用Synchronized声明一个方法,那么对象的锁将保护整个方法。每个对象都有一个内部锁并且该锁有一个内部条件。由锁管理试图进入synchronized的线程,由条件管理调用wait的线程。Synchronized中的wait()、notify()、notifyAll()分别对应内部对象锁的await()、signal()、signalAll()。
Volatile域:为实例域的同步访问提供了一种免锁机制
什么叫死锁?
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
那么为什么会产生死锁呢?
1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。
学过操作系统的朋友都知道:产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
例如:
死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。
进程:是一个正在执行中的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元。
线程在控制着进程的执行。一个进程中至少有一个线程。
多线程:
Java VM 启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
1,如何在自定义的代码中,自定义一个线程呢?
通过对api的查找,java已经提供了对线程这类事物的描述。
有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。
创建线程的第一种方式:继承Thread类。
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法,
该方法两个作用:启动线程,调用run方法。
发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
为什么要覆盖run方法呢?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
同步的前提:
1,必须要有两个或者两个以上的线程。(都加上同步才满足)
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
未满足这两个条件,不能称其为同步。
好处:解决了多线程的安全问题。
弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
一个多线程的程序如果是通过Runnable接口实现的,意味着类中的属性将被多个线程共享,这就会造成:如果这些线程要操作同一资源时有可能出现资源的同步问题。要解决这个问题,就要使用同步。同步:指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成之后才可以继续执行。
生产者消费者模式:《开实》P297
用同步以及设置标志位Object类的wait和notify
sychronized、lock、reentrantLock
《核心》P651
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
ReentrantLock 类
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
在查看清单 1 中的代码示例时,可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。