• 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
  • 相关阅读:
    c++坐标移动
    c++字串的连接最长路径查找
    c++句子逆序——堆栈实现
    c++句子逆序——substr函数
    c++计数法解决统计不同字符个数
    c++提取不重复的整数-计数
    C++取近似值简单算法
    c++排序去重
    c++计数排序例子
    分布式服务框架 Zookeeper
  • 原文地址:https://www.cnblogs.com/geekfx/p/12706377.html
Copyright © 2020-2023  润新知