6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)

线程锁

  • 1、※线程的同步:(要确保对象锁是一致的)
    • 1、未使用同步锁的抢票
    • 2、使用了同步锁的抢票
    • 3、线程-同步代码块的使用
    • 4、同步方法和代码块的区别
    • 5、同步锁释放的时机
    • 练习:多线程生产手机
  • 2※、ReentrantLock可重入锁:【优点:可以非阻塞操作,定义操作逻辑】
    • 1、可重入锁的使用
    • 2、可重入锁的注意事项1
      • 解一把锁不会输出调用test2的方法,解除两把锁才是正常输出
    • 3、可重入锁的注意事项2
    • 4、公平锁与非公平锁的区别
  • 3※、什么是死锁?
  • 4※、线程间的通信(生产者和消费者模式)

1、※线程的同步:(要确保对象锁是一致的)

同步的概念:确保代码执行的有序性、事务执行的完整性,数据共享的可靠性和正确性;在多线程并发的情况下,使得在同一个时间点只能有一个线程访问同步方法或同步代码块
同步方法:使用this作为当前同步方法的同步锁,只有获得此对象锁的线程才能执行同步方法中的操作
this是调用当前方法的对象的引用
同步代码块:可以灵活定义同步修饰的代码区域,自定义同步锁,也能实现同步代码块的嵌套

1、未使用同步锁的抢票

注意:会出现同一张火车票给多个线程获取
现实生活中这种情况是不允许出现
京东抢到了序号为;100的车型
飞猪抢到了序号为;100的车型
智行抢到了序号为;100的车型
智行抢到了序号为;97的车型
智行抢到了序号为;96的车型
飞猪抢到了序号为;98的车型

/**
 * @author Lantzrung
 * @date 2022年8月5日
 * @Description
 */
package com.day0805sileo01;

public class TicketTask2 implements Runnable {

    
    // 测试操作
    public static void main(String[] args) {
	//
	TicketTask2 task = new TicketTask2();
	// 1、创建线程的对象
	Thread jingdong = new Thread(task, "京东");
	Thread zhixing = new Thread(task, "智行");
	Thread feizhu = new Thread(task, "飞猪");
	// 2、启动线程
	jingdong.start();
	zhixing.start();
	feizhu.start();
    }

    // 定义票数 【资源共享】
    public int nums = 100;

    // 实现抢票操作
    @Override
    public void run() {
	// 抢票--循环操作
	while (nums > 0) {
	    // 获取线程名称
	    String name = Thread.currentThread().getName();
	    System.out.println(name + "抢到了序号为;" + nums + "的车型");
	    // 票数--
	    nums--;

	}
    }
}

2、使用了同步锁的抢票

调用了sleep方法后,线程会进入阻塞状态,释放了cpu执行权,但是没有释放对象锁

/**
 * @author Lantzrung
 * @date 2022年8月5日
 * @Description
 */
package com.day0805sileo01;

public class TicketTask implements Runnable {
    // 测试操作
    public static void main(String[] args) {
	// 只有一个任务对象 (task的对象锁)
	TicketTask task = new TicketTask();
	// 1、创建线程的对象
	Thread jingdong = new Thread(task, "京东");
	Thread zhixing = new Thread(task, "智行");
	Thread feizhu = new Thread(task, "飞猪");
	// 2、启动线程
	jingdong.start();
	zhixing.start();
	feizhu.start();
//	京东抢到了序号为:100的车票
//	飞猪抢到了序号为:99的车票
//	智行抢到了序号为:98的车票
//	京东抢到了序号为:97的车票
//	飞猪抢到了序号为:96的车票
//	智行抢到了序号为:95的车票
//	飞猪抢到了序号为:94的车票
//	京东抢到了序号为:93的车票
//	智行抢到了序号为:92的车票
//	飞猪抢到了序号为:91的车票
//	京东抢到了序号为:90的车票
//	智行抢到了序号为:89的车票
//	京东抢到了序号为:88的车票
//	飞猪抢到了序号为:87的车票
//	智行抢到了序号为:86的车票
    }

    // 定义票数 【资源共享】
    public int nums = 100;

