多线程技术应用一

[TOC]

线程的基本概念

并行和并发的区别

并发(concurrency)和并行性(parallel)是两个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个指令在同时执行。

进程和线程

几乎所有的操作系统都支持同时运行多个任务,一个人任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能含有多个顺序执行流,每个顺序执行流就是一个线程。

image

每个exe就是一个进程,进程是操作系统基本运行单位。线程指的是进程中独立运行的子任务,比如扣扣运行时就有很多子线程在运行。如语音线程,文件下载线程等。

多线程的好处:

  • 资源利用率更好
  • 程序设计在某些情况下更简单(使用多线程进行读写文件和使用单线程读写文件,NIO和IO)
  • 程序响应更快(web端响应交互)

使用多线程的代价

线程的分类

  • 普通线程
  • Daemon Thread
    又称为守护线程,JVM的垃圾回收线程就是典型的后台线程。特征:如果所有的前后台线程都死亡,后台线程会自动死亡

线程优先级别

分为1~10,Thread提供三个静态常量。MAX_PRIORITY:10,MIN_PRIORITY:1,NORM_PRIORITY:5.默认为NORM_PRIORITY。

  • 具有继承性,比如在A线程中启动B线程,则B线程的优先级别和A线程相同
  • 线程执行的规则性,线程优先级别越高,代表获取的执行机会越多。而优先级别低的线程则获得较少的执行机会
public class PriorityExtendThread extends Thread{
    @Override
    public void run() {
        System.out.println("当前线程"+getName()+"的优先级别为:"+getPriority());
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"线程开始时优先级别为"+Thread.currentThread().getPriority());
//        Thread.currentThread().setPriority(6);
        System.out.println(Thread.currentThread().getName()+"线程结束时优先级别为"+Thread.currentThread().getPriority());
        PriorityExtendThread thread = new PriorityExtendThread();
        thread.start();
    }
}

上面代码执行结果如下:

image



当把上面的注释代码去掉后,执行结果为:

image

说明:线程的优先级别需要操作系统的支持,并不是每个操作系统,都对能对应java中的10个级别。应该尽量使用Thread类中的三个静态常量来设置优先级别。

线程的创建方式

从Thread的构造方法说起

Thread类中的构造方法如下:

image


所有的构造方法都通过初始化方法进行,该方法主要做了几件事情。

  • 判断线程所属的group(每个线程除了初始线程,都有所属线程组,线程组是种树形结构)
  • 初始化target,这个target就是线程的执行体。源码中的注释如下:

target - the object whose run method is invoked when this thread is started. If null, this classes run method does nothing

  • 根据从属的group设定是否为守护线程,设置线程优先级别
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

从上面可以知道线程里真正的执行体是Runnable中run方法。现有java中一共有三种方式来进行创建线程。

通过继承Thread来创建线程

    public class ExtThreadCreate extends Thread{

    private int i;

    @Override
    public void run() {
        for (;i<=10;i++){
            System.out.println(getName()+" "+i);
        }
    }

    public static void main(String[] args) {
        new ExtThreadCreate().start();
        new ExtThreadCreate().start();
    }
}

通过实现Runnable接口来创建线程

对于使用继承的方式来创建线程时,也可以把创建的线程类当做一个target来使用,因为Thread类本身也实现了Runnable方法。

public class RunnableCreate implements Runnable {
    private Integer i = 0;

    @Override
    public void run() {
        for (; i <= 100; i++) {
            //使用实现Runnable接口的方法,不能直接使用getName的方式进行获取线程名称
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        RunnableCreate target = new RunnableCreate();
        new Thread(target).start();
        new Thread(target).start();
    }

}

使用Callable和Future创建线程

由于目前java不支持把随意的方法当做一个执行体,只能用到Runnable中的run方法作为线程的执行体,而run方法开始设定的时候没有返回值。为了能让线程支持返回值,java5开始,引入Callable<V>接口和Future<V>。

