java 支持多个线程同时访问一个对象或者是对象的成员变量,关键字 synchroninzed
可以修饰方法或者以同步块的形式来使用,他主要确保多个线程在同一时刻,只能有一个线程处于处于方法或者是 同步块中,他保证了线程对变量访问的可见性和排他性,又称为内置锁机制。
对象锁和类锁
对象锁适用于对象实例方法,或者一个对象上的,类锁是用于类的静态方法或者一个类的Class 对象上的。我们知道类的对象实例可以有多个,但是每个类只有一个Class对象,所以不同对象的实例的对象锁是互不干扰的,但是每个类只有一个类锁。
有一点要注意:类锁是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class 对象。类锁和对象锁其实是互不干扰的。
线程间的协作
等待 / 通知机制
只能用在synchronized 包裹的块中
等待:wait() ; 通知:notifyAll();
概念:一个线程A调用了B的wait()方法进入等待状态,而另一个线程C调用了B 的notify()或者是notifyAll()方法,线程A 收到了通知后从B的wait()返回,进而执行后续的操作。上述两个线程通过对象B 来完成交互,而对象上的wait()方法和notify()方法的关系就如同开关一样,用来完成等待方和通知方之间的交互工作。
等待和通知的标准范式:
等待方遵循的条件
- 获取对象锁
- 如果条件不满足,那么久就调用对象的wait()方法,被通知后仍要检查条件。
-
条件满足执行对应的逻辑
通知方遵循的条件:
- 获取对象锁
- 改变对象的条件
-
通知所有等待在对象上的线程
在通知的时候尽可能的去使用notifyAll()
代码模块:
等待方
/**
* 快递
*/
public class Express {
public static final String CITY= "BeiJing";
private int km; //运输公里数
private String site; //到达地点
public Express() {
}
public Express(int km, String site) {
this.km = km;
this.site = site;
}
/**
* 公里数变化了
*/
public synchronized void changeKm(){
this.km =110;
notifyAll();
}
/**
* 到达地点变化了
*/
public synchronized void changeSite(){
this.site = "ShangHai";
notifyAll();
}
public synchronized void waitKm(){
while (km<100){
try {
wait();
System.out.println("check Km thread"+Thread.currentThread().getName()+"is be notifyed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("this is km "+km);
}
public synchronized void waitSite(){
while (CITY.equals(site)){
try {
wait();
System.out.println("check site thread"+Thread.currentThread().getName()+"is be notifyed");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("this is site " +site);
}
}
通知方的代码 包含测试代码
/**
* 测试wait nofify
*/
public class TextWn {
private static Express express = new Express(0,Express.CITY);
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new CheckKm().start();
}
for (int i = 0; i < 3; i++) {
new CheckSite().start();
}
Thread.sleep(1000);
express.changeKm();
express.changeSite();
}
public static class CheckSite extends Thread {
@Override
public void run() {
super.run();
express.waitSite();
}
}
public static class CheckKm extends Thread {
@Override
public void run() {
super.run();
express.waitKm();
}
}
}
以上可以直接运行。
ThreadLocal
使用static 的静态变量,数据隔离
ThreadLoacl 是线程变量,是一个以ThreadLocal为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值,ThreadLocal往往用来实现变量在线程之间的隔离。
ThreadLocal 接口只有四个方法:
- void set(Objcet value); 设置当前线程的局部变量的值
- Object get(); 返回当前线程的局部变量的值
- void remove(); 将当前线程局部变量的值删除,减少内存的占用。
- Objcet initalValue(); 返回该线程局部变量的初始化值。
测试代码:
public class UseThreadLocal {
ThreadLocal<Integer> threadLocal = new ThreadLocal(){
@Override
protected Object initialValue() {
return 1;
}
};
public void startThreadT(){
Thread[] runs = new Thread[3];
for(int i=0;i<runs.length;i++){
runs[i]=new Thread(new TestThread(i));
}
for(int i=0;i<runs.length;i++){
runs[i].start();
}
}
/**
*类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,
* 看看线程之间是否会互相影响
*/
public class TestThread implements Runnable{
int id;
public TestThread(int id){
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName()+":start");
Integer s = threadLocal.get();
s = s+id;
threadLocal.set(s);
System.out.println(Thread.currentThread().getName()+" :"
+threadLocal.get());
// threadLocal.remove();
}
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.startThreadT();
}
}
显式锁
Lock和synchronized 的比较
我们一般java 程序都是用synchronized 来进行加锁的,使用synchronized 将会隐式的获取锁,但是它将锁的获取和释放固化了,也就是先获取在释放,synchronized 是java 语言层面的锁,也称之为内置锁。
synchronized 这种机制,一旦开始获取锁,是不能中断的,也不提供尝试获取锁的机制。
Lock 是有java 语法层面提供的,锁的获取和释放是需要我们明显的去操作,因此被成为显式锁。并且提供了synchronized 不提供的机制。
Lock 的接口和核心方法
在finally 中释放锁,目的是获取锁以后能被释放
setLock.lock();
try {
//do....
}finally {
setLock.unlock();
}
可重入锁ReentrantLock 、所谓锁的公平与非公平
synchronized 关键字隐士的支持可重入,比如一个synchronized修饰的递归方法,在方法执行时,执行线程在获取了锁以后仍能连续多次的获的该锁。ReentrantLock在调用lock()方法时,已经获取到了锁的线程,能够在调取lock()方法获取所而不会阻塞。
公平和非公平锁
如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁,也可以说锁获取是顺序的。
ReentrantLock提供了一个构造函数,能够控制锁是否是公平的。事实上,公平的锁机制往往没有非公平的效率高。原因是,在恢复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程A持有一个锁,并且线程B请求这个锁。由于这个锁已被线程A持有,因此B将被挂起。当A释放锁时,B将被唤醒,因此会再次尝试获取锁。与此同时,如果C也请求这个锁,那么C很可能会在B被完全唤醒之前获得、使用以及释放这个锁。这样的情况是一种“双赢”的局面:B获得锁的时刻并没有推迟,C更早地获得了锁,并且吞吐量也获得了提高。
读写锁 ReentrantReadWriteLock
之前提到锁(synchronized和ReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量.
代码:
**
* 商品类
*/
public class GoodsInfo {
private final String name;
private double totalMoney; //销售总额
private int storeNumber; //库存数
public GoodsInfo(String name, double totalMoney, int storeNumber) {
this.name = name;
this.totalMoney = totalMoney;
this.storeNumber = storeNumber;
}
public double getTotalMoney() {
return totalMoney;
}
public int getStoreNumber() {
return storeNumber;
}
public void changeNumber(int sellNumber){
this.totalMoney += sellNumber*25;
this.storeNumber -= sellNumber;
}
}
/**
* 简单的业务应用场景
*/
public interface GoodsService {
public GoodsInfo getNum();//获得商品的信息
public void setNum(int number);//设置商品的数量
}
public class UseThread implements GoodsService{
private GoodsInfo goodsInfo;
private ReadWriteLock lock =new ReentrantReadWriteLock();
private Lock rlock = lock.readLock();
private Lock wlock = lock.writeLock();
public UseThread(GoodsInfo goodsInfo) {
this.goodsInfo = goodsInfo;
}
@Override
public GoodsInfo getNum() {
rlock.lock();
try {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.goodsInfo;
} finally {
rlock.unlock();
}
}
@Override
public void setNum(int number) {
wlock.lock();
try {
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
goodsInfo.changeNumber(number);
}finally {
wlock.unlock();
}
}
}
public class BusiApp {
static final int readWriteRatio = 10;//读写线程的比例
static final int minthreadCount = 3;//最少线程数
//读操作
private static class GetThread implements Runnable {
private GoodsService goodsService;
public GetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){//操作100次
goodsService.getNum();
}
System.out.println(Thread.currentThread().getName()+"读取商品数据耗时:"
+(System.currentTimeMillis()-start)+"ms");
}
}
//写操作
private static class SetThread implements Runnable {
private GoodsService goodsService;
public SetThread(GoodsService goodsService) {
this.goodsService = goodsService;
}
@Override
public void run() {
long start = System.currentTimeMillis();
Random r = new Random();
for(int i=0;i<10;i++){//操作10次
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
goodsService.setNum(r.nextInt(10));
}
System.out.println(Thread.currentThread().getName()
+"写商品数据耗时:"+(System.currentTimeMillis()-start)+"ms---------");
}
}
public static void main(String[] args) {
GoodsInfo goodsInfo = new GoodsInfo("Cup",100000,10000);
GoodsService goodsService = new UseThread(goodsInfo);
for(int i = 0;i<minthreadCount;i++){
Thread setT = new Thread(new SetThread(goodsService));
for(int j=0;j<readWriteRatio;j++) {
Thread getT = new Thread(new GetThread(goodsService));
getT.start();
}
try {
TimeUnit.MILLISECONDS.sleep(100);
setT.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Condition接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
用Lock和Condition实现等待通知