1. 概述
Java中的线程表现为Thread
class,创建线程的唯一方式就是创建此类的一个实例对象。
使用Java代码有两种的方式(但最终都是创建一个Thread类实例)来创建一个线程:
- 继承Thread类:不必说,他就是Thread类的实例
- 实现Runable接口:使用时需要将实现类作为参数传递给一个Thread类的实例
2. 同步
Java提供了很多种线程之间通信的机制,最基本的一种就是 synchronization - 同步,这是通过 monitor - 监视器来实现的
首先,所有对象都关联着一个监视器,线程可以锁定或者解锁此监视器;
其次,且同一时间只能有一个线程可以锁定监视器,如果有其它线程想要尝试锁定的话它就会 block - 阻塞,直到它获得锁定权;
2.1 synchronized statement - 语句
synchronized
语句被执行时,会尝试去锁定其指定的对象上的监视器,直到成功为止;然后执行语句,最后自动解锁
语法:在synchronized (Expression) {}
的Expression
显示指定一个对象(最好的方式是实例语句中为this
,类语句中为 类.class
)
2.2 synchronized method - 方法
synchronized
方法不同与语句的地方只在于其充当监视器的对象不能指定,而是只能按照默认规则来:实例语句中为this
,类语句中为 类.class
2.3 deadlock - 死锁
Java语言不会阻止和检测死锁,程序如果需要避免死锁,应该使用自己保证
2.4 其它同步机制
volatile
变量
任何被volatile
修饰的变量,都不拷贝副本到工作内存,而是任何修改直接操作主内存。因此,对于volatile
变量的修改,所有线程可以马上看到。但是,volatile
不能保证线程们对变量的修改是有序的。很简单,比如说多个线程操作
volatile
变量a
时,public volatile int a; public void add(int b){ a = a + b; } //比如,a = 3,每个线程的b都是传1。 //1. 第一个线程来了,先进行3 + 1 记录,然后准备将4赋值给a时丢失CPU; //2. 第二个线程来了,也是3 + 1,然后赋值成功a等于4了。 //3. 第一个线程恢复CPU权,将4赋值给a。 //如上,其实正确结果无论线程一或二谁先执行,都应该是a = 5。但由于上述执行过程,a最终还是4,此时就是线程不安全。
由于
a = a + b;
并非是一个原子操作,所以有可能第一个线程执行到一半时停止而第二个线程开始执行。所以,
volatile
适用的场景是有限的,总结如下:
可行必要条件- 对变量的写操作不依赖当前值。
- 变量没有包含在具有其它变量的不变式中。
其实由于
volatile
只保证了可见性,所以其最符合直接赋值的场景。如上代码如果add
方法内部只是a = b;
即是。java.util.concurrent
包中的类
对象、监视器、锁、线程的关系如下: