Java第三阶段学习(七、线程池、多线程)
一、线程池
1.概念:
线程池,其实就是一个容纳多个线程的容器,其中的线程可以重复使用,省去了频繁创建线程对象的过程,无需反复创建线程而消耗过多资源,是JDK1.5以后出现的。
2.使用线程池的方式----Runnable接口
线程池是由线程池工厂创建的,再调用线程池中的方法调用线程,再通过线程去执行任务方法。
构造代码:2.1 Executors:线程池创建工厂类
2.2 ExecutorService:线程池类 Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行
2.3 Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
创建线程池的步骤:
1.创建线程池对象
2.创建Runnable接口子类对象
3.提交Runnable接口子类对象
4.关闭线程池
Runnable接口实现类:
package com.oracle.Demo01; public class MyRunnable implements Runnable{ //Runnable接口实现类,并且有线程任务 @Override public void run() { for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } }
线程池代码:
package com.oracle.Demo01; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //Executor创建线程池方法 public class Demo01 { public static void main(String[] args) { // 1.通过线程池工厂 获得线程池对象 ExecutorService es=Executors.newFixedThreadPool(5); //2.获得线程对象并提交 es.submit(new MyRunnable()); es.submit(new MyRunnable()); //销毁线程池:执行完毕后,销毁线程池,线程也就进入死亡状态了 //若不销毁,线程运行完毕又回到线程池,回到新建状态,没有进入死亡状态 es.shutdown(); } }
3.使用线程池方法-----Callable接口
构造代码:3.1 Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。
3.2 ExecutorService:线程池类
<T> Future<T> submit(Callable<T> task):获取线程池中的某一个线程对象,并执行线程中的call()方法
3.3 Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程的步骤:
1.创建线程池对象
2.创建Callable接口子类对象
3.提交Callable接口子类对象
4.摧毁线程池
Callable接口实现类代码:
package com.oracle.Demo01; import java.util.concurrent.Callable; //创建Callable接口的子类,并且有线程任务 public class MyCcallable implements Callable<String>{ @Override public String call() throws Exception { System.out.println("Call方法"); return "abc"; } }
线程池运行代码:
package com.oracle.Demo01; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; //使用Callable方法使用线程池的线程 public class Demo02 { public static void main(String[] args) throws InterruptedException, ExecutionException { // TODO Auto-generated method stub ExecutorService es=Executors.newFixedThreadPool(3); Future<String> f=es.submit(new MyCcallable()); String s=f.get(); System.out.println(s); } }
二、多线程
1.线程安全:
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
比如电脑端,手机端,跟售票窗口同时卖一张票,因为是多线程所以是同时进行的,当一个线程买到票后,可能另外两个线程也进行到一半了,但是因为已经没有票了,所以就会出现异常错误,比如重复的票号或异常的票号,这就是出现了线程安全问题。
2.线程同步:
Java中提供了线程同步机制,来解决线程安全的问题。
线程同步的方式有两种:
1.同步代码块
2.同步方法
2.1同步代码块:
在代码块声明上,加上synchronized
synchronized (锁对象) { 可能会产生线程安全问题的代码 }
同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。
多线程代码:
package com.oracle.Demo02; public class Demo { public static void main(String[] args) { Tickets t=new Tickets(); Thread t0=new Thread(t); Thread t1=new Thread(t); Thread t2=new Thread(t); t0.start(); t1.start(); t2.start(); } }
使用同步代码块:
package com.oracle.Demo02; //卖票任务 //synchronized(任意对象){ // 线程要操作的共享数据 //} //同步代码块解决线程不安全的原理: // 线程遇到同步代码块时,线程会先判断你的同步锁有没有,如果有,就获取同步锁,进入同步去执行代码,执行完毕后, //再把锁还回去。 // 在同步中,线程进行了休眠,此时另一个线程会执行,遇到同步代码块的时候,会判断同步锁有没有,如果没有, //没有获取到同步锁的线程,不能进入同步去执行,被阻挡在同步代码块外面 public class Tickets implements Runnable{ private int ticket=100; //private Object obj=new Object(); public void run() { while(true){ synchronized (this) { //这里的this 可以为任意对象,替换成上面的obj也可以,为自身对象this也可以 if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票"); } } } } }
2.2 同步方法
在方法声明上,加上synchronized
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步方法中的锁对象是 this
多线程代码:
package com.oracle.Demo03; public class Demo { public static void main(String[] args) { Tickets t=new Tickets(); Thread t0=new Thread(t); Thread t1=new Thread(t); Thread t2=new Thread(t); t0.start(); t1.start(); t2.start(); } }
使用同步代码块:
package com.oracle.Demo03; public class Tickets implements Runnable{ private int ticket=100; public void run() { while(true){ method(); } } //同步方法:解决线程不安全问题 //问题1:同步方法有锁吗?有,锁是本类引用,this关键字 //问题2:如果你的同步方法是静态的,还有同步锁吗?是this吗? //有,不是this,是本类自己,Tickets.class public synchronized void method(){ if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票"); }else{ return; } } }
静态同步方法: 在方法声明上加上static synchronized
public static synchronized void method(){ 可能会产生线程安全问题的代码 }
使用同步方法:
package com.oracle.Demo03; public class Tickets implements Runnable{ private int ticket=100; public void run() { while(true){ method(); } } //同步方法:解决线程不安全问题 //问题1:同步方法有锁吗?有,锁是本类引用,this关键字 //问题2:如果你的同步方法是静态的,还有同步锁吗?是this吗? //有,不是this,是本类自己,Tickets.class public synchronized void method(){ if(ticket>0){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票"); }else{ return; } } }
2.3死锁
同步锁使用的弊端:当线程任务中出现了多个同步锁(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
synchronzied(A锁){ synchronized(B锁){ } }
所以,不能在一个同步中再嵌套另一个同步
2.4 Lock接口
Lock
实现类提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。
常用方法:
Lock提供了一个更加面对对象的锁,在该锁中提供了更多的操作锁的功能。
package com.oracle.Demo04; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //Lock接口 public class Tickets implements Runnable{ private int ticket=100; private Lock lock=new ReentrantLock(); //创建一个锁的对象 public void run() { while(true){ lock.lock(); //提供锁 if(ticket>0){ try { Thread.sleep(10); System.out.println(Thread.currentThread().getName()+"出售第"+(ticket--)+"张票"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ lock.unlock(); //执行完毕释放锁 } } } } }
2.5等待唤醒机制
线程之间的通信:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制的方法:
wait():等待。将正在执行的线程释放其执行资格 和 执行权,并存储到线程池中。
notify():唤醒。唤醒线程池中被wait()的线程,一次只能唤醒一个,而且是随机的任意线程。
notifyAll():唤醒全部。可以将线程池中所有被wait()的线程唤醒。
所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁,
这样才可以明确出这些方法操作的到底是哪个锁上的线程。
等待唤醒示例图:
自定义实体类:
package com.oracle.Demo06; //等待唤醒机制 自定义实体类 public class Resource { public String name; public String sex; public boolean flag=false; }
线程类:
package com.oracle.Demo06; public class Input implements Runnable{ private Resource r; public Input(Resource r){ this.r=r; } public void run() { int i=0; while(true){ synchronized(r){ if(r.flag){ try { r.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } if(i%2==0){ r.name="张三"; r.sex="男"; }else{ r.name="lisi"; r.sex="nv"; } i++; } r.flag=true; r.notify(); } } } }
package com.oracle.Demo06; public class Output implements Runnable{ private Resource r; public Output(Resource r){ this.r=r; } //flag:当flag为true的时候,代表赋值完成 //为false,获取值完成 public void run() { while(true){ synchronized (r) { if(!r.flag){ try { r.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(r.name+"..."+r.sex); r.flag=false; r.notify(); } } } }
测试类:
package com.oracle.Demo06; public class Output implements Runnable{ private Resource r; public Output(Resource r){ this.r=r; } //flag:当flag为true的时候,代表赋值完成 //为false,获取值完成 public void run() { while(true){ synchronized (r) { if(!r.flag){ try { r.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println(r.name+"..."+r.sex); r.flag=false; r.notify(); } } } }