每一个线程都有自己的执行流,有自己的栈,有自己的缓存区域。
当使用多线程编程时,经常回出现的问题就是并发访问,并发访问则会带来两个问题:竞态条件,内存可见性
竞态条件,就是多个线程同时访问同一个资源的时候,最终的执行结果与执行的时序有关,有可能正确,也可能不正确。
解决静态条件的几种方法:使用synchronized关键字,使用显示锁,使用原子变量
内存可见性,就是当一个线程对一个资源修改后,其他线程不无法知道该资源被更改了。因为线程从主内存中获取到资源进行操作后并不会及时的同步到主内存,这样当其他线程需要对该资源操作的时候,从主内存中获取到的资源并不是最新的。
解决内存可见性的几种方法:使用volatile关键字,使用synchronize或显示锁进行同步。
线程的优点不多说,说说缺点:
线程的上下文切换,需要保存线程的上下文状态,包括当前cpu寄存器的值,程序计数器的值等,切换不仅耗时,而且使cpu中的很多缓存失效。当然这些成本是相对而言的,如果线程代码中执行的内存是很多的,这种成本是值得的,如果仅有i++这种代码则大大浪费了。另外,如果执行的任务都是cpu密集型的,即主要消耗的都是cpu,则创建超过cpu数量的线程是没有必要的,并不会加快程序的执行。
下面来说synchronized,首先synchronized是同步锁,用于修饰类的
实例方法
public void synchronized add(){...}
静态方法
public static void synchronized add(){...}
代码块
public void add(){
synchronized (this){
i++
}
}
synchronized保护的是对象,而非代码,只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被顺序访问(一个对象中两个被synchronized修饰的方法,一个线程执行a, 一个线程执行b, 只能被同步顺序访问,不能同时访问)。当然非synchronized方法可以被同时执行,所以要在使用synchronized时,要锁住所有被操作的共享资源。多个线程可以同时执行同一个synchronized实例方法,只要他们访问的对象是不同的即可。
对于实例方法,保护的是当前实例对象this,
对于静态方法,保护的是类对象,即user.class 每个对象都有一个锁和一个等待队列,类对象也不例外。
synchronized可以实现原子操作(加锁)和解决内存可见性(释放锁后刷新内存,获取锁时读取内存)
synchronized的性质:可重入性,内存可见性,死锁。
可重入性,就是指同一个线程在获取锁之后,在调用其他同样需要锁的代码时,可以直接调用,可重入是通过记录锁的持有线程和持有数量来实现的。
synchronized可以实现原子操作,避免出现静态条件,但对于本来就是原子操作的方法时,是否需要synchrinized呢?需要。因为还存在内存可见性问题,synchronized可以解决内存可见性问题:释放锁时,所有写入都会写回内存,释放锁后,都会从内存获取最新数据。但是为了实现内存可见性使用synchronized成本较高,可以使用volatile关键字,加了volatile关键字后,java会在操作对应的变量时插入特殊的指令,保证读写到内存最新值,而非缓存值。