Loading

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包

public 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()方法启动线程
public 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接口的做法,避免单继承的局限性,方便一个对象被多个线程使用

多个线程操作同一个对象

public 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对象可以用匿名内部类代替,比如

new Thread(testThread).start();

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

new 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以上

new 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. 创建执行服务

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

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

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

    service.shutdownNow();
    
public 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 线程状态

3.1 停止线程

  • 不推荐使用jdk提供的stop(),destroy()方法
  • 推荐线程自己停止
    • 使用一个标志位进行终止变量,当flag=false时,则终止线程运行
public 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不会释放锁
public 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();
    }
}

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

//模拟倒计时
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不一定成功
public 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合并线程,其他线程阻塞,待此线程执行完成后,再执行其他线程,相当于插队
public 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:已退出的线程处于此状态
public 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的调度

public 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)线程

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

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

public 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 线程不安全案例

public 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();
            }
        }
    }
}
public 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);
    }
}
public 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方法一样

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

public 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();
            }
        }
    }
}
public 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);
        }
    }
}
public 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安全类型内的集合CopyOnWriteArrayList

public 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 死锁

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

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

public 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修饰)。如果把程序修改一下,就不会死锁

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");
            }
        }
    }

死锁产生的必要条件

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

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

4.4 Lock

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

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

  • ReentrantLock 可重入锁
    • 实现了Lock
    • 与synchronized有相同的并发性和内存语义
    • 可以显示加锁和释放锁
    • 可以使用tryLock()方法尝试获得锁,如果无法获得,程序不会无限等待下去,而是做一些额外处理
public 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 生产者消费者问题

解决方法一:管程法

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

public 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;
    }
}

解决方法二:信号灯法

public 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
    • <T> Future<T> submit(Callable<T> tast):执行任务,有返回值,一般用来执行Callable
    • void shutdown():关闭连接池
  • Executors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
public 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);
    }
}
posted @ 2020-07-26 00:12  Kinopio  阅读(130)  评论(0编辑  收藏  举报