Java多线程编程

多线程初体验

多线程创建:

  • 方式一:继承 Thread 类
    1. 创建一个类继承 Thread 类
    2. 重写 run() 方法 -->> 将此线程需要的操作写在 run() 方法中
    3. 创建实例,调用 start() 方法
      • 启动当前线程
      • 调用 run() 方法

注意: 一个线程对象只能 start() 一次

方式一代码

package work.jkfx.thread.create;

/**
 * 多线程创建:
 *  -方式一:继承 Thread 类
 *      1. 创建一个类继承 Thread 类
 *      2. 重写 run() 方法  -->> 将此线程需要的操作写在 run() 方法中
 *      3. 创建实例,调用 start() 方法
 *          - 启动当前线程
 *          - 调用 run() 方法
 *      注意: 一个线程对象只能 start() 一次
 *
 * @author geekfx
 * @create 2020/4/1 11:48
 */

class MyFirstThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

public class ThreadTest extends Thread {

    public static void main(String[] args) {
        MyFirstThread thread = new MyFirstThread();
        thread.start();

        MyFirstThread thread1 = new MyFirstThread();
        thread1.start();

        // 以创建匿名子类的方式创建一个线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }.start();

        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0)
                System.out.println(Thread.currentThread().getName());
        }
    }

}

  • 方式二
    1. 定义一个类实现 Runnable 接口
    2. 实现 run() 方法
    3. 创建对象实例 target
    4. 将此对象实例放入到 Thread(Runnable target) 构造器中 得到线程对象
    5. 调用创建线程的 start() 方法
      • 启动当前线程
      • 调用 target 的 run() 方法

方式二代码

package work.jkfx.thread.create;

/**
 * 创建线程方式二
 *  1. 定义一个类实现 Runnable 接口
 *  2. 实现 run() 方法
 *  3. 创建对象实例 target
 *  4. 将此对象实例放入到 Thread(Runnable target) 构造器中 得到线程对象
 *  5. 调用创建线程的 start() 方法
 *      - 启动当前线程
 *      - 调用 target 的 run() 方法
 *
 * @author geekfx
 * @create 2020/4/1 14:14
 */

class CreateThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class CreateThreadTest {
    public static void main(String[] args) {
        CreateThread createThread = new CreateThread();
        Thread thread = new Thread(createThread);
        Thread thread1 = new Thread(createThread);

        thread.setName("线程1");
        thread1.setName("线程2");

        thread.start();
        thread1.start();
    }
}

线程常用方法

  1. start() 启动当前线程,调用当前线程的 run()
  2. run() 通常需要重写 Thread 类中的此方法
  3. currentThread() 静态方法 返回执行当前代码的线程
  4. getName() 获得当前线程的名字
  5. setName() 设置当前线程的名字
  6. yield() 释放当前线程所占cpu资源(有可能分配下一个线程又分配回来)
  7. join() 在线程中 a 中调用线程 b 的 join() 方法,此时线程 a 进入阻塞状态,直到线程 b 完全执行结束后,线程 a 结束阻塞(但不立即执行[就绪状态])
  8. stop() 强制结束当前线程(已过时,不推荐使用)
  9. sleep(long millis) 静态方法 使当前线程睡眠 millis 毫秒(阻塞)
  10. isAlive() 判断当前线程是否存活

线程优先级

  • 线程优先级有3个常量表示
    • MAX_PRIORITY
    • NORM_PRIORITY -->> 默认
    • MIN_PRIORITY
  • 获取/设置当前线程优先级
    • getPriority()
    • setPriority(int newPriority)

注意:并不是 100% 执行高优先级的线程,而是大概率执行。

线程安全问题

解决线程安全问题

  • 方式一:同步代码块
synchronized(同步监视器) {
    // 需要被同步的代码 -->> 访问共享数据的代码
}

说明:同步监视器 就是俗称的 锁 任何一个对象都可以充当锁
要求:所有线程共用一把锁

  • 方式二:同步方法
    将需要访问共享数据的代码放到一个方法中,将此方法的修饰符加一个 synchronized 即可

