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