先说说线程和进程,现代操作系统几乎无一例外地采用进程的概念,进程之间基本上可以认为是相互独立的,共享的资源非常少。线程可以认为是轻量级的进 程,充分地利用线程可以使得同一个进程中执行多种任务。Java是第一个在语言层面就支持线程操作的主流编程语言。和进程类似,线程也是各自独立的,有自 己的栈,自己的局部变量,自己的程序执行并行路径,但线程的独立性又没有进程那么强,它们共享内存,文件资源,以及其他进程层面的状态等。同一个进程内的 多个线程共享同样的内存空间,这也就意味着这些线程可以访问同样的变量和对象,从同一个堆上分配对象。显然,好处是多线程之间可以有效共享很多资源,坏处 是要确保不同线程之间不会产生冲突。

每个Java程序都至少有一个线程——main线程。当Java程序开始运行时,JVM就会创建一个main线程,然后在这个main线程里面调用
程序的main()方法。JVM同时也会创建一些我们看不到的线程,比如用来做垃圾收集和对象终结的(garbage collection and
object finalization,JVM最重要的两种资源回收),或者JVM层面的其他整理工作。

为什么要使用线程?

1、可以使UI(用户界面)更有效(利用多线程技术,可以把时间较长的UI工作交给专门的线程,这样UI的主线程就不会被长期占用,界面就会流畅而不停滞)

2、有效利用多进程系统(单线程+多进程,太浪费系统资源了)

3、简化建模

4、执行异步处理或者后台处理(不同的线程做不同的工作)

线程的生命周期:

通常有两种方法创建一个线程,1、implement Runnable接口,2、继承Thread类

创建完成后,这个线程就进入了New State,直到它的start()方法被调用,它就进入了Runnable状态。

一个线程从Running State进入Terminated / Dead State标志着线程的终结,正常情况下有这么几种可能性:

1、线程的run()执行结束

2、线程抛出没有捕捉到的异常或者错误

当一个Java程序所有的非守护进程(Daemon Thread,即守护进程,负责一些包括资源回收在内的任务,我们无法结束这些进程)结束时,程序宣告执行结束。

Java Thread的重要方法必须熟悉。

join():目标线程结束之前调用线程将会被Block,例如在main线程中创建了一个thread1线程,调用 thread1.join(),这就意味着thread1将优先执行,在thread1结束后main thread才会继续。一个join()方法的使用案例:将一个任务(比如从1万个元素的数组中选出最大值)分拆成10个小任务(每个小任务负责1000 个)分配给10个线程,调用它们的start(),然后分别调用join(),以确保10个任务都完成(分别选出了各自负责的1000个元素中的最大值) 后,主任务再进行下去(从10个结果中挑出最大值)。

sleep():使当前线程进入Waiting State,直到指定的时间到了,或者被其他线程打断,从而回到Runnable State。

wait():使调用线程进入Waiting State,直到被打断,或者时间到,或者被其他线程使用notify(),notifyAll()叫醒。

wait和sleep有一个非常重要的区别是,一个线程sleep的时候不会释放任何lock,而wait的时候会释放该对象上的lock。

notify():这个方法被一个对象调用时,如果有多个线程在等待这个对象,这些处于Waiting State的线程中的一个会被叫醒。

notifyAll():这个方法被一个对象调用时,如果有多个线程在等待这个对象,这些处于Waiting State的线程都会被叫醒。

多线程共享资源是讨论最多的话题,也是最容易出问题的地方之一,Java定义了两个关键字,synchronized和volatile,用来帮助共享的变量在多线程情况下能够正常工作。

synchronized一方面确保同一时间内只有一个线程能够执行一段受保护的代码,并且这个线程对数据(变量)进行的改动对于其他线程是可见
的。这里包含两层意思:前者依靠lock(锁)来实现,当一个线程处理一段受保护代码时,该线程就拥有lock,只有它释放了这个lock,其他线程才有
可能获得并访问这段代码;后者由JVM机制实现,对于受synchronized保护的变量,需要读取时(包括获取lock)会首先废弃缓存
(invalidate cache),进而直接读取main memory上的变量,完成改动时(包括释放lock)会flush缓存中的write
operation,强行把所有改动更新到main memory。