模拟三个窗口售100张票 使用继承 Thread 方式

package work.jkfx.thread.safe;

/**
 * 模拟三个窗口售100张票 使用继承 Thread 方式
 * 存在线程安全问题
 *
 * 解决线程安全问题
 *  - 方式一:同步代码块
 *      synchronized(同步监视器) {
 *          // 需要被同步的代码
 *      }
 *  - 方式二:同步方法
 *      * 使用 synchronized 修饰方法
 *
 * @author geekfx
 * @create 2020/4/1 13:57
 */

class Window extends Thread {

    private static int ticket = 100;
//    private static Object lock = new Object();    可以不需要 直接使用 类.class

    @Override
    public void run() {
        while(true) {
            // Window.class 只会加载一次
            // 而且可以被多个对象实例共享
//            synchronized(Window.class) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//                if (ticket > 0) {
//                    System.out.println(getName() + ": " + ticket);
//                    ticket--;
//                } else {
//                    break;
//                }
//            }
            show();
        }
    }

    private static synchronized void show() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + ticket);
            ticket--;
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

模拟三个窗口售票100张 使用实现 Runnable 接口方式

package work.jkfx.thread.safe;

/**
 * 模拟三个窗口售票100张 使用实现 Runnable 接口方式
 * 存在线程安全问题
 *
 * 解决线程安全问题
 *  方式一:同步代码块
 *      synchronized(同步监视器) {
 *          // 需要被同步的代码 -->> 访问共享数据的代码
 *      }
 *      说明:同步监视器 就是俗称的 锁 任何一个对象都可以充当锁
 *          要求:所有线程共用一把锁
 *  方式二:同步方法
 *      将需要访问共享数据的代码放到一个方法中,将此方法的修饰符加一个 synchronized 即可
 *
 * @author geekfx
 * @create 2020/4/1 14:27
 */

class WindowImpl implements Runnable {

    private int ticket = 100;
//    Object lock = new Object(); // 同步监视器 可以不需要

    @Override
    // synchronized 可以直接加在 run() 方法中
    public void run() {
        while(true) {
            // 使用同步代码块解决线程安全问题
            // 同步监视器可以不用建对象
            // 可以直接用创建线程的对象来当锁
//            synchronized(this) {
//                if (ticket > 0) {
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + ": " + ticket);
//                    ticket--;
//                } else {
//                    break;
//                }
//            }
            show();
        }
    }

    private synchronized void show() {
        // 使用同步方法解决线程安全问题
        // 同步方法的同步监视器是 this
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": " + ticket);
            ticket--;
        }
    }
}

