• java多线程


    Java多线程笔记

    1 定义

    • 程序
      • 程序是指令和数据的有序集合,没有任何运行的含义,是个静态的概念
    • 进程(process)
      • 进程是执行程序的一次执行过程,是动态的概念
      • 进程是系统资源分配的单位
    • 线程(thread)
      • 一个进程中可以包含若干个线程
      • 线程是CPU调度和执行的单位
    • 多线程
      • 真正的多线程是指有多个CPU,即多核
      • 在一个CPU的情况下,在一个时间点,CPU只能执行一个代码,只是切换很快,有同时进行的错觉

    2 线程创建

    2.1 继承Thread类

    1. 自定义线程类继承Thread类
    2. 重写run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
    public class TestThread1 extends Thread {
        //run方法线程体
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in sub thread is " + i);
            }
        }
        
        //main线程,主线程
        public static void main(String[] args) {
            //创建一个子线程
            TestThread1 testThread = new TestThread1();
            //调用start方法开启线程
            testThread.start();
            
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in main thread is " + i);
            }
        }
    }
    

    运行之后,我们能看到两个线程的输出是交错的,说明两个线程在同时运行。如果不明显,可以将循环次数调大

    如果在testThread直接调用run方法,那么不会出现多线程的情况,输出结果是由上至下依次输出的。调用start方法才会产生多线程的情况

    另外,线程开启后不一定立即执行,由CPU调度执行

    实现多线程同步下载图片

    使用commons.io包

    Copypublic class TestThread2 extends Thread {
        
        private String url;		//网络文件地址
        private String fileName;	//保存的文件
        
        public TestThread2(String url, String fileName) {
            this.url = url;
            this.fileName = fileName;
        }
        
        @Override
        public void run() {
            WebDownloader webDownloader = new WebDownloader();
            webDownloader.download(url, fileName);
            System.out.println("over, and the file is " + fileName);
        }
        
        public static void main(String[] args) {
            TestThread2 t1 = new TestThread2("https://img1.doubanio.com/view/photo/raw/public/p1413828179.jpg", "1.jpg");
            TestThread2 t2 = new TestThread2("https://img9.doubanio.com/view/photo/raw/public/p2162992026.jpg", "2.jpg");
            TestThread2 t3 = new TestThread2("https://img1.doubanio.com/view/photo/raw/public/p1413828209.jpg", "3.jpg");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    
    //下载器
    class WebDownloader {
        //下载方法
        public void download(String url, String fileName) {
            try {
                FileUtils.copyURLToFile(new URL(url), new File(fileName));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    2.2 实现Runnable接口

    1. 定义线程类实现Runnable接口
    2. 实现run()方法,编写线程执行体
    3. 创建线程对象,调用start()方法启动线程
    Copypublic class TestThread3 implements Runnable {
        //run方法线程体
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in sub thread is " + i);
            }
        }
        
        //main线程,主线程
        public static void main(String[] args) {
            //创建Runnable接口的实现类对象
            TestThread3 testThread = new TestThread3();
            //创建线程对象,通过线程对象开启线程
            //Thread thread = new Thread(testThread);
            //thread.start();
            new Thread(testThread).start();
            
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in main thread is " + i);
            }
        }
    }
    

    建议使用实现Runnable接口的做法,避免单继承的局限性,方便一个对象被多个线程使用

    多个线程操作同一个对象

    Copypublic class TestThread4 implements Runnable {
        
        private int ticketNum = 10;
        
        @Override
        public void run() {
            while (true) {
                if (ticketNum <= 0)
                    break;
                //模拟延时
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
            }
        }
        
        public static void main(String[] args) {
            TestThread4 thread = new TestThread4();
            new Thread(thread, "test1").start();
            new Thread(thread, "test2").start();
            new Thread(thread, "test3").start();
        }
    }
    

    输出结果显示会产生数据紊乱,不符合常理的问题,多个线程操作同一个资源的情况下,线程不安全

    其实,new Thread(thread).start() 这种方式是静态代理实现的,Thread类也是实现了Runnable接口,把实现了Runnable接口的thread对象作为参数传进去,就是静态代理的过程

    此外,对于thread对象可以用匿名内部类代替,比如

    Copynew Thread(testThread).start();
    

    其中,testThread实现了Runnable接口,可以写为

    Copynew Thread(new Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in sub thread is " + i);
            }
        }
    }).start();
    

    还可以用lambda表达式进一步简化,这要求jdk的版本为8以上

    Copynew Thread(() -> {
        for (int i = 0; i < 20; i++) {
            System.out.println("the number in sub thread is " + i);
        }
    }).start();
    

    2.3 实现Callable接口

    1. 实现Callable接口,需要返回值类型

    2. 重写call方法,需要抛出异常

    3. 创建目标对象

    4. 创建执行服务

      CopyExecutorService service = Executors.newFixedThreadPool(1);
      
    5. 提交执行

      CopyFuture<Boolean> result = service.submit(thread);
      
    6. 获取结果

      Copyboolean r = result.get();
      
    7. 关闭服务

      Copyservice.shutdownNow();
      
    Copypublic class TestCallable implements Callable<Boolean> {
        
        @Override
        public Boolean call() {
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in sub thread is " + i);
            }
            return true;
        }
        
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            TestCallable thread = new TestCallable();
            ExecutorService service = Executors.newFixedThreadPool(1);
            Future<Boolean> result = service.submit(thread);
            boolean r = result.get();
            service.shutdownNow();
            
            for (int i = 0; i < 20; i++) {
                System.out.println("the number in main thread is " + i);
            }
        }
    }
    

    3 线程状态

    img

    3.1 停止线程

    • 不推荐使用jdk提供的stop(),destroy()方法
    • 推荐线程自己停止
      • 使用一个标志位进行终止变量,当flag=false时,则终止线程运行
    Copypublic class TestStop implements Runnable {
        
        //设置一个标志位
        private boolean flag = true;
        
        @Override
        public void run() {
            int i = 0;
            while (flag) {
                System.out.println("thread is running... i is " + i++);
            }
        }
        
        //设置公开的方法停止线程
        public void stop() {
            flag = false;
        }
        
        public static void main(String[] args) {
            TestStop testStop = new TestStop();
            new Thread(testStop).start();
            for (int i = 0; i < 100; i++) {
                System.out.println("main thread..." + i);
                if (i == 90) {
                    testStop.stop();
                    System.out.println("thread stop");w
                }
            }
        }
    }
    

    3.2 线程休眠

    • sleep方法指定当前线程阻塞的毫秒数
    • sleep存在异常InterruptedException
    • sleep时间达到后线程进入就绪状态
    • 每个对象都有一个锁,sleep不会释放锁
    Copypublic class TestSleep implements Runnable {
            
        private int ticketNum = 10;
        
        @Override
        public void run() {
            while (true) {
                if (ticketNum <= 0)
                    break;
                //模拟延时
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
            }
        }
        
        public static void main(String[] args) {
            TestThread4 thread = new TestThread4();
            new Thread(thread, "test1").start();
            new Thread(thread, "test2").start();
            new Thread(thread, "test3").start();
        }
    }
    

    模拟网络延时可以放大问题的发生性

    Copy//模拟倒计时
    public class CountDown {
        public static void countDown() throws InterruptedException {
            int num = 10;
            while (true) {
                Thread.sleep(1000);
                System.out.println(num--);
                if (num <= 0) {
                    break;
                }
            }
        }
        
        pubilc static void main(String[] args) {
            try {
                countDown();
            } catch {
                e.printStackTrace();
            }
        }
    }
    

    3.3 线程yield

    • yield让当前正在执行的线程暂停,但不阻塞
    • 将线程从运行状态转为就绪状态
    • 让CPU重新调度,yield不一定成功
    Copypublic class TestYield {
        public static void main(String[] args) {
            new Thread(new MyThread(), "a").start();
            new Thread(new MyThread(), "b").start();
        }
    }
    
    class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "线程开始执行");
            Thread.yield();
            System.out.println("线程停止执行");
        }
    }
    

    3.4 线程强制执行

    • join合并线程,其他线程阻塞,待此线程执行完成后,再执行其他线程,相当于插队
    Copypublic class TestJoin implements Runnable {
        
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("sub thread is running..." + i);
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new TestJoin());
            thread.start();
            
            
            //main thread
            for (int i = 0; i < 100; i++) {
                if (i == 20) {
                    thread.join();
                }
                System.out.println("main thread is running..." + i);
            }
        }
    }
    

    3.5 观测线程状态

    • Thread.State
      • NEW:尚未启动的线程处于此状态
      • RUNNABLE:在Java虚拟机中执行的线程处于此状态
      • BLOCKED:被阻塞等待监视器锁定的线程处于此状态
      • WAITING:正在等待另一个线程执行特定动作的线程处于此状态
      • TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
      • TERMINATED:已退出的线程处于此状态
    Copypublic class TestState {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(() -> {
                try {
                    for (int i = 0; i < 5; i++) {
                    	Thread.sleep(1000);
                	}
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("sub thread is running...");
            });
            
            //观察状态
            Thread.State state = thread.getState();
            System.out.println(state);
            
            //观察启动后状态
            thread.start();
            state = thread.getState();
            System.out.println(state);
            
            while (state != Thread.State.TERMINATED) {
                Thread.sleep(100);
                state = thread.getState();
                System.out.println(stated);
            }
        }
    }
    

    3.6 线程优先级

    线程调度器监控程序中启动后进入就绪状态的所有线程,按照优先级决定应该调度哪个线程来执行

    线程的优先级用数字表示,范围从1到10,数字越大代表优先级越高,通过getPriority()和setPriority(int)来获取和改变优先级

    优先级的设定一定要在线程启动之前

    此外,线程优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用,得看CPU的调度

    Copypublic class TestPriority {
        public static void main(String[] args) {
            //主线程优先级
            System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
            
            Thread t1 = new Thread(new MyThread());
            Thread t2 = new Thread(new MyThread());
            Thread t3 = new Thread(new MyThread());
            Thread t4 = new Thread(new MyThread());
            Thread t5 = new Thread(new MyThread());
            Thread t6 = new Thread(new MyThread());
            
            //设置优先级
            t1.start();
            t2.setPriority(1);
            t2.start();
            t3.setPriority(4);
            t3.start();
            t4.setPriority(Thread.MAX_PRIORITY);
            t4.start();
            t5.setPriority(-1);	//报错
            t5.start();
            t6.setPriority(11);	//报错
            t6.start();
        }
    }
    
    class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
        }
    }
    

    3.7 守护(daemon)线程

    线程分为用户线程和守护线程,守护线程如后台记录操作日志,监控内存,垃圾回收等

    虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕

    Copypublic class TestDaemon {
        public static void main(String[] args) {
            Thread thread = new Thread(new MonitorThread);
            //默认为false表示用户线程
            thread.setDaemon(true);
            thread.start();
            
            new Thread(new MyThread()).start();
        }
    }
    
    class MyThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("sub thread is running...");
            }
            System.out.println("over");
        }
    }
    
    class MonitorThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                System.out.println("monitor is running...");
            }
        }
    }
    

    结果显示monitor线程会在mythread线程执行完毕后再执行一段时间,最终停止

    4 线程同步

    线程同步发生在多个线程操作同一个资源,也就是并发的情况下

    线程同步其实是一种等待机制,多个需要同时访问对象的线程进入此对象的等待池形成队列,等待前面线程使用完毕。

    4.1 锁机制

    为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可

    但也存在问题

    • 一个线程持有锁会导致其他所有需要此锁的线程挂起
    • 在多线程竞争下,加锁释放锁会导致比较多的上下文切换和调度延时,引发性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引发性能问题

    4.2 线程不安全案例

    Copypublic class UnsafeBuyTicket {
        public static void main(String[] args) {
            new Thread(new BuyTicket(), "test1").start();
            new Thread(new BuyTicket(), "test2").start();
            new Thread(new BuyTicket(), "test3").start();
        }
    }
    
    class BuyTicket implements Runnable {
        
        private int ticketNum = 10;
        private boolean flag = true;
        
        private void buy() throws InterruptedException {
            if (ticketNum <= 0) {
                flag = false;
                return;
            }
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "get " + ticketNum--);
        }
        
        @Override
        public void run() {
            while (flag) {
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    Copypublic class UnsafeBank {
        public static void main(String[] args) {
            Account account = new Account("testAccount", 100);
            Withdraw withdraw1 = new Withdraw(account, 50, "test1");
            Withdraw withdraw2 = new Withdraw(account, 100, "test2");
            
            withdraw1.start();
            withdraw2.start();
        }
    }
    
    class Account {
        String name;
        int money;
        
        public Account(String name, int money) {
            this.name = name;
            this.money = money;
        }
    }
    
    class Withdraw extends Thread {
        Account account;
        int withdrawMoney;
        int moneyInHand;
        
        public Withdraw(Account account, int withdrawMoney, String name) {
            super(name);
            this.account = account;
            this.withdrawMoney = withdrawMoney;
        }
        
        @Override
        public void run() {
            if (account.money < withdrawMoney) {
                System.out.println(Thread.currentThread().getName() + "no enough money");
                return;
            }
            
            //sleep增加问题发生的可能性
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            account.money -= withdrawMoney;
            moneyInHand += withdrawMoney;
            System.out.println(account.name + "余额为:" + account.money);
            System.out.println(Thread.currentThread().getName() + "手中的钱:" + moneyInHand);
        }
    }
    Copypublic class UnsafeList {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    list.add(Thread.currnetThread().getName());
                }).start();
            }
            
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    }
    

    4.2 同步方法

    通过private关键字保证数据对象只能被方法访问,使用synchronized关键字

    • synchronized方法
      • public synchronized void method(int args) {}
      • synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞。方法一旦执行,就独占该锁,直到方法返回才释放锁
      • 将一个大的方法声明为synchronized会影响效率
        • 方法内需要修改的资源才需要锁,锁的范围太大会影响执行速度
    • synchronized块
      • synchronized(Obj) {}
      • Obj为同步监视器
        • 可以是任何对象,但推荐使用共享资源作为同步监视器
        • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this
      • 执行过程和synchronized方法一样

    使用同步方法或者同步代码块修改不安全案例

    Copypublic class SafeBuyTicket {
        public static void main(String[] args) {
            new Thread(new BuyTicket(), "test1").start();
            new Thread(new BuyTicket(), "test2").start();
            new Thread(new BuyTicket(), "test3").start();
        }
    }
    
    class BuyTicket implements Runnable {
        
        private int ticketNum = 10;
        private boolean flag = true;
        
        //synchronized同步方法,锁的是this
        private synchronized void buy() throws InterruptedException {
            if (ticketNum <= 0) {
                flag = false;
                return;
            }
            Thread.sleep(100);
            System.out.println(Thread.currentThread().getName() + "get " + ticketNum--);
        }
        
        @Override
        public void run() {
            while (flag) {
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    Copypublic class SafeBank {
        public static void main(String[] args) {
            Account account = new Account("testAccount", 100);
            Withdraw withdraw1 = new Withdraw(account, 50, "test1");
            Withdraw withdraw2 = new Withdraw(account, 100, "test2");
            
            withdraw1.start();
            withdraw2.start();
        }
    }
    
    class Account {
        String name;
        int money;
        
        public Account(String name, int money) {
            this.name = name;
            this.money = money;
        }
    }
    
    class Withdraw extends Thread {
        Account account;
        int withdrawMoney;
        int moneyInHand;
        
        public Withdraw(Account account, int withdrawMoney, String name) {
            super(name);
            this.account = account;
            this.withdrawMoney = withdrawMoney;
        }
        
        @Override
        public void run() {
            //锁的是共享资源account
            //如果synchronized加载run方法上,那么锁的是this,也就是Withdraw对象,但这个不是共享资源
            synchronized (account) {
                if (account.money < withdrawMoney) {
                    System.out.println(Thread.currentThread().getName() + "no enough money");
                    return;
                }
            
                //sleep增加问题发生的可能性
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            
                account.money -= withdrawMoney;
                moneyInHand += withdrawMoney;
                System.out.println(account.name + "余额为:" + account.money);
                System.out.println(Thread.currentThread().getName() + "手中的钱:" + moneyInHand);
            }
        }
    }
    Copypublic class UnsafeList {
        public static void main(String[] args) {
            List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    synchronized (list) {
                        list.add(Thread.currnetThread().getName());
                    }
                }).start();
            }
            
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    }
    

    对于第三个案例,我们还可以使用JUC安全类型内的集合OnWriteArrayList

    Copypublic class TestJUC {
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    list.add(Thread.currentThread().getName());
                }).start();
            }
            
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
        }
    }
    

    4.3 死锁

    多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,导致两个或多个线程都在等待对方释放资源,都停止执行的情形

    一个同步块同时拥有两个以上对象的锁时,就有可能会发生死锁问题

    Copypublic class TestDeadlock {
        public static void main(String[] args) {
            MyThread t1 = new MyThread("test1", 0);
            MyThread t2 = new MyThread("test2", 1);
            
            t1.start();
            t2.start();
        }
    }
    
    class A {
        
    }
    
    class B {
        
    }
    
    class MyThread extends Thread {
        //用static来保证资源只有一份
        static A a = new A();
        static B b = new B();
        
        String name;
        int choice;
        
        public MyThread(String name, int choice) {
            this.name = name;
            this.choice = choice;
        }
        
        @Override
        public void run() {
            try {
                do();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        private void do() throws InterruptedException {
            if (choice == 0) {
                synchronized (a) {
                    System.out.println(name + "get a");
                    Thread.sleep(1000);
                    synchronized (b) {
                        System.out.println(name + "get b");
                    }
                }
            } else {
                synchronized (b) {
                    System.out.println(name + "get b");
                    Thread.sleep(2000);
                    synchronized (a) {
                        System.out.println(name + "get a");
                    }
                }
            }
        }
    }
    

    程序会产生死锁,因为一个同步块同时拥有了两个锁a和b,而且a和b的资源只有一份(用static修饰)。如果把程序修改一下,就不会死锁

    Copyprivate void do() throws InterruptedException {
            if (choice == 0) {
                synchronized (a) {
                    System.out.println(name + "get a");
                    Thread.sleep(1000);
                }
                synchronized (b) {
                    System.out.println(name + "get b");
                }
            } else {
                synchronized (b) {
                    System.out.println(name + "get b");
                    Thread.sleep(2000);
                }
                synchronized (a) {
                    System.out.println(name + "get a");
                }
            }
        }
    

    死锁产生的必要条件

    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不妨
    • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

    只要想办法打破其中任一条件,就可以避免死锁的发生

    4.4 Lock

    synchronized关键字是隐式定义锁来实现同步,而Lock对象显示定义同步锁实现同步

    Lock接口位于JUC包,是多个线程对共享资源进行访问的工具,作用和synchronized一样

    • ReentrantLock 可重入锁
      • 实现了Lock
      • 与synchronized有相同的并发性和内存语义
      • 可以显示加锁和释放锁
      • 可以使用tryLock()方法尝试获得锁,如果无法获得,程序不会无限等待下去,而是做一些额外处理
    Copypublic class TestLock {
        public static void main(String[] args) {
            MyThread thread = new MyThread();
            
            new Thread(thread).start();
            new Thread(thread).start();
            new Thread(thread).start();
        }
    }
    
    class MyThread implements Runnable {
        
        int ticketNum = 10;
        
        private final ReentrantLock lock = new ReentrantLock();
        
        @Override
        public void run() {
            while (true) {
                //加锁
                lock.lock();
                try {
                    if (ticketNum <= 0) {
                        break;
                    }
                
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                } finally {
                    //释放锁
                    lock.unlock();
                }
                
            }
        }
    }
    

    Lock和synchronized对比

    • Lock是显示锁,需要手动开启和关闭,synchronized是隐式锁,出了作用域自动释放
    • Lock只有代码块锁,synchronized有代码块锁和方法锁
    • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好
    • 优先使用顺序:Lock > 同步代码块 > 同步方法

    5 线程协作

    5.1 线程通信

    Java提供了几个方法解决线程通信的问题

    • wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
    • wait(long timeout):指定等待的毫秒数
    • notify():唤醒一个处于等待状态的线程
    • notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的优先调度

    这几个方法都是Object类的方法,都只能在同步方法或同步代码块中使用,否则会抛出异常

    5.2 生产者消费者问题

    解决方法一:管程法

    生产者将生产好的数据放入缓冲区,消费者从缓冲区获取数据

    Copypublic class TestPC {
        public static void main(String[] args) {
            SynBuffer buffer = new SynBuffer();
            
            new Producer(buffer).start();
            new Consumer(buffer).start();
        }
    }
    
    class Producer extends Thread {
        SynBuffer buffer;
        
        public Producer(SynBuffer buffer) {
            this.buffer = buffer;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                buffer.push(new Product(i));
                System.out.println("生产了 " + i + "个产品");
            }
        }
    }
    
    class Consumer extends Thread {
        SynBuffer buffer;
        
        public Consumer(SynBuffer buffer) {
            this.buffer = buffer;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("消费了 " + buffer.pop().id + "个产品");
            }
        }
    }
    
    class Product {
        int id;
        
        public Product(int id) {
            this.id = id;
        }
    }
    
    class SynBuffer {
        Product[] products = new Product[10];
        int count = 0;
        
        public synchronized void push(Product product) {
            if (count == products.length) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            products[count] = product;
            count++;
            this.notifyAll();
        }
        
        public synchronized Product pop() {
            if (count == 0) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            count--;
            Product product = products[count];
            this.notifyAll();
            return product;
        }
    }
    

    解决方法二:信号灯法

    Copypublic class TestPC {
        public static void main(String[] args) {
            Show show = new Show();
            
            new Actor(show).start();
            new Audience(show).start();
        }
    }
    
    class Actor extends Thread {
        Show show;
        
        public Actor(Show show) {
            this.show = show;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                if (i % 2 == 0) {
                    show.play("show1");
                } else {
                    show.play("show2");
                }
            }
        }
    }
    
    class Audience extends Thread {
        Show show;
        
        public Audience(Show show) {
            this.show = show;
        }
        
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                show.watch();
            }
        }
    }
    
    class Show {
        String item;
        boolean flag = true;
        
        public synchronized void play(String item) {
            if (!flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("演员表演了" + item);
            
            this.notifyAll();
            this.item = item;
            this.flag = !this.flag;
        }
        
        public synchronized void watch() {
            if (flag) {
                try {
                    this.wait();
                } catch (IterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("观看了" + item);
            
            this.notifyAll();
            this.flag = !this.flag;
        }
    }
    

    6 线程池

    经常创建和销毁使用量特别大的资源对性能影响很大,一般要使用线程池,提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,避免频繁创建销毁,可以重复利用

    6.1 使用线程池

    线程池相关的API:ExecutorService和Executors

    • ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
      • void execute(Runnable command):执行任务/命令。无返回值,一般用来执行Runnable
      • Future submit(Callable tast):执行任务,有返回值,一般用来执行Callable
      • void shutdown():关闭连接池
    • Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
    Copypublic class TestPool {
        public static void main(String[] args) {
            //创建服务,创建线程池
            ExecutorService service = Executors.newFixedThreadPool(10);
            
            //执行
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            
            //关闭连接
            service.shutdown();
    }
    
    class MyThread implements Runnbale {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
    
  • 相关阅读:
    javaweb学习总结(四十五)——监听器(Listener)学习二
    javaweb学习总结(四十四)——监听器(Listener)学习
    javaweb学习总结(四十三)——Filter高级开发
    javaweb学习总结(四十二)——Filter(过滤器)学习
    javaweb学习总结(四十一)——Apache的DBUtils框架学习
    javaweb学习总结(四十)——编写自己的JDBC框架
    javaweb学习总结(三十九)——数据库连接池
    javaweb学习总结(三十八)——事务
    javaweb学习总结(三十七)——获得MySQL数据库自动生成的主键
    javaweb学习总结(三十六)——使用JDBC进行批处理
  • 原文地址:https://www.cnblogs.com/wtao0730/p/14373394.html
Copyright © 2020-2023  润新知