java多线程(三) 线程通信与线程池

java多线程(三) 线程通信与线程池

java多线程(一) 基础理论与执行状态
java多线程(二) 控制线程与线程同步
java多线程(三) 线程通信与线程池

*线程通信

wait() notify() notiryAll() 这三个方法是Object类的方法,只能在隐式锁中使用,sleep(),yeild(),join()是Thread类的方法

wait()

wait()线程等待,并且释放锁定,等待notify()或者notifyAll()的唤醒,如果wait中添加时间(毫秒) 等到时间后会自动唤醒.与sleep()的区别是,sleep并不释放锁.
需要注意的一点是,调用wait()的必须是锁对象,不然会报java.lang.IllegalMonitorStateException异常.

public class MyThread1 extends Thread {
    private String s = "aaa";
    @Override
    public void run() {
        for (int i = 0; i < 2; i++) {
            test();
        }
    }

    public void test() {
        synchronized (s) {
            System.out.println(getName() + 1);
            try {
                sleep(10);
             // s.wait(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + 2);
        }
    }

    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        myThread1.setName("ThreadA");
        MyThread1 myThread2 = new MyThread1();
        myThread2.setName("ThreadB");
        myThread1.start();
        myThread2.start();
    }
}

不加s.wait(10)运行结果:
ThreadA1
ThreadA2
ThreadB1
ThreadB2
ThreadB1
ThreadB2
ThreadA1
ThreadA2

加了s.wait(10)运行结果:
ThreadA1
ThreadB1
ThreadA2
ThreadA1
ThreadB2
ThreadB1
ThreadA2
ThreadB2

当A打印了"1"后,释放了锁,这时候只有B在等待获取锁,所以线程B得到了锁,打印了"1",然后线程B又释放了锁.A获得了锁,打印了2…
从上面结果可以看出,加了wait(10) 后,在这10毫秒时间中,获得锁的线程释放了锁.10毫秒后等待获取锁

notify()与notifyAll()

notify与notifyAll()是唤醒wait()中的线程,区别在于,notify()只会唤醒随机一个线程等待获取当前锁的线程.另外也必须用锁对象调用

public class Account {
    private Integer money = 0;

    public Integer getMoney() { return money; }

    public void setMoney(Integer money) { this.money = money; }

}

