【Java并发编程学习 4】多线程之间实现通讯及Lock锁

1 什么是多线程之间的通讯

在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步。通信是指线程之间以如何来交换信息。一般线程之间的通信机制有两种:共享内存和消息传递。

Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,很可能会遇到各种奇怪的内存可见性问题。

2 通讯业务需求及实现

需求是:第一个线程写入用户,另一个线程取读取用户,实现读一个,写一个操作。

2.1 代码实现基本实现

  1. 定义User类
package com.lijie;

public class User {
    String name;
    String sex;
}
  1. 输入线程资源
package com.lijie;

//输入类
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        while (true) {
            if (count % 2 == 0) {
                user.name = "张三";
                user.sex = "男";
            } else {
                user.name = "小紅";
                user.sex = "女";
            }
            count += 1;
        }
    }
}
  1. 输出线程资源
package com.lijie;

//输出类
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        while (true) {
            System.out.println(user.name + "====" + user.sex);
        }
    }
}
  1. 创建测试
package com.lijie;

public class Test {
    public static void main(String[] args) {
        User user = new User();
        InputThread inputThread = new InputThread(user);
        OutThread outThread = new OutThread(user);
        inputThread.start();
        outThread.start();
    }
}

运行后你会发现,数据非常乱
在这里插入图片描述

2.2 使用synchronized解决线程安全问题

加上synchronized同步代码块
修改输入和输出线程资源俩个类:

package com.lijie;

//输入类
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        synchronized (this) {
            while (true) {
                if (count % 2 == 0) {
                    user.name = "张三";
                    user.sex = "男";
                } else {
                    user.name = "小紅";
                    user.sex = "女";
                }
                count += 1;
            }
        }
    }
}
package com.lijie;

//输出类
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        synchronized (this) {
            while (true) {
                System.out.println(user.name + "====" + user.sex);
            }
        }
    }
}

执行结果:发现数据没有混乱了
在这里插入图片描述

2.3 改变需求

我觉得这样输出也很乱,我想要一个张三,一个小红,在线程安全的情况下一个一个输出如何实现:

2.4 wait、notify和方法

  1. 因为涉及到对象锁,他们必须都放在synchronized中来使用. wait、notify一定要在synchronized里面进行使用。
  2. wait() :必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行
  3. notify() :唤醒因锁池中的线程,使之运行

2.6 修改代码

  1. 修改User类,添加标识
package com.lijie;

public class User {
    String name;
    String sex;
    //线程通讯标识
    public boolean sign = false;
}
  1. 修改输入类,添加wait和notify方法
package com.lijie;

//输入类
class InputThread extends Thread {
    private User user;

    public InputThread(User user) {
        this.user = user;
    }

    public void run() {
        int count = 0;
        while (true) {
            synchronized (user) {
                if (user.sign == true) {
                    try {
                        // 当前线程变为等待,但是可以释放锁
                        user.wait();
                    } catch (Exception e) {

                    }
                }
                if (count % 2 == 0) {
                    user.name = "张三";
                    user.sex = "男";
                } else {
                    user.name = "小紅";
                    user.sex = "女";
                }
                count += 1;
                user.sign = true;
                // 唤醒当前线程
                user.notify();
            }
        }
    }
}
  1. 修改输出类,添加wait和notify方法
package com.lijie;

//输出类
class OutThread extends Thread {
    private User user;

    public OutThread(User user) {
        this.user = user;
    }

    public void run() {
        while (true) {
            synchronized (user) {
                if (user.sign==false) {
                    try {
                        // 当前线程变为等待,但是可以释放锁
                        user.wait();
                    } catch (Exception e) {

                    }
                }
                System.out.println(user.name + "====" + user.sex);
                user.sign = false;
                // 唤醒当前线程
                user.notify();
            }
        }
    }
}

执行结果:
在这里插入图片描述

2.7 wait与sleep区别

  1. sleep()方法,是属于Thread类中的。
  2. wait()方法,则是属于Object类中的。
  3. sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是没有释放锁
  4. wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

3 Lock锁

在 jdk1.5 之后,并发包中新增了 Lock 接口(以及相关实现类)用来实现锁功能,Lock 接口提供了与 synchronized 关键字类似的同步功能,但需要在使用时手动获取锁和释放锁。

