Java之多线程
1.基本概念
1.1程序、进程、线程的区分:
程序(program)
本质是一段静态的代码、静态对象。
进程(process)
程序的一次执行过程,或正在运行的一个程序,是动态的过程。
进程是资源分配的单位,系统在运行时会为每个分配不同的内存区域。
线程(thread)
线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC)。
一个进程中的多个线程共享相同的内存单元/内存地址空间。
一个进程中的线程可以访问相同的变量和对象。
注:Java的应用程序java.exe至少有三个线程:main主线程、gc垃圾回收线程、异常处理线程。
1.2并行与并发
并行:多个CPU同时执行多个任务
并发:一个CPU(分割时间片)同时执行多个任务
2.线程的创建与启动
2.1Thread类(java.lang.Thread)
构造器:
Thread():创建Thread对象
Thread(String threadname):创建进程并指定进程名称
Thread(Runable target):创建指定目标对象的进程,它实现了Runnable接口的run方法
Thrad(Runnable target,String name):创建指定对象的进程并指定名称
属性
static int MAX _PRIORITY 属性值:10 最大优先级
static int MIN _PRIORITY 属性值:1 最小优先级
static int NORM _PRIORITY 属性值:5 普通优先级
方法名
static Thread currentThread():返回当前线程
static void yield():线程让步,让步给优先级更高的线程
static void sleep(long mills):线程等待,但会抛出InterruptedException异常
void stop():已过时,强制停止当前线程
void run():需被子类重写的方法
void start():启动并调用run()方法
void join():在线程A中调用线程B的join(),则会阻塞线程A直到线程B执行完成
boolean isAlive():判断线程是否还在
boolean islnterruopted():判断线程是否被中断
int getPriority():返回线程优先级
viod setPriority(int priority):设置线程优先级
String getName():返回线程名称
void setName(String name):设置名称
2.3线程分类
Java中的线程分为用户线程和守护线程。main线程就是用户线程,gc线程就是守护线程,
守护线程会依赖用户线程而存在,当用户线程执行完毕时,守护线程也会停止执行。
用户线程可以通过setDaemon(true)来设置为守护线程,当JVM中都是守护线程时,当前JVM将退出。
2.4创建线程的方式
线程创建方式一共有4种,后两种在JDK5.0之后新增。
2.4.1继承Thread类(java.lang.Thread)
定义子类继承Thread类
子类中重写Thread类的run方法
创建线程对象,调用对象的start方法来启动线程(执行run方法)
注:
若手动调用run方法,则只是普通方法,而不是多线程模式
实际中的方法由JVM调用
一个线程对象只能调用一次start方法,若重复调用会抛出异常“Iegal Thread State Exception”
2.4.2实现Runnable接口(java.lang.runnable)
问:为什么要实现Runnable接口?
-Java不支持多继承;-不打算重写Thread类的其它方法。
定义类实现Runnable接口
1 package com.imooc.runnable; 2 class PrintRunnable implements Runnable{ 3 /* 4 * (non-Javadoc) 5 * @see java.lang.Runnable#run() 6 * 核心操作 7 * 1.定义一类A去实现Runnable接口 8 * 2.实现Runnable中的run方法 9 * 3.Runnable无法直接用getName()方法 10 * 需要调用Thread的一个静态方法为Thread currentThread()表示当前线程,进而调用gatName()方法 11 * Thread.currentThread().getName(); 12 * 4.在主方法,先去定义类A的对象B,类A不能直接调用start()方法,启动线程只能是Thread和它的子类 13 *所以先创建Thread对象C,用B作为参数 Thread t1 = new Thread(pr); 14 */ 15 @Override 16 public void run() { 17 int i=1; 18 while(i <= 10) 19 System.out.println(Thread.currentThread().getName()+"正在运行!"+(i++)); 20 21 } 22 23 } 24 public class Test { 25 26 public static void main(String[] args) { 27 PrintRunnable pr = new PrintRunnable(); 28 Thread t1 = new Thread(pr); 29 t1.start(); 30 PrintRunnable pr1 = new PrintRunnable(); 31 Thread t2 = new Thread(pr1); 32 t2.start(); 33 } 34 35 }
实现类需要重写Runnable接口的run方法
将实现类的对象作为参数传递给Thread类的构造器创建线程对象。
调用线程对象的start方法(启动线程、调用当前线程的run方法)
2.4.3实现Callable接口(java.util.concurrent.Callable)
Callable接口
Callable接口相比Runnable接口,功能更加强大。实现Callable接口的类需要重写call()方法。
call()方法支持泛型的返回值,可以抛出异常,同时可以借助Future Task来获取返回结果。
Future接口(java.util.concurrent.Future)
可以对具体的Runnable,Callable任务的执行结果进行取消、查询是否完成、获取结果等操作
FutureTask类是Future接口的唯一实现类。
FutureTask类是Future接口唯一实现类。
FutureTask类同时实现了Runnable接口和Future接口。它可以作为Runnable被线程执行,也可以作为Future得到Callable.call()的返回值。
2.4.4使用线程池
背景
经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路
提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建
销毁、实现重复利用。类似生活中的公共交通工具。
好处
提高重复利用。类似生活中的公共交通工具。
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
ExecutorService:线程池接口,常用实现类ThreadPoolExecutor可用于设置线程池属性
方法
void exeute(Runnable command):执行任务或命令,一般用来执行Runnable
Future submit(callable task):执行任务,一般用来执行Callable
void shutdown():关闭线程池
Executors:工具类、线程池的工厂类,应用于创建并返回不同类型的线程
方法
static ExecutorService newCachedThreadPool():创建一个可根据需要新线程的线程池
static ExecutorService newFixedThreadPool(n):创建一个可重用固定线程数的线程池
static ExecutorService newSingleThreadExecutor():创建一个只有一个线程的线程池
static ScheduledExecutorService newScheduledThreadPool(n):创建一个线程池,可安排在给定延迟后运行或定期执行
2.4.5各线程创建方式的比较
继承Thread类VS实现Runnadle接口
在开发中更推荐使用实现Runnable接口的方式。这样可以避免类单继承性的限制,同时更适合处理多线程间的数据共享
Thread类其实也是实现了Runnable接口的
3.线程的生命周期
JDK中用Thread.State类定义线程的几种状态
新建
就绪
运行
阻塞
死亡
4.线程同步
4.1出现原因
多线程执行时用于共享数据时,会造成操作的不完整而破坏数据,实例见TestThreadBug..java。
1 class TestThreaBug { 2 public static void main(String [] args) { 3 Ticket ticket = new Ticket(); 4 5 //3个线程同时售票 6 Thread t1 = new Thead(ticket,"t1窗口“); 7 Thread t2 = new Thead(ticket,"t2窗口”); 8 Thread t3 = new Thead(ticket,"t3窗口"); 9 10 t1.start(); 11 t2.start(); 12 t3.start(); 13 } 14 } 15 16 class Ticket implements Ruunable { 17 private int tick = 20; 18 19 @Override 20 public void run() { 21 while(true) { 22 if(true > 0) { 23 System.out.println(Thred.currentThread().getName() + "售出车票,剩余车票” 24 }else 25 break; 26 } 27 } 28 }
4.2解决方法:同步机制(synchronized)
4.2.1同步代码块
局限性:操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
synchronized(同步监视器){ //需要被同步的代码,即操作共享数据的代码 }
同步监视器,也成为锁。可以是任何类的对象,但多个线程必须使用相同的锁。
线程间通信产生的问题
wait()方法:中断方法的执行,使线程等待 notify()方法:唤醒处于等待的某一个线程,使其结束等待 notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待
1 package com.imooc.queue; 2 3 public class Queue { 4 private int n; 5 boolean flag = false; 6 public synchronized int get() { 7 if(!flag) { 8 try { 9 wait(); 10 } catch (InterruptedException e) { 11 // TODO Auto-generated catch block 12 e.printStackTrace(); 13 } 14 } 15 System.out.println("消费:"+n); 16 flag = false;//消费完毕,容器中没有数据。 17 notifyAll(); 18 return n; 19 } 20 21 public synchronized void set(int n) { 22 if(flag) { 23 try { 24 wait(); 25 } catch (InterruptedException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } 29 } 30 System.out.println("生产:"+n); 31 this.n = n; 32 flag = true;//生产完毕,容器中没有数据。 33 notifyAll(); 34 } 35 36 37 }
1 package com.imooc.queue; 2 3 public class Producer implements Runnable { 4 Queue queue; 5 Producer(Queue queue){ 6 this.queue = queue; 7 } 8 @Override 9 public void run() { 10 int i = 0; 11 while(true) { 12 queue.set(i++); 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 } 20 21 }
1 package com.imooc.queue; 2 3 public class Consumer implements Runnable { 4 Queue queue; 5 Consumer(Queue queue){ 6 this.queue = queue; 7 } 8 9 @Override 10 public void run() { 11 while (true) { 12 queue.get(); 13 try { 14 Thread.sleep(1000); 15 } catch (InterruptedException e) { 16 // TODO Auto-generated catch block 17 e.printStackTrace(); 18 } 19 } 20 } 21 22 23 }
1 package com.imooc.queue; 2 3 public class Test { 4 5 public static void main(String[] args) { 6 Queue queue = new Queue(); 7 new Thread(new Producer(queue)).start(); 8 new Thread(new Consumer(queue)).start(); 9 10 } 11 12 }
1、notifyAll方法是唤醒所有线程,本线程不可能把自己唤醒,如果能执行到唤醒这一步就证明自己实在运行的状态。 2、notify()方法是随机唤醒一个线程,notifyAll()方法是唤醒所有线程,因为线程的随机性,一般用notifyAll() 3、此案例可以理解为必须生产者生产,然后消费者才能消费 可以通过一个中间布尔值变量来进行控制, 当flag=true时,说明生产者已经生产完毕,无需进行生产者方法,执行wait()方法,使线程等待, 当flag=false时,说明消费者已经消费完毕,无需进行消费者方法,执行wait()方法,使线程等待, 注:在执行消费者方法时,消费者消费完毕,此时消费者线程正处于运行状态,生产者线程处于等待阻塞状态,需要执行notifyAll方法来将生产者的线程唤醒,反之亦然。