    // 实现抢票操作
    @Override
    public void run() {
//	saleTicket();// 调用方法
	// 注意:放在这里只会执行这几个
//	100抢到了序号为:100的车票
//	99抢到了序号为:99的车票
//	98抢到了序号为:98的车票
	// 抢票--循环操作
	while (nums > 0) {
	    // 睡眠
	    try {
		saleTicket();// 调用方法
		Thread.sleep(20);
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    }
	}
    }
    // 定义一个同步方法【限定在同一个时间点,只能有一个线程执行操作】
    // 同步锁(对象锁):只有拿到了该对象锁的线程才能执行这个同步操作(this--作为同步方法的对象锁)
    // 注意:在同步方法中,循环操作中睡眠不会释放对象锁
//    OPEN2
    public synchronized void saleTicket() {
	if (nums > 0) {
	    // 获取线程名称
	    String name = Thread.currentThread().getName();
	    System.out.println(name + "抢到了序号为:" + nums + "的车票");
	    // 票数--
	    nums--;
	}
    }

//     OPEN1: 这个操作是一直循环输出 京东线程 因为使用的是while循环 就算加上睡眠操作还是会一直输出该京东线程的   
//    public synchronized void saleTicket() { 
//	while (nums > 0) {
//	    // 获取线程名称
//	    String name = Thread.currentThread().getName();
//	    System.out.println(name + "抢到了序号为;" + nums + "的车型");
//	    // 票数--
//	    nums--; 
//    // 睡眠
//    try {
//	Thread.sleep(20);
//    } catch (InterruptedException e) {
//	e.printStackTrace();
//    } 
		京东抢到了序号为;25的车型
		京东抢到了序号为;24的车型
		京东抢到了序号为;23的车型
		京东抢到了序号为;22的车型
		京东抢到了序号为;21的车型
		京东抢到了序号为;20的车型
		京东抢到了序号为;19的车型
		京东抢到了序号为;18的车型
		京东抢到了序号为;17的车型
		京东抢到了序号为;16的车型
		京东抢到了序号为;15的车型
		京东抢到了序号为;14的车型
		京东抢到了序号为;13的车型
		京东抢到了序号为;12的车型
		京东抢到了序号为;11的车型
		京东抢到了序号为;10的车型
		京东抢到了序号为;9的车型
		京东抢到了序号为;8的车型
		京东抢到了序号为;7的车型
		京东抢到了序号为;6的车型
		京东抢到了序号为;5的车型
		京东抢到了序号为;4的车型
		京东抢到了序号为;3的车型
		京东抢到了序号为;2的车型
		京东抢到了序号为;1的车型
//	}
}


3、线程-同步代码块的使用

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

public class TicketTask implements Runnable {
    public static void main(String[] args) {
	// 创建票数对象
	TicketTask task = new TicketTask();
	// 创建线程对象
	Thread jd = new Thread(task, "京东");
	Thread zx = new Thread(task, "智行");
	Thread fz = new Thread(task, "飞猪");
	// 启动线程
	jd.start();
	zx.start();
	fz.start();
    task.test();
//	test
//	飞猪抢到了序号为:100
//	智行抢到了序号为:99
//	京东抢到了序号为:98
//	京东抢到了序号为:97
//	智行抢到了序号为:96
//	飞猪抢到了序号为:95
    }

    // 定义票数
    public int nums = 100;

    // 实现抢票操作
    @Override
    public void run() {
	// 抢票--循环操作
	while (nums > 0) {
	    // 同步代码块【同步监视的区域】 【对象锁是一个对象即可】
//	    synchronized (this) {
		synchronized ("a") {
		if (nums > 0) {
		    // 获取线程名称
		    String name = Thread.currentThread().getName();
		    System.out.println(name + "抢到了序号为:" + nums);
		    // 票数--
		    nums--;
		}
	    }
	    // 睡眠
	    try {
		Thread.sleep(20);
	    } catch (InterruptedException e) {
		e.printStackTrace();
	    }
	}
    }
    // 同步代码块可以跨方法、跨类进行监听
    public void test() { 
	synchronized ("a") {
	    System.out.println("test");
	}
    }
}

4、同步方法和代码块的区别

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

