java 线程同步和通信

什么是线程同步

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的!

为什么要实现线程同步

模拟火车站售票程序,开启三个窗口售票。

class Window implements Runnable{
    //假设一共有100张票
    private static int tictet = 100;

    @Override
    public void run() {
        while (true){
            if (tictet>0){
                try {
                    // 提高JVM切换线程的概率
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //输出当前线程名和票号
                System.out.println(Thread.currentThread().getName() + "票号为:" + tictet--);
            }
            else {
                break;
            }
        }
    }
}
 @Test
    public void ThreadSynchronizationTest() throws InterruptedException {
        Window window = new Window();
        Thread thread1 = new Thread(window);
        Thread thread2 = new Thread(window);
        Thread thread3 = new Thread(window);

        thread1.setName("窗口一");
        thread2.setName("窗口二");
        thread3.setName("窗口三");
        thread1.start();
        thread2.start();
        thread3.start();

        Thread.sleep(1000);
    }
}

输出

窗口一票号为:99
窗口三票号为:99
窗口二票号为:100
窗口三票号为:98
窗口一票号为:98
窗口二票号为:98
窗口一票号为:97
窗口二票号为:96
窗口三票号为:95
...

从输出中可以看出,代码在运行过程中同时访问了共享的ticket变量,导致出现了重复的票号。

总结来说,当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

如何实现线程同步

同步锁机制:

在《Thinking in Java》 中, 是这么说的:对于并发工作, 你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争) 。 防止这种冲突的方法就是当资源被一个任务使用时, 在其上加锁。 第一个访问某项资源的任务必须锁定这项资源, 使其他任务在其被解锁之前, 就无法访问它了, 而在其被解锁之时, 另一个任务就可以锁定并使用它了。

synchronized的锁是什么?

  • 任意对象都可以作为同步锁。 所有对象都自动含有单一的锁(监视器) 。
  • 同步方法的锁:静态方法(类名.class) 、 非静态方法(this)
  • 同步代码块:自己指定, 很多时候也是指定为this或类名.class

注意:

  • 必须确保使用同一个资源的多个线程共用一把锁, 这个非常重要, 否则就
  • 无法保证共享资源的安全
  • 一个线程类中的所有静态方法共用同一把锁(类名.class) , 所有非静态方法共用同一把锁(this) , 同步代码块(指定需谨慎)

释放锁的操作

  • 当前线程的同步方法、同步代码块执行结束。
  • 当前线程在同步代码块、同步方法中遇到break、 return终止了该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception, 导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

不会释放锁的操作

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
  • 应尽量避免使用suspend()和resume()来控制线程

实现线程同步

实现一:使用synchronized
  1. 同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
  1. synchronized还可以放在方法声明中,表示整个方法为同步方法。
    例如:
public synchronized void show (String name){.
}
使用线程同步改进售票问题
class Window implements Runnable{
    private static int tictet = 100;

    @Override
    public void run() {
        while (true){
            //这里的this是同一个Window对象
            synchronized (this) {
                if (tictet > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + tictet--);
                } else {
                    break;
                }
            }
        }
    }
}
实现二:使用Lock

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的
工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象
加锁,线程开始访问共享资源之前应先获得Lock对象。

使用线程同步改进售票问题
class Window implements Runnable{
    private static int tictet = 100;
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //加锁
                lock.lock();
                if (tictet > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "票号为:" + tictet--);
                } else {
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

注意:如果同步代码有异常,要将unlock()写入finally语句块

synchronized 与 Lock 的对比
  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁, synchronized有代码块锁和方法锁
  3. 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
    优先使用顺序:
    Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)

线程的通信

如果有这样一个问题

使用两个线程打印 1-100。线程1, 线程2 交替打印

那么之前的只是显然无法满足需要,这时候就需要用到线程的通信技术

线程通信中常用的函数
  • wait() 与 notify() 和 notifyAll()
    • wait():令当前线程挂起并放弃CPU、 同步资源并等待, 使别的线程可访问并修改共享源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
      notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
      notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
  • 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
  • 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
wait() 方法
  • 在当前线程中调用方法: 对象名.wait()
  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify(或notifyAll) 为止。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待
  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
notify()/notifyAll()
  • 在当前线程中调用方法: 对象名.notify()
  • 功能:唤醒等待该对象监控权的一个/所有线程。
  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
例题实现
class Numbers implements Runnable{
    private int num=1;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                obj.notify();
                if (num <= 100) {
                    System.out.println(Thread.currentThread().getName() + ":" + num++);

                }
                else {
                    break;
                }
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
@Test
public void CommunicationTest1() throws InterruptedException {
    Numbers numbers = new Numbers();
    Thread thread1 = new Thread(numbers);
    Thread thread2 = new Thread(numbers);

    thread1.start();
    thread2.start();

    thread1.join();
    thread2.join();
}

输出

Thread-1:1
Thread-0:2
Thread-1:3
Thread-0:4
...
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值