线程基础
线程的五种状态
- 创建(New):使用new创建一个线程时的状态,尚未启动。
- 就绪(Runnable):调用start()启动线程,加入就绪队列,等待被调度进CPU运行
- 运行(Run):处于就绪状态的线程获取了cpu的执行权
- 阻塞(Block):由于某些原因该线程放弃了cpu的使用权。停止执行。除非线程进入可运行的状态,才会有机会获取cpu的使用权。
- 等待阻塞:运行中的线程执行wait方法,这时候该线程会被放入等待队列。
- 同步阻塞:运行中的线程获取同步锁,如果该同步锁被别的线程占用,这个线程会成被放入锁池,等待其他线程释放同步锁。
- 其他阻塞:运行的线程执行sleep或者join方法这个线程会成为阻塞状态。当sleep超时,join等待线程终止,该线程会进入可运行状态。
- 死亡(Dead):线程run mian 执行完毕后,或者因为某些异常产生退出了 run 方法,该线程的生命周期结束。
线程状态转换图
线程有关方法
- sleep(time):使线程进入阻塞状态,但不释放锁,睡眠时间发到后自动进入就绪状态。
作用:给其他线程执行机会。 - wait():使线程进入阻塞状态,释放锁,等到被其他线程notify()时进入就绪状态。
- interrupt():停止线程,调用时抛出中断异常。
- setDaemon(true):后台线程,当所有的前台线程都结束了,后台线程就结束。
注意:在启动前调用。作用:为前台线程提供服务。 - setPriority(Thead.MAX_PRIORITY):设置线程的优先级(1-10),默认优先级为5。
- join():当A线程执行到B线程的join()时,A就会等待,直到B结束A才会执行。
作用:临时加入线程,使该线程运行完,其他线程才继续。 - yield():使当前线程放弃分得的CPU时间片,进入就绪状态。
作用:使同优先级或更高优先级的线程有执行机会。
- sleep(time):使线程进入阻塞状态,但不释放锁,睡眠时间发到后自动进入就绪状态。
继承Thread类
代码演示
class Test extends Thread { Test(String name) { super(name); } //run()存储线程要运行的代码 public void run() { for(int x = 0; x < 60;x++) { System.out.println(Thread.currentThread().getName()+" run....."+x); } } } class ThreadDemo1 { public static void main(String[] args) { Test t1 = new Test("one"); Test t2 = new Test("two"); t1.start(); t2.start(); } }
存在的不足
- 子类无法多继承。
- 不适合资源共享。
实现Runnable接口
代码演示
class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { if(tick>0) { System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } } class ThreadDemo3 { 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); Thread t4 = new Thread(t); //启动线程,使线程处于就绪状态,等待被调度 t1.start(); t2.start(); t3.start(); t4.start(); } }
注意:实现Runnable接口的类不是线程类。创建线程对象的是Thread或Thread的子类。
多线程的安全问题
代码演示安全问题
class Ticket implements Runnable { //线程的共享数据 private int tick = 100; public void run() { while(true) { if(tick>0) { //为了演示可能出现的问题,让当前线程释放执行权 try{Thread.sleep(10);} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } }
当多线程用到共享资源时,由于CPU的时间片特性,不管run()有没有执行完毕,CPU时间片一旦执行完一个线程的时间片,会自动跳转到另一个线程的run()中去执行其时间片,这样就可能导致共享数据的不一致,也即出现了多线程安全问题。
解决方案
class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { //在涉及到共享数据的代码上加互斥锁 synchronized(this) { if(tick>0) { try{Thread.sleep(10);} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"sale:"+tick--); } } } } }
互斥锁
互斥锁的理解
互斥锁相当于公共厕所的一把锁,公共厕所是一个共享资源,为了使当前正在使用资源的人不被其他人打扰,在门上加一把锁,里面的人不开门(释放锁),外面的人就进不去。
如何判断哪些代码需要加锁
- 明确哪些代码是多线程运行代码
- 明确共享数据
- 明确多线程运行代码中哪些语句是操作共享数据的。
加锁后同步的前提
- 有两个或两个以上的线程
- 多个线程使用同一个锁
加锁的弊端
多个线程需要判断锁,较为消耗资源。
锁对象
- 同步代码块的锁可以是任意对象。
- 同步函数的锁是this。
- 静态同步函数的锁是Class对象。
多线程间的通信
- 问题描述
当多个线程在操作同一个资源,但是操作的动作不同(run()不同),就涉及到了线程间的通信问题。
解决方法:用标记+等待唤醒机制wait()和notify()实现同步
- wait():使正在执行的线程等待,进入阻塞状态。
- notify():唤醒线程池中等待的线程,进入就绪状态。
- notifyAll() :唤醒线程池中所有线程。
注意:这些方法都在互斥锁中使用,因为它们都被互斥锁对象调用。 以下是代码示例。
class Res { private String name; private String sex; private boolean flag = false; //是否释放锁的标记 //送入 public synchronized void set(String name,String sex) //加锁 { if(flag) //有煤了,等,释放锁 try{this.wait();} catch(Exception e){} this.name = name; this.sex = sex; flag = true; //标记有资源了 this.notify(); //唤醒对方线程,叫对方送走资源 } //送走 public synchronized void out() { if(!flag) //没有资源,等 try{this.wait();} catch(Exception e){} System.out.println(name+"........"+sex); flag = false; //标记没有资源了 this.notify(); //唤醒对方线程,叫对方送资源 } } class Input implements Runnable { Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true) { if(x == 0) r.set("mike","male"); else r.set("lili","女女女女女女"); x = (x+1) % 2; } } } class Output implements Runnable { Res r; Output(Res r) { this.r = r; } public void run() { while(true) { r.out(); } } } class ThreadDemo7 { public static void main(String[] args) { //要操作的共享资源 Res r = new Res(); //开启两个线程同时运行 new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
需要同步的几种情况
- 两个操作一样的线程:直接加锁(锁对象是本身对象)
- 两个操作不一样的线程:加锁+判断标记+等待唤醒机制(notify())
- 两个执行A操作的线程、两个执行B操作的线程:加锁+循环判断标记+等待唤醒机制(notifyAll())
生产者消费者问题代码示例
//共享资源 class Resource { private String name; //资源名称 private int count = 1; //操作资源的次数 private boolean flag = false; //是否释放锁的标记 public synchronized void set(String name) //隐式的锁机制 { while(flag) //有资源 try{this.wait();} catch(Exception e){} //等待的同时释放锁 this.name = name + "--" + count++; System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name); flag = true; //有资源了 this.notifyAll(); //唤醒所有等待队列中的线程 } public synchronized void out() { while(!flag) //没有资源 try{this.wait();} catch(Exception e){} System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name); flag = false; //资源被取走了 this.notifyAll(); //唤醒所有等待队列中的线程 } } //生产者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } public void run() { while(true) { res.set("+商品+"); } } } //消费者 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { res.out(); } } } class ThreadDemo8 { public static void main(String[] args) { //共享资源对象 Resource r = new Resource(); //生产者、消费者对象操作同一个资源 Producer pro = new Producer(r); Consumer con = new Consumer(r); //两个生产者线程、两个消费者线程 Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }
JDK5.0升级后的同步解决方案
升级内容
- 将同步synchronized替换成Lock接口
- 由于wait()、notify()都要标识自己所属的锁,因此将Object中的wait()、notify()、 notifyAll()封装成Condition监视器对象,该对象可以通过Lock锁对象获取,且一个锁可以对应多个监视器对象。
升级后的好处
- 显式的锁机制,使加锁和释放锁操作更透明。
- 显式的等待唤醒机制,可以有多个Condition监视器对象,使不会唤醒本方。
- 由于一个锁可以对应多个Condition监视器对象,因此不容易发生死锁。
生产者消费者问题代码示例
import java.util.concurrent.locks.*; //要操作的共享资源 class Resource { private String name; //资源名称 private int count = 1; //操作资源的次数 private boolean flag = false; //是否释放锁的标记 private Lock lock = new ReentrantLock(); //接口引用指向子类对象 private Condition condition_pro = lock.newCondition(); //生产者监视器 private Condition condition_con = lock.newCondition(); //消费者监视器 //设置资源 public void set(String name) throws InterruptedException { lock.lock(); //加锁 try { while(flag) condition_pro.await(); //生产者等待,没有释放锁 this.name = name + "--" + count++; System.out.println(Thread.currentThread().getName()+"....生产者...."+this.name); flag = true; condition_con.signal(); //消费者醒来 } finally //在await()之前必须先释放锁 { lock.unlock(); //释放锁 } } //获取资源 public void out() throws InterruptedException { lock.lock(); //加锁 try { while(!flag) condition_con.await(); //消费者等待,没有释放锁 System.out.println(Thread.currentThread().getName()+"....消费者.........."+this.name); flag = false; condition_pro.signal(); //生产者醒来 } finally //在await()之前必须先释放锁 { lock.unlock(); } } } //生产者 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res = res; } public void run() { while(true) { try { res.set("+商品+"); } catch (InterruptedException e) { } } } } //消费者 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { try { res.out(); } catch (InterruptedException e) { } } } } class ThreadDemo9 { public static void main(String[] args) { //创建共享资源对象 Resource r = new Resource(); //创建生产者消费者对象,操作同一个资源对象 Producer pro = new Producer(r); Consumer con = new Consumer(r); //两个生产者线程,两个消费者线程 Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); //开启线程对象 t1.start(); t2.start(); t3.start(); t4.start(); } }