public class TicketDemo {
    // this
    public synchronized void demo() {
	System.out.println("执行demo操作");
    }

    // 嵌套上锁 【同步代码块】
    public void test1() {
	synchronized ("a") {
	    System.out.println("执行操作a");
	    synchronized ("b") {
		System.out.println("执行操作b");
	    }
	}
    }

    // 两个类型不一样的,因为一个"a"常量池里面的 一个重新new出来的对象String 两个的情况不一样的
    public void drawMoney() {
	synchronized ("a") {
	}
	synchronized (new String("a")) {
	}
    }

5、同步锁释放的时机

思考:同步锁释放的时机

–1、正常执行完同步代码
–2、遇到return、break结合标签时
–3、抛出异常、出现错误
–4、当线程进行等待时,调用了wait()方法

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

import java.util.Iterator;

import org.omg.PortableServer.ID_ASSIGNMENT_POLICY_ID;

public class TestBlock {

    public static void main(String[] args) {
	// 创建对象为
	TestBlock b = new TestBlock();
	// 1、创建线程
	Thread t1 = new Thread(new Runnable() {
	    @Override
	    public void run() {
		b.test1();
	    }
	});
	Thread t2 = new Thread(new Runnable() {

	    @Override
	    public void run() {
		b.test2();
	    }
	});
	// 启动线程
	t1.start();
	t2.start();
    }

    public synchronized void test1() {
//	进入test1
//	进入test2
	System.out.println("进入test1");

	// 时机4:当线程进行等待时,调用了wait()方法
	// 注意:这里是线程进去等待的状态,而不会释放对象锁
	for (int i = 1; i <= 5; i++) {
	    if (i == 3) {
		try {
		    wait();// 注意:这里是线程进去等待的状态,而不会释放对象锁
		} catch (InterruptedException e) {
		    e.printStackTrace();
		}
	    }
	}

	// 时机3:为抛出异常、出现错误
	// 直接跳出异常体,不执行里面异常体内的代码块
//	进入test1
//	进入test2
//	Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
//		at com.day0806.TestBlock.test1(TestBlock.java:45)
//		at com.day0806.TestBlock$1.run(TestBlock.java:21)
//		at java.lang.Thread.run(Thread.java:745)

//	for (int i = 1; i <= 5; i++) {
//	    try {
//		// 出现异常
//		if (i == 3) {
//		    int item = 3 / 0;
//		}
//		Thread.sleep(1000);
//	    } catch (InterruptedException e) {
//		e.printStackTrace();
//	    }
//	}

//	// 时机2:遇到return、break结合标签时
//	for (int i = 1; i <= 5; i++) {
//	    try {
//		//
//		if (i == 2) {
//		    break;// break和return都是执行输出一次 test1.......
		    continue;// continue执行4次 test1.......		  
//		}
//		System.out.println("test1.......");
//		Thread.sleep(1000);
//	    } catch (InterruptedException e) {
//		e.printStackTrace();
//	    }
//	}

	// 时机1:正常执行完同步代码
//	for (int i = 1; i <= 5; i++) {
//	    try {
//		Thread.sleep(1000);
//		System.out.println("test...");
//	    } catch (InterruptedException e) {
//		e.printStackTrace();
//	    }
//	}
	// open1:
//	进入test1
//	test...
//	test...
//	test...
//	test...
//	test...
//	进入test2
    }

    public synchronized void test2() {
	System.out.println("进入test2");
    }
}

练习:多线程生产手机

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

// 手机生产类
public class PhoneProducer {

    public static void main(String[] args) {
	// 提供生产类的对象
	PhoneProducer producer = new PhoneProducer();
	// run任务类
	Runnable task = new Runnable() {

	    @Override
	    public void run() {
		// 循环生产手机
		while (producer.num > 0) {
		    producer.produceint();
		    // 延迟
		    try {
			Thread.sleep(50);
		    } catch (InterruptedException e) {
			e.printStackTrace();
		    }
		}
	    }
	};
	// 创建多个工人【线程】
	Thread t1 = new Thread(task, "翠花");
	Thread t2 = new Thread(task, "如花");
	Thread t3 = new Thread(task, "小平");
	// 开始生产
	t1.start();
	t2.start();
	t3.start();
    }

