Java-多线程-锁

synchronized 内置锁

类锁

// 使用类锁的线程
private static class SynClass extends Thread {
    @Override
    public void run() {
        System.out.println("TestClass is running...");
        synClass();
    }
}

// 类锁,实际是锁类的class对象
// synchronized == 类锁 == SynClzAndInst.class的对象锁
private static synchronized void synClass() {
    SleepTools.second(1);
    System.out.println("synClass going...");
    SleepTools.second(1);
    System.out.println("synClass end");
}

// 特殊的类锁 obj对象类锁 
private static Object obj = new Object();

private void synStaticObject() {
    synchronized (obj) {//类似于类锁
        SleepTools.second(1);
        System.out.println("synClass going...");
        SleepTools.second(1);
        System.out.println("synClass end");
    }
}

对象锁

  • 锁不能是匿名对象。因为匿名对象不是同一个对象,也就不是同一把锁
// 使用对象锁
private static class SynObject implements Runnable {
    private SynClzAndInst synClzAndInst;

    public SynObject(SynClzAndInst synClzAndInst) {
        this.synClzAndInst = synClzAndInst;
    }

    @Override
    public void run() {
        System.out.println("TestInstance is running..." + synClzAndInst);
        synClzAndInst.instance();
    }
}

// 使用对象锁
private static class SynObject2 implements Runnable {
    private SynClzAndInst synClzAndInst;

    public SynObject2(SynClzAndInst synClzAndInst) {
        this.synClzAndInst = synClzAndInst;
    }

    @Override
    public void run() {
        System.out.println("TestInstance2 is running..." + synClzAndInst);
        synClzAndInst.instance2();
    }
}

// 锁对象
private synchronized void instance() {
    SleepTools.second(3);
    System.out.println("synInstance is going..." + this.toString());
    SleepTools.second(3);
    System.out.println("synInstance ended " + this.toString());
}

// 锁对象
private synchronized void instance2() {
    SleepTools.second(3);
    System.out.println("synInstance2 is going..." + this.toString());
    SleepTools.second(3);
    System.out.println("synInstance2 ended " + this.toString());
}
  • SleepTools
import java.util.concurrent.TimeUnit;

/**
 *
 * 类说明:线程休眠辅助工具类
 */
public class SleepTools {
    
    /**
     * 按秒休眠
     * @param seconds 秒数
     */
    public static final void second(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
    
    /**
     * 按毫秒数休眠
     * @param seconds 毫秒数
     */
    public static final void ms(int seconds) {
        try {
            TimeUnit.MILLISECONDS.sleep(seconds);
        } catch (InterruptedException e) {
        }
    }
}

缺点:

  • synchronized是不能中断的
  • synchronized没法尝试等10秒后 再去拿下锁

可重入锁

synchronized

  • synchronized就是可重入锁
  • 可重入锁就是可以递归调用自己,锁可以释放出来
  • synchronized如果没有实现可重入,会自己把自己锁死
// 如果是非重入锁🔒 ,就会自己把自己锁死
public synchronized void incr2(){
    count++;
    incr2();
}

ReentrantLock

// 声明一个显示锁之可重入锁  new 可重入锁
// 非公平锁
private Lock lock = new ReentrantLock();

public void incr(){
    // 使用 显示锁 的规范
    lock.lock();
    try{
        count++;
    } finally {   // 打死都要执行  最后一定会执行
        lock.unlock();
    }
}
  • 公平锁
 new ReentrantLock(true);

死锁

package demo2;

/**
 * 定义死锁任务
 */
class DieLockThread extends Thread {

    /**
     * 此变量已经不是共享数据了,因为:
     *              DieLockThread extends Thread
     *              new DieLockThread().start();
     *              new DieLockThread().start();
     *
     * 所以:Thread-0有自己的flag     Thread-1也有自己的flag
     */
    private boolean flag;

    public DieLockThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {

        int i = 0;
        int j = 0;

        if (flag) {

            while (true) {
                // 第一步:CPU随机性切换到:Thread-1   Thread-1正常往下走......
                // 第六步:CPU随机性切换到:Thread-1 ,然后判断同步锁 注意:此时的同步锁🔒Lock == 第二把锁🔒, 第二把锁🔒是被Thread-1锁定的,就造成了线程之间不和谐:死锁

                synchronized (Lock.LOCK1) // 使用第一把锁🔒
                {

                    synchronized (Lock.LOCK2) // 使用第二把锁🔒
                    {
                        System.out.println("一一一一一一一一一一一一" + i++);
                        // 第二步:CPU随机性切换到:Thread-1  就在此时,CPU执行权没有了,  CPU去执行其他线程了
                    }
                }
                // 第四步:   CPU随机性切换到:Thread-1   Thread-1正常往下走完,并解锁🔓
            }

        } else {

            while(true) {
                // 第三步:CPU随机切换到:Thread-0,然后判断同步锁 注意:此时的同步锁🔒Lock == 第二把锁🔒, 第二把锁🔒是被Thread-1锁定的,就造成了线程之间不和谐:死锁

                synchronized (Lock.LOCK2) // 使用第二把锁🔒
                {

                    synchronized (Lock.LOCK1) // 使用第一把锁🔒
                    {

                        // 第五步:CPU随机性切换到:Thread-0 就在此时,CPU执行权没有了,  CPU去执行其他线程了
                        System.out.println("二二二二二二二二二二二二" + j++);
                    }

                }

            }

        }
    }
}

/**
 * 定义两把锁🔒
 */
class Lock {
    public final static Object LOCK1 = new Object();
    public final static Object LOCK2 = new Object();
}

public class DieLockDemo {