    @FunctionalInterface
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

call()方法的目的是为了增强run()方法,线程真正执行的还是run()方法,只是在run方法中被调用。那么怎么去获取call()方法的返回值呢?
Java 5 引入Future<V>接口,作为call返回值的代表。并且提供了FutureTask实现类,该类的结构图如下:

[图片上传失败...(image-987ba5-1514257344901)]

FutureTask实现了Runnable可以当做线程的target,并且实现了Future<V>接口。
Future提供的接口都是为了控制Callable的任务执行

  1. boolean cancel(boolean mayInterruptIfRunning):试图取消正在执行callable任务
  2. boolean isCancelled():如果任务在正常结束前被取消,则返回true;
  3. boolean isDone():如果callable任务已执行完成,则返回true;
  4. V get() throws InterruptedException, ExecutionException:阻塞线程知道任务执行完成,并有值返回,在等待结果返回过程中,如果interrupted线程,抛出InterruptedException,执行出错,则抛出ExecutionException;
  5. V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException:阻塞设定的时间内等待结果,超过时间抛TimeoutException异常,其余同上;

创建代码如下:

public class CallableCreate {
    public static void main(String[] args) {
        //创建Callable对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>((Callable<Integer>) () -> {
            int i = 1;
            for (; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "的循环变量i值为 " + i);
            }
            return i;
        });
        for (int i = 1; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "的循环变量i值为:" + i);
            if (i == 20) {
                new Thread(futureTask, "callable线程").start();
                try {
                    //get()方法将导致main方法阻塞,直到值返回为止
                    System.out.println("获取当前线程的返回值为" + futureTask.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

三种创建方式的对比

实现Runnable和实现Callable来创建线程的方式其实可以归为一种,只不过Callable可以有返回值和抛出异常。因此创建线程的方式可以分成两类

  • 实现接口(Runnable,Callable)
    -- 优点是:由于Java单继承的设计方式,使用接口创建,可以额外继承其它类
    -- 缺点是:使用Thread的静态方法时麻烦些
  • 继承Thread
    -- 优点是可以简单使用Thread的静态方法
    -- 缺点是不能再额外继承其它类
    ==推荐使用实现接口的方式进行创建线程==

线程的生命周期

新建(New) 就绪(runnable) 运行(running) 阻塞(blocked,waitting,timed_waitting) 死亡(dead)
使用new关键字进行新建一个线程后,该线程就处于新建状态

当线程对象调用了start()方法之后,该线程就处于就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态中的线程并没有开始运行。是否运行,需要看jvm中线程调度器的调度。

线程启用说明:
run()只是线程执行体,不能直接使用此方法调用线程,而是要通过start()方法进行启用线程。如果先启用run()方法,后再启动该线程,会报IllegalThreadStateException异常,因为使用run()方法后threadStatus状态已变更。

线程运行说明:
线程调度的细节取决于操作系统的底层策略。
对抢占式策略的操作系统来说(所有现代的操作系统都是此策略),系统会给每个线程一段时间来处理业务,如果到时间了,系统就会剥夺该线程的处理器资源,交由下一个线程。
对协作式策略的操作系统来说(小型设备,如手机等),只有当线程主动调用sleep()或yield()时才会放弃系统的处理器资源。

线程阻塞说明:

  • 调用sleep()方法
  • IO阻塞(一般IO都为阻塞形式,NIO为非阻塞)
  • 等待同步锁
  • 等待通知(??)
  • suspend()(?容易引发死锁)--启用resume()恢复状态

线程死亡:

  • 正常结束
  • 抛异常
  • 调用stop()方法,容易引起死锁(?为什么)
线程生命周期

==说明:当启用一个线程后,该线程就与主线程具有同样的地位,不会因为主线程的结束而结束。==

控制线程

java提供了很多工具方法,通过这些工具方法,我们能更好的应用线程

sleep():线程睡眠

让当前正在执行的线程暂停一段时间,并进入阻塞状态。

public class SleepTest {
    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10;i++){
            System.out.println("当前时间: "+new Date());
            Thread.sleep(1000);
        }
    }
}
循环中,每执行一次暂定一秒.

yield():线程让步

让当前线程处于就绪状态。当某个线程调用yield()方法暂停之后,线程调度器又将其重新执行。
当某个线程调用了yield()方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪状态的线程才会获得执行的机会

join线程

当在某个程序执行流中调用其他线程的join()方法时,当前线程被阻塞,直到被join()方法加入的join线程执行为止.

public class JoinEffect implements Runnable {

    @Override
    public void run() {
        int random = (int) (Math.random() * 1000);
        System.out.println(Thread.currentThread().getName() + "睡眠时间为" + random + "毫秒");
        try {
            Thread.sleep(random);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new JoinEffect(), "被join的线程");
        thread.start();
        try {
            //如果没有这行代码,则主线程都会先于该子线程结束
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "线程结束");
    }
}

如果没有调用join()方法,则main线程能在分配给他的cpu资源占用时间内执行完成。

使用join出现异常的情况

public class ThreadA extends Thread{

    @Override
    public void run() {
        while (true){
        }
    }
}

public class ThreadB extends Thread{

    @Override
    public void run() {
        ThreadA threadA = new ThreadA();
        threadA.start();
        try {
            threadA.join();
            System.out.println("我是ThreadB,正常结束");
        } catch (InterruptedException e) {
            System.out.println("我是ThreadB,在异常处结束");
            e.printStackTrace();
        }

    }
}
public class ThreadC extends Thread{

    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.interrupt();
    }
}
public class JoinException {
    public static void main(String[] args) {
   
        ThreadB threadB = new ThreadB();
        threadB.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        ThreadC threadC = new ThreadC(threadB);
        threadC.start();
    }
}