public class WindowImplTest {
    public static void main(String[] args) {
        // 使用下面对象充当锁
        WindowImpl window = new WindowImpl();

        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

解决单例懒汉式模式的线程安全问题

package work.jkfx.thread.safe;

/**
 * 解决单例懒汉式模式的线程安全问题
 * @author geekfx
 * @create 2020-04-02 18:36
 */
public class BankTest {
}

// 单例模式——懒汉式
class Bank {
    private Bank() {}

    private static Bank instance = null;

    public static Bank getInstance() {
        // 方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null) {
//                instance = new Bank();
//            }
//            return instance;
//        }
        // 方式二
        if(instance == null) {
            synchronized(Bank.class) {
                if(instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

死锁

演示死锁问题

解决死锁的方式:

  • 专门的算法、原则
  • 尽量减少同步资源的定义
  • 尽量避免嵌套同步
package work.jkfx.thread.deadlock;

/**
 * 演示死锁问题
 *
 * 解决死锁的方式:
 *  - 专门的算法、原则
 *  - 尽量减少同步资源的定义
 *  - 尽量避免嵌套同步
 *
 * @author geekfx
 * @create 2020-04-02 19:19
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer("");
        StringBuffer s2 = new StringBuffer("");

        new Thread() {
            // 使用匿名类的继承方式创建线程
            @Override
            public void run() {
                synchronized(s1) {
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized(s2) {
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            // 使用匿名类的实现方式创建进程
            @Override
            public void run() {
                synchronized (s2) {
                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();

    }
}

解决线程安全问题

  • 方式三:使用 Lock 接口(ReentrantLock 实现类)
package work.jkfx.thread.deadlock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题
 *  - 方式三:使用 Lock 接口(ReentrantLock 实现类)
 * @author geekfx
 * @create 2020-04-02 19:40
 */

class Window implements Runnable {

    private int ticket = 100;
    /*
    ReentrantLock 有两个构造器:
        - ReentrantLock(boolean fair);
            * 当 fair 为 true 时:创建公平锁
                - 按照先进先出的方式处理等待的线程
        - ReentrantLock();
            * 无参构造器默认 fair 为 false
     */
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true) {
            try {
                lock.lock();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + ": " + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window window = new Window();

        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

死锁的演示代码

package work.jkfx.thread.deadlock;
//死锁的演示
class A {
	public synchronized void foo(B b) { //同步监视器:A类的对象:a
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了A实例的foo方法"); // ①
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用B实例的last方法"); // ③
		b.last();
	}

	public synchronized void last() {//同步监视器:A类的对象:a
		System.out.println("进入了A类的last方法内部");
	}
}

class B {
	public synchronized void bar(A a) {//同步监视器:b
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 进入了B实例的bar方法"); // ②
		try {
			Thread.sleep(200);
		} catch (InterruptedException ex) {
			ex.printStackTrace();
		}
		System.out.println("当前线程名: " + Thread.currentThread().getName()
				+ " 企图调用A实例的last方法"); // ④
		a.last();
	}

	public synchronized void last() {//同步监视器:b
		System.out.println("进入了B类的last方法内部");
	}
}

public class DeadLock implements Runnable {
	A a = new A();
	B b = new B();

	public void init() {
		Thread.currentThread().setName("主线程");
		// 调用a对象的foo方法
		a.foo(b);
		System.out.println("进入了主线程之后");
	}

	public void run() {
		Thread.currentThread().setName("副线程");
		// 调用b对象的bar方法
		b.bar(a);
		System.out.println("进入了副线程之后");
	}

	public static void main(String[] args) {
		DeadLock dl = new DeadLock();
		new Thread(dl).start();

		dl.init();
	}
}

线程通信

线程通信设计的三个方法:

  • wait() 一旦执行此方法,当前进程进入阻塞状态,并且释放同步监视器(锁)。
  • notify() 执行此方法,唤醒一个被 wait() 的线程,如果阻塞的线程有多个,唤醒一个优先级最高的。
  • notifyAll() 执行此方法,唤醒所有被 wait() 的线程

说明:

  • 三个方法必须放在同步代码块或同步方法中
  • 三个方法的调用者必须是同步代码块/同步方法的同步监视器
  • 三个方法都是定义在 java.lang.Object 类下
package work.jkfx.thread.communication;

/**
 * 使两个线程交替打印输出 1-100
 * 线程通信设计的三个方法:
 * - wait() 一旦执行此方法,当前进程进入阻塞状态,并且释放同步监视器(锁)。
 * - notify() 执行此方法,唤醒一个被 wait() 的线程,如果阻塞的线程有多个,唤醒一个优先级最高的。
 * - notifyAll() 执行此方法,唤醒所有被 wait() 的线程
 * 说明:
 * - 三个方法必须放在同步代码块或同步方法中
 * - 三个方法的调用者必须是同步代码块/同步方法的同步监视器
 * - 三个方法都是定义在 java.lang.Object 类下
 *
 * @author geekfx
 * @create 2020-04-02 21:06
 */

class Communication implements Runnable {
    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if(number <= 100) {
                    System.out.println(Thread.currentThread().getName() + ": " + number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Communication communication = new Communication();
        Thread t1 = new Thread(communication);
        Thread t2 = new Thread(communication);

        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

JDK5.0 新增两种线程创建方式

  • 方式一:实现 Callable 接口
    1. 创建一个类实现 Callable 接口 此接口比 Runnable 强大 支持泛型
    2. 实现 cal() 方法,此方法类似 run() 方法但有返回值而且可以抛出异常
    3. 创建实现类的对象
    4. 将实现类对象作为参数传递到 FutureTask 类的构造器中,得到 FutureTask 对象
    5. 将 FutureTask 对象作为参数传递到 Thread 构造器中
    6. 调用 start() 方法启动线程
    7. 完成任务后,可以调用 FutureTask 对象的 get() 方法来获得实现类实现的 cal() 方法的返回值

说明:

  1. FutureTask 类实现了 Runnable、Future 接口,并且是 Future 接口的唯一实现类
  2. 它既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值

方式一代码

package work.jkfx.thread.create;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * JDK5.0 新增两种创建线程的方式:
 * - 方式一:实现 Callable 接口
 * (1) 创建一个类实现 Callable 接口 此接口比 Runnable 强大 支持泛型
 * (2) 实现 cal() 方法,此方法类似 run() 方法但有返回值而且可以抛出异常
 * (3) 创建实现类的对象
 * (4) 将实现类对象作为参数传递到 FutureTask 类的构造器中,得到 FutureTask 对象
 * (5) 将 FutureTask 对象作为参数传递到 Thread 构造器中
 * (6) 调用 start() 方法启动线程
 * (7) 完成任务后,可以调用 FutureTask 对象的 get() 方法来获得实现类实现的 cal() 方法的返回值
 * * 说明:
 * (1) FutureTask 类实现了 Runnable、Future 接口,并且是 Future 接口的唯一实现类
 * (2) 它既可以作为 Runnable 被线程执行,也可以作为 Future 得到 Callable 的返回值
 *
 * @author geekfx
 * @create 2020-04-02 22:35
 */

//1. 创建类,实现 Callable 接口并实现 cal() 方法
class NewCreate implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 101; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
                sum += i;
            }
        }
        return sum;
    }
}

public class NewCreateTest {

    public static void main(String[] args) {
        //2. 创建实现类的对象
        NewCreate newCreate = new NewCreate();
        //3. 将实现类对象作为参数传递到 FutureTask 构造器中,得到 FutureTask 对象
        FutureTask futureTask = new FutureTask(newCreate);
        //4. 将 FutureTask 对象作为参数传递到 Thread 构造器中,创建线程对象
        Thread thread = new Thread(futureTask);
        thread.setName("新线程");
        //5. 执行 start() 方法启动线程
        thread.start();
        //6. 可以调用 FutureTask 对象的 get() 方法来获得实现类的 cal() 方法的返回值
        try {
            Object o = futureTask.get();
            System.out.println("cal() 方法的返回值: " + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
  • 方式二:使用线程池

提前创建好多个线程,放入线程池;使用时直接获取,使用后放回,避免了频繁的创建销毁,实现了重复利用;便于线程管理。

  1. 调用工具类 Executors.newFixedThreadPool(int nThreads) 返回一个容量为 nThreads 个线程的线程池
  2. 返回一个 (Executor[interface] -> ExecutorService[interface] -> AbstractExecutorService[class] -> ThreadPoolExcutor) 对象
  3. 调用 execute()/submit() 方法传入一个实现了 Runnable/Callable 接口的方法来启动线程,后者可以有返回值,返回值为实现 cal() 方法的返回值
  4. 调用 shutdown() 关闭线程池

方式二代码

package work.jkfx.thread.create;

import java.util.concurrent.*;

/**
 * JDK5.0 新增两种线程创建方式
 * - 方式二:使用线程池
 * 提前创建好多个线程,放入线程池;使用时直接获取,使用后放回,避免了频繁的创建销毁,实现了重复利用;便于线程管理。
 * (1) 调用工具类 Executors.newFixedThreadPool(int nThreads) 返回一个容量为 nThreads 个线程的线程池
 * (2) 返回一个 (Executor[interface] -> ExecutorService[interface] -> AbstractExecutorService[class] -> ThreadPoolExcutor) 对象
 * (3) 调用 execute()/submit() 方法传入一个实现了 Runnable/Callable 接口的方法来启动线程,后者可以有返回值,返回值为实现 cal() 方法的返回值
 * (4) 调用 shutdown() 关闭线程池
 *
 * @author geekfx
 * @create 2020-04-02 23:30
 */
public class ThreadPool {

    public static void main(String[] args) {
        //1. 使用工具类 Executors 创建进程池
        ExecutorService executorService = Executors.newFixedThreadPool(15);
        // 实际上返回的对象是 ThreadPoolExecutor 对象
        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
        // 由于 ExecutorService 方法不多不便于线程池管理 返回的对象实际上是 ThreadPoolExecutor 有很多管理线程池的方法

        // 调用 execute() 方法传入一个实现了 Runnable 接口的方法
        // 此方法没有返回值、不能抛出异常、不支持泛型
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + i);
                    }
                }
            }
        });

        // 调用 submit() 传入一个实现了 Callable 接口的类
        // 使用此方法可以有返回值、可以抛出异常、可以使用泛型
        // 此方法返回一个 FutureTask 对象
        Future<Integer> future = threadPoolExecutor.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                for (int i = 1; i <= 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + ": " + i);
                        sum += i;
                    }
                }
                return sum;
            }
        });
        try {
            Integer sum = future.get();
            System.out.println("使用 submit() 方法启动线程得到的返回值: " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        // 关闭线程池
        executorService.shutdown();
    }
}

经典线程同步问题 生产者-消费者问题

package work.jkfx.thread.exer;

/**
 * 经典线程同步问题 生产者-消费者问题
 * 假设产品最多存放 20 个
 *
 * @author geekfx
 * @create 2020-04-02 21:41
 */

// 店员 最多存放 20 个产品
class Clerk {
    private int productNum = 0;

    // 生产产品
    public synchronized void produceProduct() {
        if(productNum < 20) {
            productNum++;
            System.out.println(Thread.currentThread().getName() + ": " + productNum);
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    // 消费产品
    public synchronized void consumeProduct() {
        if(productNum > 0) {
            System.out.println(Thread.currentThread().getName() + ": " + productNum);
            productNum--;
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 生产者
class Producer extends Thread {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + " 开始生产产品");
        while (true) {
            try {
                Thread.sleep(80);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

// 消费者
class Consumer extends Thread {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + " 开始消费产品");
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

public class ProductTest {

    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");

//        Consumer c2 = new Consumer(clerk);
//        c2.setName("消费者2");

        p1.start();
        c1.start();
//        c2.start();
    }
}

线程安全问题练习:银行账户

package work.jkfx.thread.exer;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 线程安全问题测试:银行账户
 * 一个银行账户有 3000 元,两个账户往里存钱,每个账户存 3 次,每次存 1000,存完打印余额
 *
 * @author geekfx
 * @create 2020-04-02 19:57
 */

class Account {
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    public void save(double money) {

        balance += money;

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "存钱 余额: " + balance);
    }
}

class Customer extends Thread {
    private Account account;
    // 如果使用 ReentrantLock 在继承子类的线程类的方式解决线程安全问题
    // 需要将锁设置为静态
    private static ReentrantLock lock = new ReentrantLock();

    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            lock.lock();
            account.save(1000);
            lock.unlock();
        }
    }
}

public class BankAccountTest {
    public static void main(String[] args) {
        Account account = new Account(3000);
        Customer c1 = new Customer(account);
        Customer c2 = new Customer(account);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();
    }
}
不一定每天 code well 但要每天 live well
原文地址:https://www.cnblogs.com/geekfx/p/12706377.html