    // 限定每天100台
    int num = 100;

    // 提供一个生产方法【this对象要唯一】
    public synchronized void produceint() {
	if (num >= 1) {
	    //
	    String name = Thread.currentThread().getName();
	    //
	    System.out.printf("%s生产了No.xs%d的手机!\n", name, num);
	    //
	    num--;
	}
    }

}

2※、ReentrantLock可重入锁:【优点:可以非阻塞操作,定义操作逻辑】

–可重入锁特点:
(A) 上锁次数
[1] lock.lock() 可以上锁多次 [可重入]
[2] 你想完全解锁, 必须解够上锁的次数。
解锁次数 == 上锁次数
(B) 上锁与解锁
[1] 你可以在任意位置上锁, 也可以任意位置解锁。
[2] 但是 上锁与解锁的线程必须保证是同一个线程, 否则, 会发生线程处理状态异常。
© 尝试上锁tryLock(),如果有获取到锁则上锁并且操作,没有获取到锁,也不会阻塞线程,也可以设置超时时间
(D) 可以设置公平锁和非公平锁【默认是非公平】
非公平锁:抢夺式,谁抢到就谁执行【可以先直接抢,后排队】
公平锁:每个线程的获取资源的几率是一样的【直接排队】

– 注意事项: [1] 确保lock是同一个对象锁 [2] 防范异常的发生, 发现有如下的问题, 因为发生一个异常,
导致锁无法释放。
应该采取某个策略来防止类似的事情发生。
引入异常处理机制 try{ } catch(){ }finally{ }
将 lock.unlock(); 放入 finally 中。 [3] 为了防止 Lock 引用被修改, 请将 Lock 定为 final 最终变量。
– 使用场景: [1] 需要灵活上锁 [2] 不想在上锁时阻塞,可以定义非阻塞的操作逻辑 [3] 需要设置为公平锁时

1、可重入锁的使用

注意:要是单是上锁的话,会比平时执行的效率慢一点
注意:加上了unlock就会回到平时执行效率

(A) 上锁次数
[1] lock.lock() 可以上锁多次 [可重入]
[2] 你想完全解锁, 必须解够上锁的次数。
解锁次数 == 上锁次数

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo {

    public static void main(String[] args) {
	ReentrantLockDemo reenDemo = new ReentrantLockDemo();
	Runnable take = new Runnable() {

	    @Override
	    public void run() {
		while (reenDemo.num > 0) {
		    reenDemo.produceing();
		    // 延迟
		    try {
			Thread.sleep(50);
		    } catch (InterruptedException e) {
			e.printStackTrace();
		    }
		}
	    }
	};
	Thread t1 = new Thread(take, "翠花");
	Thread t2 = new Thread(take, "如花");
	Thread t3 = new Thread(take, "小平");
	// 开始生产
	t1.start();
	t2.start();
	t3.start();

    }

    int num = 100;

    // 可重入锁 【确保lock对象是唯一】
    Lock lock = new ReentrantLock();

    // 提供一个方法生产方法 【可重入锁】
    public void produceing() {
	// 上锁
	lock.lock();// 注意:要是单是上锁的话,会比平时执行的效率慢一点
	if (num >= 1) {
	    //
	    String name = Thread.currentThread().getName();
	    //
	    System.out.printf("%s生产了No.xs%d的手机!\n", name, num);
	    //
	    num--;
	}
	lock.unlock();// 注意:加上了unlock就会回到平时执行效率
    }
}
// [同步代码块]
//    public void produceing() {
// 	synchronized ("produceing") {
// 	    if (num >= 1) {
// 		//
// 		String name = Thread.currentThread().getName();
// 		//
// 		System.out.printf("%s生产了No.xs%d的手机!\n", name, num);
// 		//
// 		num--;
// 	    }
// 	}
//     }

2、可重入锁的注意事项1

    (B) 上锁与解锁
    [1] 你可以在任意位置上锁, 也可以任意位置解锁。
    [2] 但是 上锁与解锁的线程必须保证是同一个线程, 否则, 会发生线程处理状态异常。
    