ThreadB 中被join进了ThreadA,ThreadA一直执行,到ThreadC时,尝试停止ThreadB,此时会抛异常.运行结果如下:


image

此时程序还一直在运行,是因为
ThreadA线程一直在运行。调用interrupt()方法时,会由于三种情况抛出异常:

  • Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.
  • If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then - its interrupt status will be cleared and it will receive an InterruptedException.
  • If this thread is blocked in an I/O operation upon an InterruptibleChannel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a java.nio.channels.ClosedByInterruptException.

如果在ThreadB中指定join()等待时间,如果等待时间小于JoinException中main线程的sleep时间,则不会抛出异常。因为在启动ThreadC线程时,ThreadB已经执行完成。

join(long)与sleep(long)的区别

join(long)方法具有释放锁的特点,而sleep(long)则不具有释放锁的特点。

线程同步

线程安全问题

多线程来访问同一个数据,很容易“偶然出现”问题。
如果模拟两个线程对同一个账户进行使用取钱的过程。创建一个账户对象,该对象有两个属性,一个是账户名称accountName,一个是账户余额balance.

public class Account {
    private String accoutName;

    private BigDecimal balance;

    public Account(String accoutName, BigDecimal balance) {
        this.accoutName = accoutName;
        this.balance = balance;
    }

    public String getAccoutName() {
        return accoutName;
    }

    public void setAccoutName(String accoutName) {
        this.accoutName = accoutName;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }
}

创建一个取钱线程,当账户余额大于或等于取出的金额时,才允许取钱,否则提示“余额不足”

public class DrawThread extends Thread {
    private Account account;

    private BigDecimal drawAmt;

    public DrawThread(String threadName,Account account, BigDecimal drawAmt) {
        super(threadName);
        this.account = account;
        this.drawAmt = drawAmt;
    }

    @Override
    public void run() {
        if (account.getBalance().compareTo(drawAmt) > 0) {
            System.out.println(getName()+"取钱成功,取出"+drawAmt+"元");
     /*      try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }*/ 
            account.setBalance(account.getBalance().subtract(drawAmt));
            System.out.println("余额为:"+account.getBalance());
        } else {
            System.out.println(account.getAccoutName() + "的账户余额不足!");
        }
    }

    public static void main(String[] args) {
        Account account = new Account("测试账户", BigDecimal.valueOf(1200));
        DrawThread thread = new DrawThread("线程001", account, BigDecimal.valueOf(800));
        thread.start();
        DrawThread thread2 = new DrawThread("线程002", account, BigDecimal.valueOf(800));
        thread2.start();
    }
}