为了提高performance,处理器都是会利用缓存来保存一些变量储存在内存中的地址,这样就存在一种可能性,在一个多进程架构中,一个内存地
址在一个进程的缓存中被修改了,其他进程并不会自动获得更新,于是不同进程上的2个线程就会看到同一个内存变量的两个不同值(因为两个缓存中的保存的内存
地址不同,一个被修改过)。Volatile关键字可以有效地控制原始类型变量(primitive
variable,比如integer,boolean)的单一实例:当一个变量被定义为volatile的时候,无论读写,都会绕过缓存而直接对
main
memory进行操作。

关于Java的锁(Locking)有一个问题需要注意:一段被lock保护的代码并不意味着就一定不能被多线程同时访问,而只意味着不能被等待同一个lock的多线程同时访问。

对于绝大多数的synchronized方法,它的lock就是调用方法的实例对象;对于static
synchronized方法,它的lock是定义方法的类(因为static方法是每个类只有一份copy,而不是每个实例都有一份copy)。因
此,即使一个方法被synchronized保护了,多线程仍然可以同时调用这个方法,只要它们是调用不同实例上的这个方法。

synchronized代码块稍微复杂一些,一方面它也需要和synchronized方法一样定义lock的类型,另一方面必须考虑如果最小化
被保护的代码块,即能不放到synchronized里面就不放进去,比如局部变量的访问通通不需要保护,因为局部变量本身就只存在于单线程上。

下面两种加锁的方法是等效的,都是以Point类的实例为lock(即多线程可以同时访问不同Point实例的synchronized setXY()方法):

    public class Point {
public synchronized void setXY(int x, int y) {
this.x = x;
this.y = y; }
}
    public class Point {
public void setXY(int x, int y) {
synchronized (this) {
this.x = x;
this.y = y;
} }
}

死锁(deadlock)是多线程编程中最怕遇到的情况。什么是死锁?当2个或2个以上的线程因为等待彼此释放lock而处于无限的等待状态就称 为死锁。简单来说就是线程1拥有对象A的lock,等待获取对象B的lock,线程2拥有对象B的lock,等待获取对象A的lock,这样就没完没了 了。

如何检测deadlock?

检查代码,看是否有层叠的synchronized代码块,或者调用彼此的synchronized方法,或者试图获取多个对象上的lock,等等。如果程序员不注意的话,这些情况都容易导致deadlock。

怎么防止deadlock是一个大话题,可以写一本书,简单来说的话就是当线程需要获取多个lock的时候(比如线程1和2都要获取对象A和B的 lock),永远按照一定的次序来。比如如果线程1和2都是先获取对象A的lock,再获取对象B,那就不会出现上面的deadlock了,因为如果1获 得了A lock,2就得等,而不是去获得B lock。

总结一下synchronized关键字的一些注意点:

1、synchronized关键字确保了需要同一个lock的多线程永远无法同时或并行访问同一个共享资源或者synchronized方法

2、synchronized关键字只能修饰方法或者代码块

3、任何时候一个线程想要访问synchronized方法或者代码块时,都要先获取lock,任何时候一个线程结束访问synchronized方法或代码块时,都会释放lock。即使因为错误或异常结束访问,也会释放lock

4、Java线程进入一个实例层synchronized方法时,要先获取对象层面的lock(object level lock);进入静态synchronized方法时,要先获取类层面的lock(class level lock)

5、一个Java synchronized方法调用另一个synchronized方法,两个方法需要同一个lock的时候,线程不需要重新获取lock

6、在synchronized(myInstance)中,如果myInstance为Null,会抛出NullPointerException

7、synchronized关键字一个主要缺点就是它不支持并行的读取(因此对于一些值不可变的情况不要使用这个关键字,否则会无谓地影响performance)