  open1:
// 提供一个方法生产方法 【可重入锁】
  open2 :
// 加上try上后出现多次的异常
// 认为的异常进行操作
/**
 * @author Lantzrung
 * @create 2022-08-06
 * @Description
 */
/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo2 {

    public static void main(String[] args) {
        ReentrantLockDemo2 reenDemo = new ReentrantLockDemo2();
        Runnable take = new Runnable() {

            @Override
            public void run() {
                while (reenDemo.num > 0) {
                    reenDemo.produceing();
                    // 延迟
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        Thread t1 = new Thread(take, "翠花");
        Thread t2 = new Thread(take, "如花");
        Thread t3 = new Thread(take, "小平");
        // 开始生产
        t1.start();
        t2.start();
        t3.start();
    }

    // 限定每天100台的操作
    int num = 100;

    // 可重入锁 【确保lock对象是唯一】【注意:出现异常不会释放锁,所以要在finall去释放可重入锁】
    Lock lock = new ReentrantLock();

    // open2:
    // 加上try上后出现多次的异常
    // 认为的异常进行操作
//    public void produceing() {
//	// 上锁
//	lock.lock();
//	try {
//	    if (num >= 1) {
//		//
//		String name = Thread.currentThread().getName();
//		//
//		System.out.printf("%s生产了No.xs%d的手机!", name, num);
//		//
//		num--;
//		//
//		int item = 3 / 0;
		小平生产了No.xs12的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	java.lang.ArithmeticException: / by zero
	如花生产了No.xs11的手机!	at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	翠花生产了No.xs10的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	小平生产了No.xs9的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	如花生产了No.xs8的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	小平生产了No.xs7的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	翠花生产了No.xs6的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	如花生产了No.xs5的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	翠花生产了No.xs4的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	小平生产了No.xs3的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	如花生产了No.xs2的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
	翠花生产了No.xs1的手机!java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
//
//	    }
//	} catch (Exception e) {
//	    e.printStackTrace();
//	} finally {
//	    // 解锁
//	    lock.unlock();
//	}
//    }

//    open1:
//    // 提供一个方法生产方法 【可重入锁】
//    public void produceing() {
//	// 上锁
//	lock.lock();
//	if (num >= 1) {
//	    //
//	    String name = Thread.currentThread().getName();
//	    //
//	    System.out.printf("%s生产了No.xs%d的手机!\n", name, num);
//	    //
//	    num--;
//	    // open1;人为的异常操作
//	    // 此操作出现异常后不会释放锁进行后续的操作。
	    int item = 3 / 0;
//	    // 翠花生产了No.xs100的手机!
	    Exception in thread "翠花" java.lang.ArithmeticException: / by zero
		at com.day0806.ReentrantLockDemo2.produceing(ReentrantLockDemo2.java:58)
		at com.day0806.ReentrantLockDemo2$1.run(ReentrantLockDemo2.java:20)
		at java.lang.Thread.run(Thread.java:745)
//	}
//	// 解锁
//	lock.unlock();
//   }
}

解一把锁不会输出调用test2的方法,解除两把锁才是正常输出

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0805sil02;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestBlock02 {
    public static void main(String[] args) {
 	// 创建对象
 	TestBlock02 b = new TestBlock02();
 	// 1、创建线程
 	Thread t1 = new Thread(new Runnable() {
 	    @Override
 	    public void run() {
 		b.test1();
 	    }
 	});

 	t1.start();
 	// 先确保启动线程一 再启动线程二
 	try {
 	    Thread.sleep(100);
 	} catch (InterruptedException e) {
 	    throw new RuntimeException(e);
 	}
 	Thread t2 = new Thread(new Runnable() {
 	    @Override
 	    public void run() {
 		b.test2();
 	    }
 	});
 	t2.start();
     }
    
    // 创建上锁对象
    Lock lock = new ReentrantLock();

    // 第一个方法
    public void test1() {
        // 第一次上锁
        lock.lock();
        System.out.println("进入test1方法...");
        // 第二次上锁
        lock.lock();
        // 延迟
        for (int i = 0; i <= 5; i++) {
            try {
                // 输出
                System.out.println("test1...");
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        // 解第一把锁
        lock.unlock();
        // 解第二把锁
        lock.unlock();
        open1
        // 正常的输出操作是输出这个的
//        进入test1方法...
//        test1...
//        test1...
//        test1...
//        test1...
//        test1...
//        test1...
//        进入test2

        open2
        // 要是不解除就不会执行test2了
//        进入test1方法...
//        test1...
//        test1...
//        test1...
//        test1...
//        test1...
//        test1...

    }

    public void test2() {
        // 上锁
        lock.lock();
        System.out.println("进入test2");
        //  解锁
        lock.unlock();
    }
}



3、可重入锁的注意事项2

尝试上锁tryLock(),如果有获取到锁则上锁并且操作,没有获取到锁,也不会阻塞线程,也可以设置超时时间

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0805sil02;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestBlock3 {
    public static void main(String[] args) {
	// 创建对象
	TestBlock3 b = new TestBlock3();
	// 1、创建线程
	Thread t1 = new Thread(new Runnable() {
	    @Override
	    public void run() {
		b.test1();
	    }
	});

	t1.start();
	// 先确保启动线程一 再启动线程二
	try {
	    Thread.sleep(100);
	} catch (InterruptedException e) {
	    throw new RuntimeException(e);
	}
	Thread t2 = new Thread(new Runnable() {
	    @Override
	    public void run() {
		b.test2();
	    }
	});
	t2.start();
    }

    // 创建上锁对象
    Lock lock = new ReentrantLock();

    // 第一个方法
    public void test1() {
	// 第一次上锁
	lock.lock();
	System.out.println("进入test1方法...");
	// 第二次上锁
	lock.lock();
	// 延迟
	for (int i = 0; i <= 5; i++) {
	    try {
		// 输出
		System.out.println("test1...");
		Thread.sleep(200);
	    } catch (InterruptedException e) {
		throw new RuntimeException(e);
	    }
	}
	// 解第一把锁
	lock.unlock();
	// 解第二把锁
	lock.unlock();
    }

    public void test2() {
	// 尝试获取锁 【非阻塞地获取锁】
//	open1
//	进入test1方法...
//	test1...
//	进入test2
//	Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
//		at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
//		at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
//		at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
//		at com.day0805sil02.TestBlock3.test2(TestBlock3.java:70)
//		at com.day0805sil02.TestBlock3$2.run(TestBlock3.java:33)
//		at java.lang.Thread.run(Thread.java:745)
//	test1...
//	test1...
//	test1...
//	test1...
//	test1...
//	lock.tryLock();
//	System.out.println("进入test2");
//	// 解锁
//	lock.unlock();

	// open2
//	进入test1方法...
//	test1...
//	进入test2,先做其他事情
//	test1...
//	test1...
//	test1...
//	test1...
//	test1...
//	boolean isLocked = lock.tryLock();
//	if (isLocked) {
//	    System.out.println("进入test2");
//	    // 解锁
//	    lock.unlock();
//	} else {
//	    System.out.println("进入test2,先做其他事情");
//	}

	// 添加超时时间
	boolean isLocked;
	try {
	    isLocked = lock.tryLock(1, TimeUnit.SECONDS);
	    if (isLocked) {
		System.out.println("进入test2");
		// 解锁
		lock.unlock();
	    } else {
		System.out.println("进入test2,先做其他事情");
	    }
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }
}

4、公平锁与非公平锁的区别

6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)_第1张图片

可以设置公平锁和非公平锁【默认是非公平】非公平锁:
抢夺式,谁抢到就谁执行【可以先直接抢,后排队】
公平锁:每个线程的获取资源的几率是一样的【直接排队】

/**
 * @author Lantzrung
 * @date 2022年8月6日
 * @Description
 */
package com.day0806;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 手机生产类
public class PhoneProducer {

    public static void main(String[] args) {
	// 提供生产类的对象
	PhoneProducer producer = new PhoneProducer();
	// run任务类
	Runnable task = new Runnable() {

	    @Override
	    public void run() {
		// 循环生产手机
		while (producer.num > 0) {
		    producer.produceint();
		    // 延迟
		    try {
			Thread.sleep(50);
		    } catch (InterruptedException e) {
			e.printStackTrace();
		    }
		}
	    }
	};
	// 创建多个工人【线程】
	Thread t1 = new Thread(task, "翠花");
	Thread t2 = new Thread(task, "如花");
	Thread t3 = new Thread(task, "小平");
	// 开始生产
	t1.start();
	t2.start();
	t3.start();
    }

    int num = 15;

    final Lock lock = new ReentrantLock(true);

    // 公平锁的源码 默认的就是非公平的
//    public ReentrantLock() {
//        sync = new NonfairSync();
//    }
    
    // 然后下面还有一个源码 true设置为公平锁 false设置不公平锁
//    public ReentrantLock(boolean fair) {
//        sync = fair ? new FairSync() : new NonfairSync();
//    }
    

    // 可重入锁
    public void produceint() {
	// 上锁
	lock.lock();
	if (num >= 1) {
	    //
	    String name = Thread.currentThread().getName();
	    //
	    System.out.printf("%s生产了No.xs%d的手机!\n", name, num);
	    //
	    num--;
	}
	lock.unlock();
    }
}

3※、什么是死锁?

※–什么是死锁 ?(笔试题)

两个或多个线程, 相互之间互持对方想获取的资源, 在没释放自身资源时之前, 又去试图获取其它线程持有的资源,而造成多个线程同时阻塞,
无法解除。

比如: A 线程持有 “1” 这个资源, 在没有释放 “1” 时, 又试图获取 “2”。 B 线程持有 “2” 这个资源, 在没有释放
“2” 时, 又试图获取 “1”。

这样就形成死锁。
为了尽量避免死锁的发生, 在持有一个资源的同时, 少点去获取其它资源。
6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)_第2张图片
6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)_第3张图片

/**
 * @author Lantzrung
 * @date 2022年8月7日
 * @Description
 */
package com.day0806;

public class PersonTask implements Runnable {

    public static void main(String[] args) {
	PersonTask task = new PersonTask();
	// 创建线程对象
	Thread A = new Thread(task, "Aperson");
	Thread B = new Thread(task, "Bperson");
	// 启动线程
	A.start();
	B.start();
//	[Aperson]尝试进入[A]密室.........
//	[Aperson]尝试进入[A]密室!!!
//	[Aperson]尝试进入[B]密室.........
//	[Aperson]尝试进入[B]密室!!!
//	[Bperson]尝试进入[B]密室.........
//	[Bperson]尝试进入[B]密室!!!
//	[Bperson]尝试进入[A]密室.........
//	[Bperson]尝试进入[A]密室!!!
    }

    @Override
    public void run() {
	// 1、判断是Aperson、Bperson
	String name = Thread.currentThread().getName();
	// 2、判断
	if (name.equals("Aperson")) { // Aperson
	    // A走的路径
	    runWay(new String[] { "A", "B" });
	} else { // Bperson
	    // B走的路径
	    runWay(new String[] { "B", "A" });
	}
    }

    // 路线
    public void runWay(String[] keys) { // 入参锁对象 、密室{A,B} {B,A}
	// 1、获取名称
	String name = Thread.currentThread().getName();
	System.out.printf("[%s]尝试进入[%s]密室.........\n", name, keys[0]);
	// 先进入第一个密室
	synchronized (keys[0]) {
	    System.out.printf("[%s]尝试进入[%s]密室!!!\n", name, keys[0]);
	    // 尝试进入第二个密室
	    System.out.printf("[%s]尝试进入[%s]密室.........\n", name, keys[1]);
	    // 进入第二个密室
	    synchronized (keys[1]) {
		// 尝试进入B密室
		System.out.printf("[%s]尝试进入[%s]密室!!!\n", name, keys[1]);
	    }
	}
    }
}

4※、线程间的通信(生产者和消费者模式)

在这里插入图片描述

–API: wait();导致当前线程等待; notify();唤醒在此监视器对象的单个线程 notifyAll();唤醒在此监视器对象的所有线程
–使用wait和notify时需要同步锁修饰,同步修饰的对象锁要一致
–//强调:调用wait和notify方法的对象是当前的对象锁【对象锁和调用方法的对象是一样的】;明确调用wait和notify的对象一致,能够确保通信操作是对应的线程

注意:
在这里插入图片描述
6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)_第4张图片

/**
 * @author Lantzrung
 * @date 2022年8月7日
 * @Description
 */
package com.day0806;

public class PhoneBuffer {
    // 手机经销商
    public static void main(String[] args) {
	// 1、创建生产者和消费者
	PhoneBuffer buffer = new PhoneBuffer();
	// 1、生产者
	ProducerThread pro = new ProducerThread();
	pro.setBuffer(buffer);
	pro.setName("生产者");
	// 2、消费者
	ConsumerThread con = new ConsumerThread();
	con.setBuffer(buffer);
	con.setName("消费者");
	// 3、启动
	pro.start();
	con.start();

	// OPEN1:
	// 不在doNotify和dowait加上同步代码块是会出现报错的
//	Exception in thread "消费者" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
//	at java.lang.Object.wait(Native Method)
//	at java.lang.Object.wait(Object.java:502)
//	at com.day0806.PhoneBuffer.dowait(PhoneBuffer.java:45)
//	at com.day0806.ProducerThread.run(PhoneBuffer.java:79)
//java.lang.NullPointerException
//	at com.day0806.ConsumerThread.run(PhoneBuffer.java:103)

    }

    // 缓存的手机
    int num = 0;
    // 定义一个标志位,判断是否为空
//    boolean isEmpty = false;// 默认为空
    boolean isEmpty = true;// 默认为空

    // OPEN2
    // 唤醒同一监视器下(同步锁)的线程 
    // 通知方法 [同步修饰] 通知在该对象锁监视下的等待的线程根据同步锁唤醒的唤醒 等待线程【this】
    public synchronized void doNotify() {
	this.notify();
    }

    // 等待方法 [同步修饰] [对象锁]
    public synchronized void dowait() {
	try {
	    this.wait();
	} catch (InterruptedException e) {
	    e.printStackTrace();
	}
    }
    // OPEN2
//    // 通知方法
//    public void doNotify() {
//	this.notify();
//    }
//
//    // 等待方法
//    public void dowait() {
//    try {
//    this.wait();
//    } catch (InterruptedException e) {
//    e.printStackTrace();
//    }
//  }
}

// 生产者线程
class ProducerThread extends Thread {
    // 提供缓冲区的引用
    PhoneBuffer buffer;
    // 提供getset方法

    public PhoneBuffer getBuffer() {
	return buffer;
    }

    public void setBuffer(PhoneBuffer buffer) {
	this.buffer = buffer;
    }

    @Override
    public void run() {
	while (true) {
	    // 1、判断缓冲区是否为空
	    if (buffer.isEmpty) {
		// 空--则生产
		buffer.num = (int) (Math.random() * 100 + 1);
		System.out.println(this.getName() + "生产了:" + buffer.num + "台手机");
		// 生产完,标志位【不为空】
		buffer.isEmpty = false;
		// 通知消费者进行消费
		buffer.doNotify();
	    } else {
		// 不空,则等待
		buffer.dowait();
	    }
	}
    }
}

// 消费者线程
class ConsumerThread extends Thread {
    // 提供缓冲区的引用
    PhoneBuffer buffer;
    // 提供getset方法

    public PhoneBuffer getBuffer() {
	return buffer;
    }

    public void setBuffer(PhoneBuffer buffer) {
	this.buffer = buffer;
    }

    @Override
    public void run() {
	while (true) {
	    // 1、判断缓冲区是否为空
	    if (buffer.isEmpty) {
		// --空 则等待 【监视器--同步锁】
		buffer.dowait();
	    } else {
		// 不空,则消费
		System.out.println(this.getName() + "消费了:" + buffer.num + "台手机");
		// 生产完,标志位【不为空】
		buffer.isEmpty = true;
		// 通知消费者进行消费
		buffer.doNotify();
	    }
	}
    }
}

要确保对象锁是一致的,否则不能唤醒

6※、线程同步、同步锁、同步代码块的使用、同步锁释放的时机、ReentrantLock可重入锁、公平锁与非公平锁的区别、什么是死锁、线程间的通信(生产者和消费者模式)_第5张图片

你可能感兴趣的:(05,IO流和多线程,java,jvm,算法,数据结构,开发语言)