线程池
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。线程池主要用来解决线程生命周期开销问题和资源不足问题。
使用线程池方式--Runnable接口
通常,线程池都是通过线程池工厂创建,再调用线程池中的方法获取线程,再通过线程去执行任务方法。
Executors:线程池创建工厂类 public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象 ExecutorService:线程池类 Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行 Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程对象的步骤:
1. 创建线程池对象
2. 创建Runnable接口子类对象
3. 提交Runnable接口子类对象
4. 关闭线程池
package com.oracle.xiancheng; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Xiancheng { public static void main(String[] args) { //创建线程池对象(从线程池工厂中获得) ExecutorService ec=Executors.newFixedThreadPool(2); //创建Runnable接口子类实例对象(创建任务对象) MyRunnable r = new MyRunnable(); // 提交Runnable接口子类对象 //线程池会选一条线程执行提交的任务 ec.submit(r); //注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。 //又将使用完的线程又归还到了线程池中 for(int i=0;i<50;i++){ System.out.println("main..."+i); } ec.shutdown();//关闭线程池 } }
Runnable接口实现类
package com.oracle.xiancheng; public class MyRunnable implements Runnable { public void run() { for(int i=0;i<50;i++){ System.out.println("runable..."+i); } } }
使用线程池方式—Callable接口
Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。 ExecutorService:线程池类 <T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法 Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程对象的步骤:
1. 创建线程池对象
2. 创建Callable接口子类对象
3. 提交Callable接口子类对象
4. 关闭线程池
Callable接口实现类,call方法可抛出异常、返回线程任务执行完毕后的结果
package com.oracle.xiancheng; import java.util.concurrent.Callable; //Callable泛型就是你call的返回值类型 public class MyCallable implements Callable<String> { public String call() throws Exception { return "这是call方法"; } }
测试类:
package com.oracle.xiancheng; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class demo01 { public static void main(String[] args) throws InterruptedException, ExecutionException { //获取线程池 ExecutorService ec=Executors.newFixedThreadPool(2); //创建callable子类 MyCallable mc=new MyCallable(); //提交Callable子类 Future<String> f=ec.submit(mc); String str=f.get(); System.out.println(str); //关闭线程池 ec.shutdown(); } }
异步计算0-100的和跟0-200的和
package com.oracle.xiancheng; import java.util.concurrent.Callable; public class JiSuan implements Callable<Integer>{ private Integer a; public JiSuan(Integer a){ this.a=a; } public Integer call(){ int sum=0; for(int i=0;i<=a;i++){ sum +=i; } System.out.println(sum); return sum; } }
测试类:
package com.oracle.xiancheng; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Yibu { //异步计算0-100的和跟0-200的和 public static void main(String[] args) throws InterruptedException, ExecutionException { //获取线程池 ExecutorService ec=Executors.newFixedThreadPool(2); //创建callable子类 JiSuan js=new JiSuan(100); JiSuan js1=new JiSuan(200); //提交callable子类 Future<Integer> f1=ec.submit(js); Future<Integer> f2=ec.submit(js1); /*//从future中获取返回值 System.out.println(f1.get()); System.out.println(f2.get());*/ //关闭 ec.shutdown(); } }
多线程
线程安全
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “功夫熊猫3”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “功夫熊猫3”这场电影票(多个窗口一起卖这100张票)
需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
模拟票:
public class Ticket implements Runnable { private int tickets=100; public void run(){
//模拟卖票 while(true){ if(tickets>0){
//模拟选座 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"张票"); } } } }
测试类;
package com.oracle.demo01; public class demo01 { public static void main(String[] args) { Ticket t=new Ticket();//创建票对象 Thread t1=new Thread(t); Thread t2=new Thread(t); Thread t3=new Thread(t); t1.start(); t2.start(); t3.start(); } }
结果会出现重复的票,还有错误的票,0和-1问题
线程同步(线程安全处理Synchronized)
java中提供了线程同步机制,它能够解决上述的线程安全问题。
线程同步的方式有两种:
方式1:同步代码块
方式2:同步方法
同步代码块
//同步代码块: 在代码块声明上 加上synchronized synchronized (锁对象) { //可能会产生线程安全问题的代码 }
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
使用同步代码块,对电影院卖票案例中Ticket类进行如下代码修改:
模拟出票
public class Ticket implements Runnable { private int tickets=100; private Object lock = new Object(); public void run(){ while(true){ synchronized (lock) { if(tickets>0){ try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"张票"); }else{ break; } } } }
同步方法
//同步方法:在方法声明上加上synchronized public synchronized void method(){ //可能会产生线程安全问题的代码 }
同步方法中的锁对象是 this
使用同步方法,对电影院卖票案例中Ticket类进行如下代码修改:
package com.oracle.demo01; public class Ticket implements Runnable { private int tickets=100; private Object lock = new Object(); public void run() { //模拟卖票 while(true){ //同步方法 method(); } } //同步方法,锁对象this public synchronized void method(){ if (tickets> 0) { //模拟选坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"张票"); } } }
//静态同步方法: 在方法声明上加上static synchronized public static synchronized void method(){ //可能会产生线程安全问题的代码 }
静态同步方法中的锁对象是本类字节码对象 类名.class
同步锁:对象锁,对象监视器
同步是怎么保证安全性?
因为同步中有锁,没有锁的线程,不能执行,只能等。
StringBuilder比StringBuffer快
原因:StringBuffer有同步关键字synchronized,安全性比StringBuilder高,但速度慢
死锁
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
格式如:
synchronzied(A锁){ synchronized(B锁){ } }
定义锁对象类:
package com.oracle.demo01; public class LockA { public final static LockA locka=new LockA(); private LockA(){ } }
package com.oracle.demo01; public class LockB { public final static LockB lockb=new LockB(); private LockB(){ } }
线程任务类:
package com.oracle.demo01; public class DeadLock implements Runnable { private int i=0; public void run() { while(true){ if(i%2==0){ //情况一 synchronized (LockA.locka){ System.out.println("IF......lockA"); synchronized (LockB.lockb){ System.out.println("IF......lockB"); } } }else{ //情况二 synchronized (LockB.lockb){ System.out.println("else......lockB"); synchronized (LockA.locka){ System.out.println("else......lockA"); } } } i++; } } }
测试类:
package com.oracle.demo01; public class TestDEADlock { public static void main(String[] args) { //创建线程任务类对象 DeadLock dl=new DeadLock(); //创建两个线程 Thread t1=new Thread(dl); Thread t2=new Thread(dl); //开启线程 t1.start(); t2.start(); } }
Lock接口
使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,对电影院卖票案例中Ticket类进行如下代码修改:
package com.oracle.demo01; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class NewTicket implements Runnable{ private int tickets=100; //创建Lock锁对象 private Lock ck = new ReentrantLock(); public void run(){ while(true){ ck.lock(); if(tickets>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"出售第"+tickets--+"张票"); } catch (InterruptedException e) { e.printStackTrace(); }finally{ ck.unlock();//释放锁 } } } } }