线程间的共享和协作

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 机制

Lock 的接口和核心方法
在finally 中释放锁,目的是获取锁以后能被释放

       setLock.lock();
        try {
            //do....        
        }finally {
            setLock.unlock();
        }
lock 方法

可重入锁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实现等待通知

Condition

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容