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();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值