Java多线程 -- 线程同步

Java多线程 – 线程同步

一、线程安全问题

  在多线程编程中,极容易出现一个问题,即线程安全。举个栗子~,如果此时火车站有十张余票,四个售票口,此时四个售票口相当于四个线程,他们一同运转,卖票,那么就会出现一个很经典的安全问题。

示例代码:

class demo{
    public static void main(String[] args) throws InterruptedException {
        Train train = new Train();
        Train train1 = new Train();
        Train train2 = new Train();
        Train train3 = new Train();
        train.start();
        train1.start();
        train2.start();
        train3.start();

    }
}

class Train extends Thread{
    private static int count = 10;
    @Override
    public void run(){
        while (true){
            try{
                Thread.sleep(500);
                boolean flag = sale();
                if(!flag)
                    break;
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public boolean sale(){
        if(count > 0){
            --count;
            System.out.println( Thread.currentThread().getName() + "售出一张,剩余:"+count);
            return true;
        }else{
            return false;
        }
    }
}

大家可以运行一下以上的程序,会发现有的时候售出的票不只是10张,这正是线程调度的不确定性,

二、同步代码块

  之所以出现以上的情况,是因为run()方法的方法体不具有同步安全性,当多个线程同步修改同一变量的时候容易出现以上情况,为了解决这个尴尬的问题,Java的多线程支持引入了同步监视器来解决这个问题,使用同步监视器的通用方法就是同步代码块。

synchronized(obj){
	//此处代码就是同步代码块
}

  注意:任何时刻都只能拥有一个线程获得对同步监视器的锁定,当同步代码块执行完成之后,该线程就会释放对同步监视器的锁定。

class demo{
    public static void main(String[] args) throws InterruptedException {
        Train train = new Train();
        Train train1 = new Train();
        Train train2 = new Train();
        Train train3 = new Train();
        train.start();
        train1.start();
        train2.start();
        train3.start();

    }
}

class Train extends Thread{
    private static int count = 10;
    @Override
    public void run(){
		//获取类锁
        synchronized(Train.class){
            while (true){
                try{
                    Thread.sleep(500);
                    boolean flag = sale();
                    if(!flag)
                        break;
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public boolean sale(){
        if(count > 0){
            --count;
            System.out.println( Thread.currentThread().getName() + "售出一张,剩余:"+count);
            return true;
        }else{
            return false;
        }
    }
}

三、同步方法

  与同步代码块相对应,Java的多线程安全支持,还提供了同步方法,同步方法就是使用synchronized修饰的实例方法(非static方法),则该方法称为同步方法,对于synchronized修饰的实例方法,无需显式指定同步监视器,同步方法的同步监视器是this,也就是调用该方法的对象。
  通过同步方法可以非常方便地实现线程安全的类。
  线程安全的类有以下的特征:
   1. 该类的对象可以被多个线程安全的访问。
   2. 每个线程调用该对象的任意方法之后都将得到正确结果。
   3. 每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。

注意:synchronized可以修饰方法和代码块,但不能修饰构造器和成员变量等

四、释放同步监视器的锁定

  任何线程进入同步代码块或同步方法前,都必须先获得对同步监视器的锁定,那么何时会释放对同步监视器的锁定呢?程序无法显示释放对同步监视器的锁定,而是在以下这些情况释放对同步监视器的锁定:
  1. 当前线程的同步方法、同步代码执行结束,当前线程即释放同步监视器。
  2. 当前线程在同步代码块、同步方法中遇到了breakreturn终止了该代码块、该方法的继续执行,当前线程将会释放同步监视器。
  3. 当前线程在同步代码块、同步方法之中出现了未处理的errorException,导致了该代码块,该方法异常结束的时候,当前线程将会释放同步监视器。
  4. 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并且释放同步监视器。

  以下情况,线程不会释放同步监视器:
  1. 线程执行同步代码块或者同步方法的时候,程序调用Thread.sleep()Thread.yield()方法来暂停当前线程的执行,但是当前线程并不会释放同步监视器。
  2. 线程执行同步代码块的时候,其他的线程调用该线程的suspend()方法将其挂起,该线程不会释放同步监视器,当然,需要注意的是:程序中尽量少使用suspend()resume()方法来控制线程。

五、同步锁

  从Java 5开始,Java提供了一种功能更强大的线程同步机制 – 通过显示定义同步锁对象来实现同步。
  Lock提供了比synchronized方法和代码块更广泛的锁定操作,Lock允许实现更加灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。
  Lock是控制多个线程对共享资源进行访问的工具。通常,所提供了对共享资源的独占访问,每次都只能有一个线程对Lock对象枷锁,线程开始访问共享资源之前,应该先获得Lock对象。
  某些所可能允许对共享资源并发操作,例如说:ReadWriteLock(读写锁),LockReadWriteLock是Java 5提供的两个根接口,且为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWreiteLock提供了ReentrantReadWriteLock实现类。
  Java 8新增了新型的StampedLock类,在大多数场景中他可以替代传统的ReentrantreadWriteLockReentrantreadWriteLock为读写操作提供了三种锁模式:WritingReadingOptimisiticReading
  在实现线程安全的控制之中,较为常用的是ReentrantLock(可重入锁)。使用该Lock对象可以显式地添加所和释放锁。

import java.util.concurrent.locks.ReentrantLock;

class demo{
    //定义锁对象
    private final ReentrantLock lock = new ReentrantLock();
    //定义需要保证线程安全的方法
    public void m(){
        //加锁
        lock.lock();
        try{
            //需要保证线程安全的代码
        }
        //使用finally块来释放锁
        finally {
            //解锁
            lock.unlock();
        }
    }
}

  使用ReentrantLock对象进行同步,加锁和释放锁均出现在不同的额作用范围之内,通常建议使用finally块来确保在必要的时候释放锁,通常使用ReentrantLock对象。

六、避免死锁

  当两个线程相互等待对方释放同步监视器就会发生死锁,其中Java虚拟机中没有监测,也没有采取措施处理死锁情况,所以多线程编程中应当精良避免死锁出现,一旦出现死锁,整个程序既不会发生任何异常,也不会有任何提示,只是所有线程处于阻塞状态,无法继续。

  死锁不应该在程序中出现,编写时应该尽量避免死锁。
  1. 避免多次锁定,尽量避免同一个线程对多个同步监视器进行锁定
  2. 具有相同的加锁顺序,如果多个线程对多个同步监视器进行锁定,则应该报这个它们以相同的顺序请求加锁。
  3. 采用定时所,程序调用Lock对象的truLock()方法加锁时,可以指定timeunit参数,当超过指定时间就会自动释放对Lock的锁定。
  4. 死锁检测,这是依靠算法进行实现的死锁预防措施,主要针对那些不可能实现按序加锁,也不可能使用定时锁的场景。

//会产生死锁的代码:

class A{
    public synchronized void foo(B b){
        System.out.println("线程名:" + Thread.currentThread().getName() + "进入A实例的foo方法");
        try{
            Thread.sleep(200);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("线程名:" + Thread.currentThread().getName() + "企图调用B实例的Last方法");
        b.last();
    }

    public synchronized void last(){
        System.out.println("进入A类的last方法内部");
    }

}

class B{
    public synchronized void foo(A a){
        System.out.println("线程名:" + Thread.currentThread().getName() + "进入A实例的foo方法");
        try{
            Thread.sleep(200);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println("线程名:" + Thread.currentThread().getName() + "企图调用B实例的Last方法");
        a.last();
    }

    public synchronized void last(){
        System.out.println("进入B类的last方法内部");
    }
}

public class demo implements Runnable{
    A a = new A();
    B b = new B();
    public void init(){
        Thread.currentThread().setName("主线程");
        a.foo(b);
        System.out.println("进入主线程之后");
    }

    @Override
    public void run(){
        Thread.currentThread().setName("副线程");
        b.foo(a);
        System.out.println("进入副线程");
    }

    public static void main(String[] args) {
        demo d = new demo();
        new Thread(d).start();
        d.init();
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值