当多次执行该main方法时,会出现如下结果:


image

如果把注释的代码放开,则会一直出现上述结果。出现这种情况的原因为,==线程是在判断余额时进行切换的。当第一个线程判断通过后,还没有往下执行时,切换到另一个线程中,此时余额还没有变更,导致第二个线程还是可以进入到取钱的代码中。==

同步代码块

java的多线程支持引入了同步监视器来解决这个问题

.同步监视器代码结构如下:

 synchronized (obj){
            //业务代码
        }

obj就是同步监视器,上面代码的含义是:线程开始执行同步代码之前,必须先获得同步监视器的锁定。

任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程会释放对同步件事器的锁定。

==同步监视器的作用:防止两个线程对同一个共享资源进行并发访问。==

上面两个线程进行取钱的问题可以通过使用同步代码块的方式来进行解决线程安全问题。

public class DrawThread extends Thread {
    private Account account;

    private BigDecimal drawAmt;

    public DrawThread(String threadName,Account account, BigDecimal drawAmt) {
        super(threadName);
        this.account = account;
        this.drawAmt = drawAmt;
    }

    @Override
    public void run() {
        /**
         * 把account作为一个同步监视器,任何线程在进入该代码块之前都需要获取到account的锁定,
         * 加锁->修改->释放锁
         */
        synchronized (account){
            if (account.getBalance().compareTo(drawAmt) > 0) {
                System.out.println(getName()+"取钱成功,取出"+drawAmt+"元");
                account.setBalance(account.getBalance().subtract(drawAmt));
                System.out.println("余额为:"+account.getBalance());
            } else {
                System.out.println(account.getAccoutName() + "的账户余额不足!");
            }
        }
    }

    public static void main(String[] args) {
        Account account = new Account("测试账户", BigDecimal.valueOf(1200));
        DrawThread thread = new DrawThread("线程001", account, BigDecimal.valueOf(800));
        thread.start();
        DrawThread thread2 = new DrawThread("线程002", account, BigDecimal.valueOf(800));
        thread2.start();
    }
}
  • 同步方法
    同步方法就是使用synchronized关键字修饰的方法。同步方法不需要显示制定同步监视器,默认把当前对象当做同步监视器。
public class AccountSync {
    private String accoutName;

    private BigDecimal balance;

    public AccountSync(String accoutName, BigDecimal balance) {
        this.accoutName = accoutName;
        this.balance = balance;
    }

    public  synchronized void draw(BigDecimal drawAmt){
        if (balance.compareTo(drawAmt) > 0) {
            System.out.println(Thread.currentThread().getName()+"取钱成功,取出"+drawAmt+"元");
            this.setBalance(balance.subtract(drawAmt));
            System.out.println("余额为:"+balance);
        } else {
            System.out.println(accoutName + "的账户余额不足!");
        }
    }

    public String getAccoutName() {
        return accoutName;
    }

    public void setAccoutName(String accoutName) {
        this.accoutName = accoutName;
    }

    public BigDecimal getBalance() {
        return balance;
    }

    public void setBalance(BigDecimal balance) {
        this.balance = balance;
    }

}

线程调用方式代码如下:

  public void run(){
        account.draw(drawAmt);
    }

同步方法的使用策略

  • 只对那些会改变竟态资源也(也叫共享资源)的方法进行同步。如上面的account类中,不需要对accountName实例变量进行同步。
  • 如果存在单线程和多线程两种环境,可提供两种版本。

同步监视器锁的释放

1.代码正常结束

2.遇到break,return终止了该代码块或该方法的继续执行,当前线程将会释放同步监视器。

3.程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。

以下该种线程不会释放同步监视器:

1.程序调用了Thread.sleep(),Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器

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

同步锁(Lock)-待详细

ReentrantLock的使用。

死锁

  • 什么是死锁
    • 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程

会造成死锁的一个简单例子:

class ResourceA {
    public synchronized void getResourceBName(ResourceB b) {
        System.out.println(Thread.currentThread().getName() + "线程进入ResourceA中的getResourceBName方法");
        try {
            Thread.sleep(200);//2
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程准备调用ResourceB方法来获取B中的名称");//4
        b.printName();
    }

    public synchronized void printName() {
        System.out.println("我是resourceA!");
    }
}

class ResourceB {
    public synchronized void printName() {
        System.out.println("我是resourceB!");
    }

    public synchronized void getResourceAName(ResourceA a) {
        System.out.println(Thread.currentThread().getName() + "线程进入ResourceB中的getResourceAName方法");
        try {
            Thread.sleep(200);//3
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "线程准备调用ResourceA方法来获取A中的名称");//5
        a.printName();
    }
}

public class DeadLock implements Runnable{
    private ResourceA a = new ResourceA();
    private ResourceB b = new ResourceB();

    @Override
    public void run() {
        a.getResourceBName(b);
    }

    public void initB(){
        b.getResourceAName(a);
    }

    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();
        new Thread(deadLock,"死锁测试线程").start();//1
        deadLock.initB();
    }
}

代码运行到1处,会新建一个“死锁测试线程”线程,该线程的执行体用于调用ResourceA中的getResourceName方法。主线程继续执行,会调用到DeadLock的initB()方法。当代码运行到2时,死锁测试线程会变为阻塞状态,此时该线程已经获取到ResourceA的同步监视器。此时会切换到主线程来执行,当运行到3处时,主线程变为阻塞状态,此时该线程已经获取到ResourceB的同步监视器了。此时又切回死锁测试线程运行,当运行到4处时,会先尝试去获取ResourceB的同步监视器,因为该同步监视器被主线程持有,所以死锁测试线程会进入阻塞状态,此时会切回主线程。代码运行到5处时,会尝试获取ResourceA的同步监视器,因为该同步监视器被死锁测试线程持有,又会进入阻塞状态。此时双方都在等待对方释放同步监视器,此时既不会抛异常,也不会继续往下走,因此产生死锁。死锁简图:


image

使用jstack命令dump出该线程使用情况,节选如下:

Found one Java-level deadlock:
=============================
"死锁测试线程":
  waiting to lock monitor 0x000000005774d158 (object 0x00000000d5eb9c08, a com.blucewang.multiThread.extra.synchronize.deadLock.ResourceB),
  which is held by "main"
"main":
  waiting to lock monitor 0x000000005774bc08 (object 0x00000000d5eb6628, a com.blucewang.multiThread.extra.synchronize.deadLock.ResourceA),
  which is held by "死锁测试线程"

如何避免死锁,如死锁简图所示,当双方都出现竞争彼此的持有锁时就会产生死锁。因此,可以从避免出现竞态条件进行考虑来避免死锁的产生。

参考自java并发编程的艺术

  • 一个线程中尽量不要同时获取多个锁
  • 使用有时间限制的竟锁条件,如使用lock.tryLock(seconds)来进行获取锁
  • 避免一个锁内占有多个资源
  • 对于数据库锁,加锁和解锁要在同一个数据库连接中,否则会出现解锁失败的情况。

线程间的通信

synchronize及Object中的wait和notify来通信
使用synchronize修饰的方法,默认的同步监视器是当前对象,使用当前对象的wait和notify方法来进行通信

  • wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程
  • notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意的,只有同步监视器放弃对同步监视器的锁定后(使用wait()方法),才可以执行唤醒。
  • notifyAll():唤醒在此同步监视器上的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

假设有一种情况,一个线程不断存钱,一个线程不断取钱,只有当存进钱以后,取钱线程才能执行。

public class AccountNormal {

    private String accoutName;

    private BigDecimal balance = BigDecimal.ZERO;

    /**
     * 余额是否足够标记
     */
    private volatile boolean balanceEnoughFlag = false;

    public AccountNormal(String accoutName, BigDecimal balance) {
        this.accoutName = accoutName;
        this.balance = balance;
    }