    public static void main(String[] args) {
        // 多线程
        new DieLockThread(true).start();
        new DieLockThread(false).start();
    }

}

生产者消费者

  • 生产一个,消费一个

/**
 * 描述资源
 */
class Res3 {

    /**
     * name 是共享数据,被Thread-0 Thread-1公用使用
     */
    private String name;

    /**
     * id 是共享数据,被Thread-0 Thread-1公用使用
     */
    private int id;

    /**
     * flag 是共享数据,被Thread-0 Thread-1公用使用
     */
    private boolean flag; // 定义标记 默认第一次为false

    /**
     * 对操作共享数据的地方加入同步锁的方式来解决安全问题
     * public synchronized(this) void put(String name) {
     */
    public synchronized void put(String name) {

        /**
         * 生产之前判断标记
         */
        if (!flag) {

            // 开始生产
            id += 1;
            // this.name = name + " 商品编号:" + id;
            System.out.println(Thread.currentThread().getName() + "生产者 生产了:" + this.id);
            // 生产完毕

            /**
             * 修改标记
             */
            flag = true;

            /**
             * 唤醒 wait(); 冻结的线程,如果没有就是空唤醒,Java是支持的
             * 生产好了,消费者 快来买 ,唤醒
             */
            notify(); // 注意:⚠️ wait();  notify();  这些必须要有同步锁包裹着

            /**
             * 当前自己线程 冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
             */
            try {
                // 生产好一个,我就去睡觉了
                wait(); // 注意:⚠️ wait();  notify();  这些必须要有同步锁包裹着
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 对操作共享数据的地方加入同步锁的方式来解决安全问题
     * public synchronized(this) void put(String name) {
     */
    public synchronized void out() {

        /**
         * 消费之前判断标记
         */
        if (flag) {

            // 开始消费
            System.out.println(Thread.currentThread().getName() +  ">>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:" + this.id);
            // 消费完毕

            /**
             * 修改标记
             */
            flag = false;

            /**
             * 唤醒 wait(); 冻结的线程,如果没有就是空唤醒,Java是支持的
             * 唤醒生产者,你可以再生产一个面包了
             */
            notify(); // 注意:⚠️ wait();  notify();  这些必须要有同步锁包裹着

            /**
             * 当前自己线程 冻结,释放CPU执行资格,释放CPU执行权,CPU就会去执行其他线程了
             */
            try {
                // 顾客又睡觉了
                wait(); // 注意:⚠️ wait();  notify();  这些必须要有同步锁包裹着
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 描述生产者任务
 */
class ProduceRunnable3 implements Runnable {

    /**
     * 此变量已经不是共享数据了,因为:
     *              new Thread(produceRunnable).start();
     *              new Thread(consumeRunnable).start();
     *
     * 所以:Thread-0有自己的res     Thread-1也有自己的res
     */
    private Res3 res;

    ProduceRunnable3(Res3 res) {
        this.res = res;
    }

    /**
     * 执行线程任务
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            res.put("面包🍞");
        }
    }
}

/**
 * 描述消费者任务
 */
class ConsumeRunnable3 implements Runnable {

    /**
     * 此变量已经不是共享数据了,因为:
     *              new Thread(produceRunnable).start();
     *              new Thread(consumeRunnable).start();
     *
     * 所以:Thread-0有自己的res     Thread-1也有自己的res
     */
    private Res3 res;

    ConsumeRunnable3(Res3 res) {
        this.res = res;
    }

    /**
     * 执行线程任务
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            res.out();
        }
    }
}

/**
 *    wait(); 等待/冻结 :可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池
 *    notify(); 唤醒线程池里面 任意一个线程,没有顺序;
 *    notifyAll(); 唤醒线程池里面,全部的线程;
 *    注意:⚠️ wait(); notify(); 这些必须要有同步锁包裹着
 *
 */
public class ThreadCommunicationDemo3 {

    public static void main(String[] args) {
        // 创建资源对象
        Res3 res = new Res3();

        // 创建生产者任务
        ProduceRunnable3 produceRunnable = new ProduceRunnable3(res);

        // 创建消费者任务
        ConsumeRunnable3 consumeRunnable = new ConsumeRunnable3(res);

        // 启动生产者任务
        new Thread(produceRunnable).start();

        // 启动消费者任务
        new Thread(consumeRunnable).start();
    }

}

打印日志:

Thread-0生产者 生产了:1
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:1
Thread-0生产者 生产了:2
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:2
Thread-0生产者 生产了:3
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:3
Thread-0生产者 生产了:4
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:4
Thread-0生产者 生产了:5
Thread-1>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 消费者 消费了:5

等待唤醒机制:

  • wait(); 等待/冻结:可以将线程冻结,释放CPU执行资格,释放CPU执行权,并把此线程临时存储到线程池
  • notify(); 唤醒线程池里面 任意一个线程,没有顺序;
  • notifyAll(); 唤醒线程池里面,全部的线程;

wait 或者 notify 为什么需要 syn 包裹,否则报错?

首先 wait 和 notify 需要的是同一把锁

wait 内部操作:
1.获取对象锁
2.检测条件等等内部逻辑
waitsyn(this) 已经被锁住了,notify就无法获取锁了。wait() 后,持有锁会被释放

notify 内部操作:
1.获取对象锁
2。然后
syn(this)
notify 或者 notifyAll

使用等待唤醒注意事项:

  • 1.使用来wait();冻结,就必须要被其他方notify();,否则一直wait()冻结,所以等待与唤醒是配合一起使用的;
  • 2.wait(); notify(); notifyAll();等方法必须被synchronized(锁) {包裹起来},意思就是:wait(); notify(); notifyAll(); 必须要有同步锁,否则毫无意义;
  • 3.wait(); notify(); notifyAll(); 等方法必须持有同一把锁,因为:lockA.wait(); 只能使用 lockA.notify(); / lockA.notifyAll();, 它们是使用同一把锁的;

读写锁

  • 读写锁比sync效率更高。sync锁住后其他线程是无法进入的。但是读取操作是不会引发安全问题的,读写锁即使锁住了,还是允许线程进入读取数据,所以时间更短
/**
 * 类说明:商品的实体类
 */
public class GoodsInfo {
    private final String name;
    private double totalMoney;//总销售额
    private int storeNumber;//库存数

    public GoodsInfo(String name, int 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 class UseSyn implements GoodsService {
    
    private GoodsInfo goodsInfo;
    
    public UseSyn(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }

    // 读取
    @Override
    public synchronized GoodsInfo getNum() {
        SleepTools.ms(5);
        return this.goodsInfo;
    }

    // 读取
    @Override
    public synchronized void setNum(int number) {
        SleepTools.ms(5);
        goodsInfo.changeNumber(number);

    }

}

/**
 * 类说明: 这里采用 【读写锁】
 */
public class UseRwLock implements GoodsService{

    private GoodsInfo goodsInfo;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock getLock = lock.readLock(); // 读锁
    private final Lock setLock = lock.writeLock(); // 写锁

    public UseRwLock(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }

    // 读取
    @Override
    public GoodsInfo getNum() {
        getLock.lock();
        try {
            SleepTools.ms(5);
            return this.goodsInfo;
        }finally {
            getLock.unlock();
        }
    }

    // 写入
    @Override
    public void setNum(int number) {
        setLock.lock();
        try {
            SleepTools.ms(5);
            goodsInfo.changeNumber(number);
        }finally {
            setLock.unlock();
        }
    }

}

ThreadLocal

  • 给每个线程进行了隔离

没有ThreadLocal的情况演示:

/**
 * 类说明:没有ThreadLocal的情况演示
 *
 * 数字会乱套 例如:341  134  124 等等
 */
public class NoThreadLocal {

    // 静态的
    static Integer count = new Integer(1);

    /**
     * 运行3个线程
     */
    public void StartThreadArray() {
        Thread[] runs = new Thread[3];
        for (int i = 0; i < runs.length; i++) {
            runs[i] = new Thread(new TestTask(i));
        }
        for (int i = 0; i < runs.length; i++) {
            runs[i].start(); // 启动三个线程
        }
    }

    /**
     * 类说明:
     */
    public static class TestTask implements Runnable {
        int id;

        public TestTask(int id) {
            this.id = id;
        }

        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            count = count + id; // 123  231
            System.out.println(Thread.currentThread().getName() + ":" + count);
        }
    }

    public static void main(String[] args) {
        NoThreadLocal test = new NoThreadLocal();
        test.StartThreadArray();

        // 每个线程没法独有自己的一份数据
    }
}

演示ThreadLocal的使用:


/**
 * 说明:演示ThreadLocal的使用
 *
 * 数字不会乱套 例如:123  132  123 213 等等 始终在123范围中
 */
public class UseThreadLocal {

    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    /**
     * 运行3个线程
     */
    public void StartThreadArray() {
        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 static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        public void run() {
            System.out.println(Thread.currentThread().getName() + ":start");
            // 如果使用了 ThreadLocal 会单独Copy一份 到 当前线程 例如 Thread-0
            Integer s = threadLocal.get();
            s = s + id;
            threadLocal.set(s); // 这里所修改的内容  只对 Thread-0 有效果, 和 Thread-1 没有半毛钱关系
            System.out.println(Thread.currentThread().getName() + " :"
                    + threadLocal.get());
            //threadLocal.remove();
        }
    }

    public static void main(String[] args) {
        UseThreadLocal test = new UseThreadLocal();
        test.StartThreadArray();
    }
}

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

推荐阅读更多精彩内容