3.1 Lock写法

 	private Lock lock = new ReentrantLock();
    
    //使用完毕释放后其他线程才能获取锁
    public void lockTest(Thread thread) {
        lock.lock();//获取锁
        try {

        } catch (Exception e) {

        }finally {
            lock.unlock(); //释放锁
        }

3.2Lock锁示例代码

package com.lijie;

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

public class LockTest {
    private Lock lock = new ReentrantLock();

    //使用完毕释放后其他线程才能获取锁
    public void lockTest(Thread thread) {
        lock.lock();//获取锁
        try {
            System.out.println("线程" + thread.getName() + "获取锁");
        } catch (Exception e) {
            System.out.println("线程" + thread.getName() + "发生了异常");
        } finally {
            System.out.println("线程" + thread.getName() + "执行完毕释放锁");
            lock.unlock(); //释放锁
        }
    }

    public static void main(String[] args) {
        LockTest lockTest = new LockTest();
        Thread thread1 = new Thread(new Runnable() {

            public void run() {
                lockTest.lockTest(Thread.currentThread());
            }
        }, "线程一");

        Thread thread2 = new Thread(new Runnable() {

            public void run() {
                lockTest.lockTest(Thread.currentThread());
            }
        }, "线程二");
        // 启动2个线程
        thread2.start();
        thread1.start();
    }
}

4 Condition

Condition与Lock的关系就类似于synchronized与Object.wait()/signal()

  1. Conditionaw.ait():必须暂定当前正在执行的线程,并释放资源锁,让其他线程可以有机会运行。和Object.wait()方法很相似。
  2. Conditionaw.singal():唤醒因锁池中的线程,使之运行。和Object.wait()方法很相似。

4.1 Condition和Lock代码示例

package com.lijie;

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

class User {
    public String name;
    public String sex;
    //定义标识变量
    public boolean flag = false;
    //定义Lock锁和ReentrantLock实现类
    Lock lock = new ReentrantLock();
}

class InputThread extends Thread {
    private User user;
    //定义Condition属性,靠他来释放锁和唤醒锁
    Condition newCondition;

    //构造参数赋值
    public InputThread(User user, Condition newCondition) {
        this.user = user;
        this.newCondition = newCondition;
    }

    //使用完毕释放后其他线程才能获取锁
    public void run() {
        int count = 0;
        while (true) {
            try {
                //获取锁
                user.lock.lock();
                if (user.flag) {
                    try {
                        //使当前线程等待,同时释放当前锁
                        newCondition.await();
                    } catch (Exception e) {

                    }
                }
                if (count == 0) {
                    user.name = "张三";
                    user.sex = "男";
                } else {
                    user.name = "小红";
                    user.sex = "女";
                }
                count = (count + 1) % 2;
                user.flag = true;
                //唤醒一个在等待中的线程
                newCondition.signal();
            } catch (Exception e) {

            } finally {
                //释放锁
                user.lock.unlock();
            }
        }
    }
}

class OutThread extends Thread {
    private User user;
    private Condition newCondition;

    public OutThread(User user, Condition newCondition) {
        this.user = user;
        this.newCondition = newCondition;
    }

    public void run() {
        while (true) {
            try {
                user.lock.lock();
                if (!user.flag) {
                    try {
                        newCondition.await();
                    } catch (Exception e) {

                    }
                }
                System.out.println(user.name + "===" + user.sex);
                user.flag = false;
                newCondition.signal();
            } catch (Exception e) {

            } finally {
                user.lock.unlock();
            }
        }

    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        User user = new User();
        Condition newCondition = user.lock.newCondition();
        InputThread inputThread = new InputThread(user, newCondition);
        OutThread outThread = new OutThread(user, newCondition);
        inputThread.start();
        outThread.start();
    }
}

5 synchronized与Lock的区别

  1. 首先synchronized是java内置关键字,在jvm层面,Lock是个java类;
  2. synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;
  3. synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;
  4. 用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,自动放弃的方法tryLock(),具有更完善的错误恢复机制。
  5. Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
发布了68 篇原创文章 · 获赞 56 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_43122090/article/details/104958675