    public synchronized void draw(BigDecimal drawAmt) {
        //余额不足时,释放当前的同步监视器,该线程进入等待
        if (!balanceEnoughFlag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        balance = balance.subtract(drawAmt);
        System.out.println("余额为:" + balance+",钱不够了快来存吧!");
        balanceEnoughFlag = false;
        notifyAll();
    }

    public synchronized void deposit(BigDecimal amt) {
        //余额足够时,释放同步监视器,让线程进入等待
        if (balanceEnoughFlag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        balance = balance.add(amt);
        System.out.println("余额为:" + balance+",钱已经存进去了,快来消费吧!");
        balanceEnoughFlag = true;
        notifyAll();
    }

    public String getAccoutName() {
        return accoutName;
    }

    public void setAccoutName(String accoutName) {
        this.accoutName = accoutName;
    }

    public BigDecimal getBalance() {
        return balance;
    }

}

public class DrawThread implements Runnable {
    private AccountNormal account;

    private AccountLock accountLock;

    private BigDecimal drawAmt;

    public DrawThread(AccountNormal account, BigDecimal drawAmt) {
        this.account=account;
        this.drawAmt=drawAmt;
    }

    public DrawThread(AccountLock accountLock, BigDecimal drawAmt) {
        this.accountLock=accountLock;
        this.drawAmt=drawAmt;
    }



    @Override
    public void run() {
        for (int i=0;i<100;i++){
            //使用synchronized关键字来加锁,使用Object中的wait()和notifyAll()方法来进行线程通信
//            account.draw(drawAmt);
            //使用Lock显式锁,并使用Condition中的await()和sinalAll()来进行线程通信
            accountLock.draw(drawAmt);
        }
    }
}

public class DepositThread implements Runnable {
    private AccountNormal account;

    private AccountLock accountLock;

    private BigDecimal depositAmt;

    public DepositThread(AccountNormal account, BigDecimal depositAmt) {
        this.account = account;
        this.depositAmt = depositAmt;
    }

    public DepositThread(AccountLock accountLock, BigDecimal depositAmt) {
        this.accountLock = accountLock;
        this.depositAmt = depositAmt;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //使用synchronized关键字来加锁,使用Object中的wait()和notifyAll()方法来进行线程通信
//            account.deposit(depositAmt);
            //使用Lock显式锁,并使用Condition中的await()和sinalAll()来进行线程通信
            accountLock.deposit(depositAmt);
        }
    }
}

  • Lock及Condition中的await和signalAll来通信

可以使用显示锁,及显示锁的Condition对象搭配使用

public class AccountLock {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    private String accoutName;

    private BigDecimal balance = BigDecimal.ZERO;

    /**
     * 余额是否足够标记
     */
    private volatile boolean balanceEnoughFlag = false;

    public AccountLock(String accoutName, BigDecimal balance) {
        this.accoutName = accoutName;
        this.balance = balance;
    }

    public void draw(BigDecimal drawAmt) {
        lock.lock();//显示使用锁
        try {
            //余额不足时,释放当前的同步监视器,该线程进入等待
            if (!balanceEnoughFlag) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            balance = balance.subtract(drawAmt);
            System.out.println("余额为:" + balance + ",钱不够了快来存吧!");
            balanceEnoughFlag = false;
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//抛异常强制解锁
        }
    }

    public void deposit(BigDecimal amt) {
        lock.lock();
        try {
            //余额足够时,释放同步监视器,让线程进入等待
            if (balanceEnoughFlag) {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            balance = balance.add(amt);
            System.out.println("余额为:" + balance + ",钱已经存进去了,快来消费吧!");
            balanceEnoughFlag = true;
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public String getAccoutName() {
        return accoutName;
    }

    public void setAccoutName(String accoutName) {
        this.accoutName = accoutName;
    }

    public BigDecimal getBalance() {
        return balance;
    }
}

有道笔记链接

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容

  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,921评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,377评论 1 15
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,257评论 3 87
  • 第三章 Java内存模型 3.1 Java内存模型的基础 通信在共享内存的模型里,通过写-读内存中的公共状态进行隐...
    泽毛阅读 4,287评论 2 22
  • 既来之则安之 我还在这里,我没确定好我去了之后会不会又逃跑,所以我不走了;就安安静静的呆在这里,哪里也不要去了。我...
    三小妹阅读 1,087评论 0 0