概述
目录
- wait与sleep
- synchronized与lock
- synchronized
- lock
- synchronized 与 lock区别
- 生产消费模型
- synchronized实现
- 此处存在的问题
- lock锁实现
- Condition
- 死锁
- 为什么需要锁?
- 锁的工作模式:
- 解决死锁的办法
wait与sleep
-
来自不同的类
首先,wait和sleep都不是一个类下的方法:
wait来自:Object
sleep来自:Thread
因为java中所有的类都是继承自object的,所以所有类都可以调用wait方法,这是一个final的方法,同时不是一个静态方法,所以调用该方法需要先实例化一个Object对象才可以 -
释放锁的不同
wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放! 也就是说,如果有两个线程,其中一个锁住了某个对象时,中间sleep了,这时候另一个线程时拿不到该对象的锁的,得等第一个线程sleep完并释放锁才可。
wait会释放这个锁,并把这个wait的线程加入到这个锁的等待队列中去 -
使用的范围不同
wait必须在同步代码块中使用 -
使用sleep不需要被唤醒,但是wait是需要notify()或者notifyAll()去唤醒的,除了wait(1000)这种形式.
举例说明问题:
synchronized与lock
synchronized
synchronized如果加在了非静态方法上,表示的是synchronized(调用方法的类的对象) {},如果加在了静态方法上,表示的是synchronized(类.class) {}
public class DifferSynchronizedAndLock {
//synchronized
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 1; i < 40 ; i++) { ticket.sale();
} },"A").start();
new Thread(()->{
for (int i = 1; i < 40 ; i++) {
ticket.sale(); }
},"B").start();
new Thread(()->{
for (int i = 1; i < 40 ; i++) {
ticket.sale(); }
},"C").start();
}
static class Ticket {
// 属性、方法
private int number = 30;
// 卖票的方式
// synchronized 本质: 队列,锁
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
}}
}
此处如果不对方法加synchronized修饰(不加锁):
lock
Lock是一个接口,实现类有一下几个:
先看可重入锁(ReentrantLock):
这里的可重入锁构造时候除非传入fair公平,否则默认为不公平锁。
公平锁:十分公平:可以先来后到
非公平锁:十分不公平:可以插队 (默认)深入剖析ReentrantLock公平锁与非公平锁源码实现
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DifferSynchronizedAndLock {
//synchronized
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 1; i < 40 ; i++) { ticket.sale();
} },"A").start();
new Thread(()->{
for (int i = 1; i < 40 ; i++) {
ticket.sale(); }
},"B").start();
new Thread(()->{
for (int i = 1; i < 40 ; i++) {
ticket.sale(); }
},"C").start();
}
static class Ticket {
// 属性、方法
private int number = 30;
Lock lock = new ReentrantLock();
// 卖票的方式
// synchronized 本质: 队列,锁
public void sale(){
lock.lock();
try {
if (number>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}}
}
synchronized 与 lock区别
- synchronized 内置的Java关键字, Lock 是一个Java类
- synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
Thread提供了holdLock()方法检测当前线程是否持有锁,注意,是当前线程
3.synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
3. synchronized 线程 1(获得锁,阻塞)、线程2(只能等待);Lock锁就不一定会等待下去了,这里有个lock.tryLock()方法,尝试获取锁,可以做个判断让其尝试不到锁时不等待!!
4. synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以在构造ReentrantLock()中自行设置boolean fair,true为公平,默认flase非公平
5. synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
生产消费模型
说白了就是多线程之间的通信,场景如下:有两个线程分别负责同一个资源类里变量的增加和减少,即生产与消费,对于增加和减少的逻辑:当资源中为0的时候,减少的方法就应该等待,不能再减少了;那么当资源不等于0 或者大于某个值时,增加方法就应该不再继续增加了。
具体方法总结:判断等待–>业务–>通知
synchronized实现
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
// 判断等待,业务,通知
class Data{ // 数字 资源类
private int number = 0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){ //0
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){ // 1
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
this.notifyAll();
}
}
此处存在的问题
案例这样,如果只是两个线程通信,一个增加一个减少,必然不会出错,如果再增加几个呢?那notify之后,哪个线程来抢占呢?
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
通过结果可以看出,有个值变成了2,也就是两个加法被唤醒了,且使得值均加了1。
由于这里的资源类中增加和减少的方法使用的是if判断,所以也就只有一次判断,而wait之后,重新被唤醒要执行的是wait之后的语句,所以必须让他反复的判断一下值,这样才能保证线程安全!!将if改成while即可!!
if在官方文档中存在一个虚假唤醒的问题
lock锁实现
Lock提供了condition.await(); 来替换等待,condition.signalAll(); 来替换唤醒全部
// 判断等待,业务,通知
class Data2{ // 数字 资源类
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//condition.await(); // 等待
//condition.signalAll(); // 唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
// 业务代码
while (number!=0){ //0
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//-1
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){ // 1
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition
前面实现的都是随机的状态,也就是没有人为的去控制线程执行顺序,Condition可以精准的通知和唤醒!!
这里提供一个场景:A 执行完调用B,B执行完调用C,C执行完调用A!!
这里如果num为1时,让A执行,num=2–>B, num=3–>C
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data.printC();
}
},"C").start();
}
}
class Data3{ // 资源类 Lock
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C
public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
// 唤醒,唤醒指定的人,B
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
// 业务,判断-> 执行-> 通知
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBBB");
// 唤醒,唤醒指定的人,c
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出结果即:A执行完通知B执行,B执行完通知C…
死锁
为什么需要锁?
多线程操作共享资源,存在同时对资源的读写 ,会导致资源的原子性遭到不一致处理
需要锁操作来控制多个线程对象资源操作的原子性(安全性)
锁的工作模式:
对于一个共享资源,我们有 lock 与 unlock 两个原子操作
在一个线程对于一个资源进行操作的时候,实行lock 操作 ,这个时候其他线程就不能再操作这个资源对象了进入阻塞状态
/**
有两个资源对象
一个是碗 一个是米饭
有两个线程 T1 T2
T1 拿了一个碗 想去盛饭
T2 拿了一份米饭 想拿这个碗来装
T1 可以拿到饭吗? 不能
T2 可以拿到碗吗? 不能
**/
package com.thread0622;
public class ThreadTest {
static String str1 = "米饭";
static String str2 = "碗";
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
/**
* 线程1 持有米饭的监视器锁
*/
synchronized (str1) {
System.out.println("T1 持有 " + str1);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (str2) {
System.out.println("T1 想持有 " + str2);
}
System.out.println("T1 End....");
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 线程2 持有碗 的监视器锁
synchronized (str2) {
System.out.println("T2 持有 " + str2);
// 线程2 持有碗 的监视器锁 的情况下想获取 米饭的锁
synchronized (str1) {
System.out.println("T2 想持有 " + str1);
}
System.out.println("T2 End....");
}
}
});
t2.start();
}
}
解决死锁的办法
1、在申请一个对象资源锁的时候 加入一个时间判断,释放手头所有的资源锁
2、写代码时候,尽量避免这种情况的写法 ,嵌套资源锁
package com.thread0622;
public class ThreadTest {
static String str1 = "米饭";
static String str2 = "碗";
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
/**
* 线程1 持有米饭的监视器锁
*/
synchronized (str1) {
System.out.println("T1 持有 " + str1);
}// 使用完一个资源之后就立即释放锁
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (str2) {
System.out.println("T1 想持有 " + str2);
}
System.out.println("T1 End....");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
// 线程2 持有碗 的监视器锁
synchronized (str2) {
System.out.println("T2 持有 " + str2);
// 线程2 持有碗 的监视器锁 的情况下想获取 米饭的锁
synchronized (str1) {
System.out.println("T2 想持有 " + str1);
}
System.out.println("T2 End....");
}
}
});
t2.start();
}
}
3、finally 关键字 能够保证 finally 块中的代码 执行
static Lock lock = new ReentrantLock();
try{
lock.lock();
}finally {
lock.unlock();
}
最后
以上就是舒服糖豆为你收集整理的多线程中的wait与sleep,synchronize与lock有啥子区别?生产消费模型是啥?死锁怎么解决?wait与sleepsynchronized与lock生产消费模型死锁解决死锁的办法的全部内容,希望文章能够帮你解决多线程中的wait与sleep,synchronize与lock有啥子区别?生产消费模型是啥?死锁怎么解决?wait与sleepsynchronized与lock生产消费模型死锁解决死锁的办法所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复