8、synchronized关键字还有一个限制,它只支持单一JVM内的共享资源访问,对于多JVM共享一些文件资源或者数据库资源的时候,单单使用它就不够了,这时候程序员需要实现全局性的lock

9、synchronized关键字对performance影响很大,因此只有当真正需要的时候才用

10、优先使用synchronized代码块,而不是synchronized方法,确保将synchronized代码减小到最精,能不synchronized就不用synchronized关键字

11、静态和非静态的synchronized方法可能同时或者并行运行,因为它们被认为是使用了不同的lock(一个是object level,一个是class level)

12、从Java 5开始,对于volatile修饰的变量,读和写都被保证是原子的(atomic),即安全的。从performance的角度,操作volatile变量比从synchronized代码中访问变量要高效

13、synchronized代码可能会导致死锁

14、Java不允许在构造函数中使用synchronized关键字。理由很简单,如果构造函数中出现synchronized关键字,那当一个线程在构造实例时,其他线程都不知道,这就违背了同步的原则

15、synchronized关键字不能用于修饰变量,正如volatile关键字不能用于修饰方法

16、Java.util.concurrent.locks包提供了synchronized关键字的扩展功能,可以帮助程序员编写更为复杂的多线程操作

17、synchronized关键字同步内存(线程内存和主内存)

18、Java synchronization的一些关键方法,比如wait()、notify()、notifyAll(),定义在Object类中

19、在synchronized代码块中不要以非final变量(non final
field)为锁,因为非final变量的引用常常会改变,一旦锁改变了,那synchronization就失去了意义。比如这个例子,一旦对
String变量进行操作,就在内存中生成新的String对象

    private String lock = new String("lock");
synchronized(lock){
System.out.println("locking on :" + lock);
}

20、不推荐使用String对象作为synchronized代码块的锁,即使是final String。因为String存放在内存的String变量池中,可能会有其他代码或者第三方的代码使用了同一个String对象为锁,这样容易导致一 些无法预测的问题。在下面的例子中,与其使用LOCK为锁,还不如创建一个Object实例为锁。

    private static final String LOCK = "lock";   //not recommended
private static final Object OBJ_LOCK = new Object(); //better public void process() {
synchronized(LOCK) {
........
}
}

21、在Java库中,很多类默认不是线程安全的,需要程序员特别注意加上安全保护,比如Calendar, SimpleDateFormat等。

最新文章

  1. COGS1008. 贪婪大陆[树状数组 模型转换]
  2. 两轮自平衡小车双闭环PID控制设计
  3. Asp.net中HttpRequest.Params与Reques.Item之异同
  4. 在Firefox中通过AJAX跨域访问Web资源---
  5. newsstand杂志阅读应用源码ipad版
  6. css制作小三角
  7. commons-pool2-中的一些配置
  8. 抄360于Launcher浮动窗口的屏幕显示内存使用情况(改进版)
  9. jQuery中开发插件的两种方式
  10. .net下简单快捷的数值高低位切换
  11. Openjudge-计算概论(A)-苹果和虫子
  12. c=$[$c%5]到let c=$c%5的转换
  13. motan负载均衡/zookeeper集群/zookeeper负载均衡的关系
  14. MySQL中group_concat()函数的排序方法
  15. Python之函数基础
  16. 1.4 正则化 regularization
  17. 导出excel表格,前端和后台导出
  18. pd.read_csv() 、to_csv() 之 常用参数
  19. 机器学习 - 损失计算-softmax_cross_entropy_with_logits
  20. 结构型---享元模式(Flyweight Pattern)

热门文章

  1. 部署HBase系统(分布式部署)
  2. 小程序 image跟view标签上下会有空隙
  3. Sqrt(x)——二分法,防越界
  4. Path Sum I&&II
  5. .net中 Timer定时器
  6. Eclipse 报 "The builder launch configuration could not be found" 错误的解决办法
  7. 最简单的Web Service实现
  8. Sqli-labs less 2
  9. Flask实战第45天:完成前台登录界面
  10. 提高sqlmap爆破效率