基本概念:
线程是一个程序内部的顺序控制流,一个进程相当于一个任务,一个线程相当于任务的一个执行路径。
进程是一个操作系统执行的任务,一般都是.exe文件。一个进程中可以运行着多个线程。
线程和进程的相似性在于它们都是单一顺序控制流。
多进程就是一个操作系统运行着多个任务。
多线程就是一个程序内部运行着多个顺序控制流。
每个Java运行程序至少有一个主线程,例如:public static void main(String[] args){}就是一个主线程。
运行方式:
通过start()方法启动一个线程。
通过run()来执行一个线程,它是线程的主体部分。
通过sleep(long millis)方法来使当前线程休眠一段时间,当过了mills时间后再恢复到可运行态,不是运行状态。因此,sleep()方法不能保证该线程到期后就立马开始运行。sleep()是静态方法,只能控制当前正在运行的线程,因此,休眠期间不影响其他线程的运行。
通过yield()方法暂停当前执行的线程,让同优先级的线程轮询执行一段时间。
通过手动调用stop()方法来结束一个线程(或者执行到run()方法的末尾,或者抛出未经处理Exception/Error,这两都是程序自动结束线程)。
通过join()方法来让线程A加入到线程B的尾部,在B执行完之前A不能工作。
通过notify() 唤醒在此对象监视器上等待的单个线程。
通过notifyAll() 唤醒在此对象监视器上等待的所有线程。
通过wait() 导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
如何创建和启动线程:
两种方式:extends Thread类、implements Runnable接口。
Thread类:也是实现了Runnable接口。调用方式:继承Thread类后的子类生成对象后调用start()方法,如:new MyThread().start()。
class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("i="+i); } }}
Runnable接口:只有一个run()方法来定义线程运行体。使用Runnable接口可以为多线程提供共享数据。调用方式:将子类对象通过Thread的构造器来执行start()方法,如:new Thread(new MyRunnable ()).start()。
class MyRunnable implements Runnable{ String name ="xiaom"; @Override public void run() { System.out.println("name:"+name); }}
线程状态:
① 新线程态(New Thread)
产生了一个Thread对象就生成一个新线程。当线程处于“新线程”状态时,仅仅只是一个空线程对象,系统还没有给它分配资源。因此,只能start()或者stop()操作,除此之外的操作都会引发异常。
② 可运行态(Runnable)
start()方法产生线程运行时所需的资源,调度线程执行,并且调用run()方法时候,线程处于“可运行”状态。之所以不称为“运行态”是因为它不总是一直占用处理器。特别是对只有一个处理器的PC而言,任何时刻只有一个可运行状态的线程占用。Java通过调度来实现多线程对处理器的共享。
③ 非运行态(Not Runnable)
当sleep()、wait()等方法被调用或者线程出于I/O等待时称为“非运行”状态。
④ 死亡态(Dead)
当run()方法返回,或者别的线程调用stop()方法时,线程进入“死亡”状态。
线程优先级:
当PC只有一个处理器时,以某种顺序在单处理器的情况下执行多线程被称为”调度”。Java采用的是固定优先级调度,根据处于可运行状态线程的优先级来调度。
当线程产生时,它继承原线程的优先级,必要的时候可以通过Thread.setPriority()方法对优先级更改。如果有多个线程等待运行,系统选择优先级别最高的可运行线程运行。只有当它停止、自动放弃、或者由于某种原因成为非运行态时低优先级的线程才能运行。如果两个线程具有相同的优先级,那么它们会被交替运行。
在任何时刻,如果一个比其他线程优先级都高的线程的状态变为“可运行“,那么实时系统将选择该线程来运行。
线程组:
每个Java线程都是某个线程组的成员。线程组提供一种机制,将多个线程集于一个对象内,能对它们进行整体操作。譬如,你能用一个方法调用来启动或挂起组内的所有线程。Java线程组由ThreadGroup类实现。当线程产生时,可以指定线程组或由实时系统将其放入某个缺省的线程组内。线程只能属于一个线程组,并且当线程产生后不能改变它所属的线程组。
多线程同步:
因为并发性,当多个线程同时操作一个可共享资源时易产生数据不一致,因此加入同步锁来避免该线程没有完成操作之前有其他线程进入,从而保证数据的唯一性和准确性。
使用synchronized关键字修饰。
① 修饰方法:
private synchronized void put(){}
Java中每个对象都有一个内置锁,用此关键字修饰方法时,内置锁会保护整个方法。在调用方法前,需要获得内置锁,否则会造成阻塞。
synchronized还可以修饰static方法,当静态方法被调用时,锁住的就是整个Class。
//修饰方法private synchronized void _outTicket(){ if(total>0){ System.out.println(Thread.currentThread().getName()+ "出票" + this.total); this.total--; }}
② 修饰代码块:
synchronized(this){}
被关键字修饰的语句块会自动加上内置锁,从而实现同步。
class MoreThread implements Runnable { private int total = 10;// 总票数 @Override public void run() { for (int i = 0; i < 20; i++) {//线程运行数 try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } outTicket(); }} // 出票方法 private void outTicket() { synchronized(this){//修饰代码块 if(total>0){ System.out.println(Thread.currentThread().getName()+ "出票" + this.total); this.total--; } } }}public class MyThreadTest { public static void main(String[] args) { // 多线程同步 MoreThread t = new MoreThread(); new Thread(t,"线程1").start(); new Thread(t,"线程2").start(); new Thread(t,"线程3").start(); }}
同步是一种高开销的操作,因此应该尽量减少同步内容。通常没必要同步整个方法,使用synchronized代码块同步关键代码即可。
线程锁:
① 原理:
Java中每个对象都有一个内置锁。当程序运行到synchronized修饰的非静态方法上时会自动获得当前执行代码的对象的锁。
一个对象只有一个锁,如果一个线程获得当前对象锁,其他线程就不能再获得该对象锁,除非该锁已被释放。
释放锁是指持锁对象已退出了synchronized修饰的方法或者代码块。
② 锁和同步的要点:
a) 只能同步方法,不能同步变量和类。
b) 不必同步类的所有方法,类可以同时拥有同步方法合非同步方法。
c) 如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
d) 如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
e) 线程睡眠时,锁不会释放。
f) 线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
向线程传递数据:
① 通过构造器传递
class MyRunnable implements Runnable { String name ; MyRunnable(String name){ this.name = name; } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("name:" + name+i); } }}
调用方式:
new Thread(new MyRunnable("Xiaoming")).start();
②通过变量的setters方法传递
class MyRunnable implements Runnable { String name ; public void setName(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("name:" + name+i); } }}
调用方式:
MyRunnable myRunnable = new MyRunnable(); myRunnable.setName("Xiaoming"); Thread thread = new Thread(myRunnable); thread.start();
③ 通过回调函数传递
class Data { public int i =100;//初始数据}class Work{ public void process(Data data, int random){ System.out.println("data.i="+data.i+" random="+random); data.i*=random; }}class NewThread implements Runnable{ private Work work; NewThread(Work work){ this.work = work; } @Override public void run() { Data data = new Data(); work.process(data, new Random().nextInt(10));//回调函数 System.out.println("result:"+data.i); } public static void main(String[] args) { new Thread(new NewThread(new Work())).start(); }}
线程池:
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。
① 减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
② 可根据系统的承受能力,调整线程池中线程的数目,防止因为消耗过多的内存,而把服务器累趴(每个线程大约需要1MB内存,线程开的越多内存消耗就越大)。
池的创建方式:
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
Executors提供以下几个方法来创建线程池:
① newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池。
代码:
ExecutorService pool =Executors.newFixedThreadPool(3);pool.execute(new Thread(new MoreThread(),"线程1"));pool.execute(new Thread(new MoreThread(),"线程2"));pool.execute(new Thread(new MoreThread(),"线程3")); //关闭线程池pool.shutdown();
② newSingleThreadExecutor():创建一个单线程的线程池。线程池只有一个线程在工作,如果这个线程出现异常,会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务提交的顺序执行。
代码:
ExecutorService pool = Executors.newSingleThreadExecutor();
③ new CachedThreadPool():创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
代码:
ExecutorService pool = Executors.newCachedThreadPool();
④ newScheduledThreadPool(int corePoolSize):创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行。
代码:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);//使用延迟,隔1、3、5秒执行当前线程pool.schedule(new Thread(new MoreThread(),"线程1"), 1000, TimeUnit.MILLISECONDS);pool.schedule(new Thread(new MoreThread(),"线程2"), 3000, TimeUnit.MILLISECONDS);pool.schedule(new Thread(new MoreThread(),"线程3"), 5000, TimeUnit.MILLISECONDS); //关闭线程池pool.shutdown();
自定义线程池ThreadPoolExecutor:
ThreadPoolExecutor的构造器:
ThreadPoolExecutor(int corePoolSize ,int maximumPoolSize,long keepAliveTime ,TimeUnit unit,BlockingQueueworkQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
参数:
corePoolSize:池中所保存的线程数,包括空闲线程。
maximumPoolSize:池中允许的最大线程数。
keepAliveTime:当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit:keepAliveTime的时间单位。
workQueue:执行前用于保持任务的队列。此队列仅保持由execute方法提交的Runnable任务。
threadFactory:执行程序创建新线程时使用的factory。
handler:由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
并发机制-锁:
在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,利用控制对竞争资源并发访问的控制,这些内容主要在java.util.concurrent.locks包中,里面有三个重要的接口:
- Condition:将Object监视器方法(wait、notify、notifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合适用,为每个对象提供多个等待。
- Lock:提供了比使用synchronized方法和语句更广泛的锁定操作。
- ReadWriteLock:维护了一组相关的锁定,一个只读操作,一个写入操作。
直接提供一段Lock代码:
/** * 线程锁的机制 * @author admin */public class ThreadLockTest { public static void main(String[] args) { Ticket ticket = new Ticket(30);//创建总票数 TicketWindow wind = new TicketWindow();//售票窗口随机 Lock lock = new ReentrantLock();//创建锁 ExecutorService pool = Executors.newCachedThreadPool();//线程池 pool.execute(new Thread(new Tecketing(wind, new Buyyer("张力",2), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("李兴",1), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("刘兰兰",5), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("兔兔",2), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("张力",2), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚丽丽",1), ticket, lock))); pool.execute(new Thread(new Tecketing(wind, new Buyyer("姚明明",3), ticket, lock))); pool.shutdown(); }}/** * 售票窗口类 * @author admin */class TicketWindow{ //随机返回一个窗口 public int wind(){ return (new Random().nextInt(5)+1); }}/** * 总票数类 * @author admin */class Ticket{ private int total; //构造器 public void setTotal(int total) { this.total = total; } public Ticket(int total) { this.total = total; } public int getTotal() { return total; }}/** * 售票机制 * @author admin */class Tecketing implements Runnable{ private TicketWindow wind; //窗口 private Buyyer user; private Ticket ticket; //总票数 private Lock lock; public Tecketing(TicketWindow wind, Buyyer user, Ticket ticket, Lock lock) { this.wind = wind; this.user = user; this.ticket = ticket; this.lock = lock; } @Override public void run() { //获取锁 lock.lock(); if(ticket.getTotal()>0){ System.out.print(user.getName()+"在"+wind.wind()+"窗口购买了"+user.getTicketCount()+"张票,"); ticket.setTotal(ticket.getTotal()-user.getTicketCount()); System.out.println("目前剩余票数:"+ticket.getTotal()); }else{ System.out.println("今日票已全部售罄!"); } //释放锁,否则其他线程无法执行 lock.unlock(); }}/** * 购票者类 */class Buyyer{ private String name; private int ticketCount; //购票数量 public Buyyer(String name, int ticketCount) { this.name = name; this.ticketCount = ticketCount; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getTicketCount() { return ticketCount; } public void setTicketCount(int ticketCount) { this.ticketCount = ticketCount; }}
ReadWriteLock机制的锁,读写锁分离,灵活性更好。代码:
/** * ReadWriterLock接口读写时分别用锁 * @author admin */public class ReadWriterLokTest { public static void main(String[] args) { Ticket ticket = new Ticket(30);//创建总票数 TicketWindow wind = new TicketWindow();//售票窗口随机 ReadWriteLock lock = new ReentrantReadWriteLock();//创建锁 ExecutorService pool = Executors.newCachedThreadPool();//线程池 pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("张力",2), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("李兴",1), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("刘兰兰",5), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("兔兔",2), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("张力",2), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚丽丽",1), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("姚明明",3), ticket, lock, true))); pool.execute(new Thread(new Tecketing_1(wind, new Buyyer("窗口",0), ticket, lock, false))); pool.shutdown(); }}/** * 售票机制 * @author admin */class Tecketing_1 implements Runnable{ private TicketWindow wind; //窗口 private Buyyer user; private Ticket ticket; //总票数 private ReadWriteLock lock; private boolean isCheck;//是否查询 private int solded =0;//已售卖 public Tecketing_1(TicketWindow wind, Buyyer user, Ticket ticket, ReadWriteLock lock,boolean isCheck) { this.wind = wind; this.user = user; this.ticket = ticket; this.lock = lock; this.isCheck = isCheck; } @Override public void run() { if(isCheck){ //获取写锁 lock.writeLock().lock(); if(ticket.getTotal()>0){ System.out.print(user.getName()+"在"+wind.wind()+"窗口购买了"+user.getTicketCount()+"张票,"); ticket.setTotal(ticket.getTotal()-user.getTicketCount()); System.out.println("目前剩余票数:"+ticket.getTotal()); }else{ System.out.println("今日票已全部售罄!"); } //释放 lock.writeLock().unlock(); }else{ //获取读锁 lock.readLock().lock(); solded +=user.getTicketCount(); ticket.setTotal(ticket.getTotal()-solded); System.out.println("窗口"+wind.wind()+" 正在查询剩余票数:"+ticket.getTotal()); //释放 lock.readLock().unlock(); } }}