public class MyThread2 extends Thread{
    private Account account;

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

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            test();
        }
    }

    public void test() {
        synchronized (account) {
            Integer money = account.getMoney();
            if (account.getMoney()%2==0){
                try {
                    account.setMoney(money+1);
                    System.out.println(getName()+" if");
                    account.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }else{
                account.setMoney(money+1);
                System.out.println(getName()+" else");
                account.notify();
              //account.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        Account account = new Account();
        MyThread2 myThread1 = new MyThread2(account);
        myThread1.setName("ThreadA");
        MyThread2 myThread2 = new MyThread2(account);
        myThread2.setName("ThreadB");
        myThread1.start();
        myThread2.start();
    }
}

运行结果之一:
ThreadA if
ThreadB else
ThreadB if
ThreadA else
ThreadA if
ThreadB else
ThreadB if
ThreadA else
ThreadA if
ThreadB else

A线程获得锁,money=0,进入if,money=1 然后wait()释放了锁并等待被唤醒,B获得锁 money=1 进入else,唤醒A线程,A等待获取锁,B执行完test方法后释放锁进入下次循环,A跟B同时竞争锁…
上方结果看来是B执行完test后,又获得执行权,这个是假象,多次运行结果后,会发现,进入else的线程释放了锁以后,A跟B其实是共同抢锁的.

Condition

上述的只能在隐式锁(synchronized)中使用,如果要用显示锁(Lock)中使用,需要用Condition对象,与wait(),notify(),notifyAll() 对应的是Condition对象中的await(),signal(),signalAll()方法,Condition对象是通过Lock实例.newCondition()获取的.

public class Account {
    private Integer money = 0;

    public Integer getMoney() { return money; }

    public void setMoney(Integer money) { this.money = money; }

}

public class MyThread3 extends Thread {
    private ReentrantLock reentrantLock;
    private Condition condition ;
    private Account account;

    public MyThread3(Account account,ReentrantLock reentrantLock,Condition condition) {
        this.account = account;
        this.reentrantLock=reentrantLock;
        this.condition = condition;
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
           test();
        }
    }

    public void test() {
        reentrantLock.lock();
        try {
            Integer money = account.getMoney();
            if (account.getMoney() % 2 == 0) {
                try {
                    account.setMoney(money + 1);
                    System.out.println(getName() + " if");
                    condition.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                account.setMoney(money + 1);
                System.out.println(getName() + " else");
                condition.signalAll();
            }
        } finally {
            reentrantLock.unlock();
        }
    }

    public static void main(String[] args) {
        Account account = new Account();
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();
        MyThread3 myThread1 = new MyThread3(account,reentrantLock,condition);
        myThread1.setName("ThreadA");
        MyThread3 myThread2 = new MyThread3(account,reentrantLock,condition);
        myThread2.setName("ThreadB");
        myThread1.start();
        myThread2.start();
    }
}
运行结果之一:
ThreadA if
ThreadB else
ThreadB if
ThreadA else
ThreadA if
ThreadB else
ThreadB if
ThreadA else
ThreadA if
ThreadB else

注意一点,跟显式锁中的锁对象概念一样,Lock/Condition对象必须是共享的,也就是所有的线程拿到的Lock/Condition对象必须是同一个.
另外,线程通信还有几种方式,使用阻塞队列,piped流等这里不说了.

*线程池

线程组: ThreadGroup:通过组方便操作一类线程,不赘述了.
线程池: 线程池并不是将线程放到线程池中,而是把tagert(实现Runnable的类和实现Callable类)放到线程池中,线程池中的线程执行完run或call方法之后不会死亡,而是继续执行下一个tagert,这样就不要每次创建线程,因为创建线程会消耗资源,所以线程池性能更好,并且可以控制并发数量,最大并发就是线程池中线程的个数.

常用线程池有下面几种:

Executors.newCachedThreadPool();创建具有缓存功能的线程池,最大数量Integer.MAX_VALUE,可以灵活回收线程池,如果线程空闲超过60秒会被回收,提交新的任务时没有空闲线程将会创建新的线程.
Executors.newFixedThreadPool(x);创建有x个线程的线程池,线程可重用,会一直占据资源.如果当前执行的任务等于线程个数,新任务将会添加到队列中等待执行
Executors.newScheduledThreadPool(x);创建有x个线程的线程池,,支持定时的以及周期性的任务执行,适合于时间延迟以及重复周期的任务
Executors.newSingleThreadExecutor; 相当于Executors.newScheduledThreadPool(1)

不同线程池API不做说明了

public class ThreadTarget implements Runnable { 
    //可以用lambda表达式创建target
    @Override
    public void run() {
        //省略过程
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(3);;
        ThreadTarget threadTarget1 = new ThreadTarget();
        ThreadTarget threadTarget2 = new ThreadTarget();
        threadPool.submit(threadTarget1);//提交target,线程池如果有空余线程,就会去执行这个target
        threadPool.submit(threadTarget2);
        threadPool.shutdown();//关闭线程池,线程池关闭前会执行完正在执行的线程
    }
}

ForkJoinPool

下面说一个特殊的线程池 ForkJoinPool, 适合于不知道具体需要多少任务数的情况.他可以把大任务拆分成很多的小任务去执行.需要传参ForkJoinTask(接口),它有两个抽象子类 RecursiveAction和RecursiveTask分别代表有返回值和无返回值的任务. 如果理解递归的概念,下面的代码可以很好的理解.
无返回值:卖票
卖票是一个大任务,如果规定每个人只能分小于等于100张票,那么我们可以根据总票数来决定多少人去执行小任务.

public class SellTicketTask extends RecursiveAction {
    private int startTicketNum; //票的起始编号
    private int endTicketNum;   //票的结束编号

    public SellTicketTask(int startTicketNum, int endTicketNum) {
        this.startTicketNum = startTicketNum;
        this.endTicketNum = endTicketNum;
    }

    @Override
    protected void compute() {
        if (endTicketNum - startTicketNum+1<=10) {
            for (int i = startTicketNum; i <= endTicketNum; i++) {
                System.out.println(Thread.currentThread().getName() + "卖出" + i + "号票");
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }else {
            SellTicketTask sellTicketTask1 = new SellTicketTask(startTicketNum, startTicketNum+10-1);
            SellTicketTask sellTicketTask2 = new SellTicketTask(startTicketNum+10, endTicketNum);
            sellTicketTask1.fork(); //fork是并行执行compute(),而直接调用compute就成了单线程了.
            sellTicketTask2.fork();
        }

    }
    public static void main(String[] args) throws InterruptedException {
        //构造参数可以设置并行度,但是如果高于电脑核数,只能并行执行电脑核数个任务
        ForkJoinPool forkJoinPool = new ForkJoinPool(3);
        SellTicketTask sellTicketTask = new SellTicketTask(0, 200);
        forkJoinPool.submit(sellTicketTask);
        //线程阻塞,等待所有任务完成
        forkJoinPool.awaitTermination(2, TimeUnit.SECONDS);
        forkJoinPool.shutdown();
    }
}
运行结果一部分:
ForkJoinPool-1-worker-1卖出200号票
ForkJoinPool-1-worker-2卖出0号票
ForkJoinPool-1-worker-3卖出10号票
ForkJoinPool-1-worker-1卖出190号票
ForkJoinPool-1-worker-3卖出11号票
ForkJoinPool-1-worker-2卖出1号票
ForkJoinPool-1-worker-1卖出191号票
ForkJoinPool-1-worker-3卖出12号票
ForkJoinPool-1-worker-2卖出2号票
......

上面可以看到有多个人在卖票,而且我们规定了每个人最多可以分到10张票,过意根据票编号入参的不同,任务数是不一样的.

有返回值: 累加
开始数,结束数,计算两个数中间的累加结果

public class AccumulationTask extends RecursiveTask<Integer> {
    private int startTicketNum;
    private int endTicketNum;

    public AccumulationTask(int startTicketNum, int endTicketNum) {
        this.startTicketNum = startTicketNum;
        this.endTicketNum = endTicketNum;
    }

    @Override
    protected Integer compute() {
        int accumulation = 0;
        if (endTicketNum - startTicketNum+1<=10) {
            for (int i = startTicketNum; i <= endTicketNum; i++) {
                accumulation+=i;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }else {
            AccumulationTask accumulationTask1 = new AccumulationTask(startTicketNum, startTicketNum+10-1);
            AccumulationTask accumulationTask2 = new AccumulationTask(startTicketNum+10, endTicketNum);
            accumulationTask1.fork();
            accumulationTask2.fork();
            //两个任务结果相加
            accumulation = accumulationTask1.join()+accumulationTask2.join();
        }
        return accumulation;
    }
    public static void main(String[] args) throws Exception {
        //构造参数可以设置并行度,但是如果高于电脑核数,只能并行执行电脑核数个任务
        ForkJoinPool forkJoinPool = new ForkJoinPool(3);
        AccumulationTask accumulationTask = new AccumulationTask(11, 100);
        ForkJoinTask<Integer> submit = forkJoinPool.submit(accumulationTask);
        System.out.println(submit.get());
        forkJoinPool.shutdown();
    }
}
运行结果:
4995

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值