一、Java的多线程有三种实现方式。
1、继承创建.
a、定义子类,重写run方法
b、创建Thread子类的实例(即现成对象)
c、调用start() 方法启动现成
特征:不可以共享变量。
1 public class FirstThreadByExtends extends Thread { 2 private int i; 3 4 public FirstThreadByExtends(){ 5 super(); 6 } 7 public FirstThreadByExtends(String name){ 8 super(name); 9 } 10 public void run() { 11 for (; i < 100; i++) { 12 System.out.println(getName() + " " + i); 13 } 14 } 15 16 public static void main(String[] args) { 17 for (int i = 0; i < 100; i++) { 18 System.out.println("当前线程的名字: " + Thread.currentThread().getName()); 19 if (i == 20) { 20 new FirstThreadByExtends("线程" + i).start(); 21 } 22 } 23 } 24 25 }
Note: 每次都是new出来的对象,没有共享变量。
2、实现Runable接口.
a、定义Runnable接口实现类,重写run()方法
b、创建Runnable实现类实例,作为Thread实例的Target
c、调用start() 方法启动现成
特征:可以共享变量,因为可以用同一个Target创建多个线程。
1 public class SecondThreadByImplementsRunnable implements Runnable { 2 private int i; 3 4 public void run() { 5 for (; i < 10; i++) { 6 System.out.println(Thread.currentThread().getName() + " " + i); 7 } 8 } 9 public static void main(String[] args) { 10 for (int i = 0; i < 10; i++) { 11 System.out.println("当前线程的名字a: " + Thread.currentThread().getName()); 12 if (i == 5) { 13 SecondThreadByImplementsRunnable st = new SecondThreadByImplementsRunnable(); 14 new Thread(st,"新线程1").start(); 15 new Thread(st,"新线程2").start(); 16 } 17 } 18 } 19 }
3、使用Callable和future创建线程
a、创建Callable实现类,实现call()方法
b、创建Callable实现类的实例,用FutureTask实现类来包装
c、使用FutureTask作为Thread的Target来创建对象
d、通过调用FT的get()方法来获取线程的返回值
特征:可以有返回值,可以抛出异常
1 public class ThirdThreadByImplementsCallable implements Callable<Long> { 2 public Long call() throws Exception { 3 int i = 0; 4 for (; i < 100; i++) { 5 System.out.println(Thread.currentThread().getName() + " " + i); 6 } 7 return System.currentTimeMillis(); 8 } 9 public static void main(String[] args) throws InterruptedException, ExecutionException { 10 ThirdThreadByImplementsCallable tt = new ThirdThreadByImplementsCallable(); 11 FutureTask<Long> task = new FutureTask<Long>(tt); 12 for (int i = 0; i < 100; i++) { 13 System.out.println("当前线程的名字: " + Thread.currentThread().getName() +" "+ i); 14 if (i == 5) { 15 new Thread(task,"有返回值的线程1").start(); 16 } 17 if (i == 6) { 18 System.out.println("----------------------test---------------------------------"); 19 new Thread(task,"有返回值的线程2").start(); 20 } 21 } 22 System.out.println("子线程的返回值:" + task.get()); 23 } 24 }
Note: 第二个线程实例没有跑起来,情况未知,待解决!
三种方式的对比
采用实现Ruannable或Callable接口的方式创建:
a、线程类只是实现了Runnable或Callable接口,还可以继承其他类
b、多个线程可以共享一个Target对象,适合多个相同线程处理统一份资源,从而将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的思想
c、劣势是 编程较复杂,如需访问当前现成还要调用 Thread.CurrentThread()方法.
继承Thread类的方式创建多线程:
a、优势,编写简单,可直接调用this获得当前线程
b、劣势,因继承了Thread,无法再继承其它类
二、线程生命周期
线程的生命周期包括5个状态:新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)。
a、新建和就绪
新建:当使用了new关键字创建了一个线程,该线程就处于新建状态。
就绪:当线程调用了start()方法,该线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器。该状态表示可以运行,准备被JVM线程调度器调度。
注意:启动线程的方法是调用线程实例的start(),如果直接调用run()方法,系统会把线程对象,当成一个普通对象,run()方法也会被当成普通方法,而不是线程执行体。
1 package lifecycle; 2 3 public class InvokeRun extends Thread { 4 private int i; 5 6 public void run() { 7 for (; i < 100; i++) { 8 System.out.println(Thread.currentThread().getName() + " " + i); 9 } 10 } 11 12 public static void main(String[] args) { 13 for (int i = 0; i < 100; i++) { 14 System.out.println(Thread.currentThread().getName() + " " + i); 15 if (i == 20) { 16 //直接调用线程对象的Run方法 17 //系统会把线程对象当作普通对象,把run方法当成普通方法, 18 //所以,一下两行代码不会启动两个线程,而是依次执行两个Run方法 19 new InvokeRun().run(); 20 new InvokeRun().run(); 21 } 22 } 23 } 24 }
b、运行和阻塞
运行:就绪状态时,获得了CPU,该线程就处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;但当线程数大于CPU数,也会出现轮换。具体线程调度细节取决于底层平台所采用的策略。
运行==>阻塞:
a、线程调用sleep()方法主动放弃所占用的资源
b、线程调用了一个阻塞式IO,在该方法返回之前,该线程被阻塞
c、线程试图获得一个同步监视器,但该同步监视器被其它线程所持有。关于同步监视器的知识、后面将有更深入的介绍
d、线程在等待某个通知(notify)
e、程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
阻塞==>运行:
a、sleep()方法超过了指定的时间
b、线程调用的阻塞式IO已经返回
c、线程成功获得了试图获得的同步监视器
d、线程正在等待某个通知时,其它线程发出了一个通知
e、处于挂起状态的的线程被调用了resume()方法
c、线程死亡
a、run()或call方法执行完成,线程正常结束
b、线程抛出一个未捕获的Exception或Error
c、直接调用线程的Stop()方法结束线程,容易死锁,不推荐使用
注意:不要试图对已经死亡的线程调用start()方法,会抛出不合法线程状态异常
1 public class StartDead implements Callable<Long> { 2 private int i; 3 4 public static void main(String[] args) throws InterruptedException, ExecutionException { 5 StartDead sd = new StartDead(); 6 FutureTask<Long> ft = new FutureTask<Long>(sd); 7 Thread t1 = new Thread(ft, "马上会死的线程,哇哈哈哈哈"); 8 //t1.setPriority(Thread.MAX_PRIORITY); 9 t1.start(); 10 //get方法会阻塞main方法 11 System.out.println(ft.get()); 12 for (int i = 0; i < 300; i++) { 13 if (i == 20) { 14 System.out.println(t1.isAlive() + "Love is forever!"); 15 } 16 } 17 if (!t1.isAlive()) { 18 t1.start(); 19 } 20 } 21 22 public Long call() throws Exception { 23 for (; i < 100; i++) { 24 System.out.println(Thread.currentThread().getName() + " " + i); 25 } 26 return 100L; 27 } 28 29 }
三、线程控制
1、join()线程
Thread提供了一个线程等待另一个线程完成的方法 join(),当在某个程序执行流中调用了其它线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程执行完成为止。
join()方法由使用线程的程序调用,将大问题划分成许多小问题,每一个小问题分配一个线程。当所有小问题都得到处理以后,再调用主线程进行进一步操作。
join()方法有三种重载形式:
join(),等待被join的线程执行完成。
join(long millis),等待被join的线程的时间最长为millis毫秒。如果在millis毫秒之内被join的线程还没有执行结束,则不再等待。
join(long millis,int nanos),比上面多了个微秒。
1 public class JoinThread extends Thread { 2 public JoinThread(String name) { 3 super(name); 4 } 5 6 public void run() { 7 for (int i = 0; i < 100; i++) { 8 System.out.println(this.getName() + " " + i); 9 } 10 } 11 12 public static void main(String[] args) throws InterruptedException { 13 new JoinThread("新线程").start(); 14 for (int i = 0; i < 100; i++) { 15 if (i == 20) { 16 JoinThread jt = new JoinThread("被Join的线程"); 17 jt.start(); 18 jt.join(0); 19 } 20 System.out.println(Thread.currentThread().getName() + " " + i); 21 } 22 } 23 24 }
2、后台线程
所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。
下面是一个后台线程的示例:
1 public class Daemon extends Thread { 2 public Daemon(){ 3 4 } 5 public Daemon(String name){ 6 super(name); 7 } 8 9 public void run(){ 10 for (int i = 0; i < 1000; i++) { 11 System.out.println(this.getName() + " " + i); 12 try { 13 sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 } 18 } 19 20 public static void main(String[] args) throws InterruptedException { 21 Thread t = new Daemon("守护线程"); 22 t.setDaemon(true); 23 t.start(); 24 for (int i = 0; i < 10; i++) { 25 sleep(200); 26 System.out.println(Thread.currentThread().getName() + " " + i); 27 } 28 //Program end here 29 //and Daemon is end follow it. 30 System.out.println("啊哦,守护线程应该死了了吧!"); 31 } 32 }
3、线程睡眠:Sleep()
让线程暂停,进入阻塞状态。它是Thread的静态方法。
1 public class SleepThread { 2 public static void main(String[] args) throws InterruptedException { 3 for (int i = 0; i < 10; i++) { 4 System.out.println(Thread.currentThread().getName() + " " + i); 5 Thread.sleep(300); 6 } 7 } 8 }
4、线程让步:yield()
让线程进入就绪状态,等待CPU重新调度。不过在此期间,只有 相同或更高优先级的进程能被调用,不然的话会继续执行。
5、线程优先级
范围 1 ~ 10,三个静态常量 MAX_PRIORITY:10, MAX_PRIORITY:1, MAX_PRIORITY:5.
t.setPriority(MAX_PRIORITY);
四、线程同步
1、线程安全
银行取钱问题。
a. 用户输入帐号、密码,系统验证是否通过
b. 用户输入取款金额
c. 系统判断账户余额是否大于取款金额
d. 如果余额大于取款金额,成功; 如果余额小于取款金额,取款失败。
以下是没有同步控制的代码,也许有时候会正确,但只要有一次错误,那整个代码就是错误的。
2、同步代码快
synchronized (obj) {}, obj就是同步监视器,线程执行之前必须获得同步监视器的锁定。
流程:加锁==>操作==>释放锁。
1 synchronized (account) { 2 // If balance > drawAmount, check out! 3 if (account.getBalance() > drawAmount) { 4 System.out.println(this.getName() + "取钱成功!吐出钞票:" + this.drawAmount); 5 try { 6 Thread.sleep(1); 7 } catch (Exception e) { 8 e.printStackTrace(); 9 } 10 account.setBalance(account.getBalance() - this.drawAmount); 11 System.out.println(" 余额为:" + account.getBalance()); 12 } else { 13 System.out.println(getName() + "余额不足,取款失败!"); 14 } 15 }
3、同步方法
用同步方法可以非常方便的实现线程安全类。任意线程都可以安全访问。但无法显示指定同步监视器,同步监视器就是本身。
代码如下:
1 public synchronized void draw(double drawAmount) { 2 // If balance > drawAmount, check out! 3 if (this.getBalance() > drawAmount) { 4 System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + 5 drawAmount); 6 try { 7 Thread.sleep(1); 8 } catch (Exception e) { 9 e.printStackTrace(); 10 } 11 this.setBalance(this.getBalance() - drawAmount); 12 System.out.println(" 余额为:" + this.getBalance()); 13 } else { 14 System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!"); 15 } 16 }
4、释放同步监视器的锁定
a.同步方法、同步代码快执行结束,当前线程释放同步监视器。
b.break、reutrn终止了该代码块,该方法的继续执行,当前线程会释放同步监视器。
c. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法的异常结束时,当前线程将会释放同步监视器。
d. 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。
以下不会释放:
a.sleep().yield();
b.suspend(). resume(); 容易弄成死锁,不推荐
5、同步锁
lock.lock(), lock.unlock();
1 lock.lock(); 2 try { 3 // If balance > drawAmount, check out! 4 if (this.getBalance() > drawAmount) { 5 System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount); 6 try { 7 Thread.sleep(1); 8 } catch (Exception e) { 9 e.printStackTrace(); 10 } 11 this.setBalance(this.getBalance() - drawAmount); 12 System.out.println(" 余额为:" + this.getBalance()); 13 } else { 14 System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!"); 15 } 16 } finally { 17 lock.unlock(); 18 }
6、死锁
资源的相互依赖,你把我要用的给锁了,我把你要用的给锁了,我们各自拿着一块等待对方,谁都不肯释放。