[关闭]
@MrXiao 2017-12-19T11:46:14.000000Z 字数 6563 阅读 754

多线程

Concurrent Java


1 概述

1.1 进程与线程

1.2 并行与并发

1.3 基本实现方式

1.3.1 继承Thread类覆盖run方法

  1. package com.topvision.thread;
  2. public class ThreadDemo1 {
  3. public static void main(String[] args) {
  4. Demo1 d1 = new Demo1();
  5. d1.start();
  6. for (int i = 0; i < 60; i++) {
  7. System.out.println(Thread.currentThread().getName() + i);
  8. }
  9. }
  10. }
  11. class Demo1 extends Thread {
  12. @Override
  13. public void run() {
  14. for (int i = 0; i < 10; i++) {
  15. System.out.println(Thread.currentThread().getName() + i);
  16. }
  17. }
  18. }

1.3.2 实现Runnable接口实现run方法

  1. public class ThreadDemo1 {
  2. public static void main(String[] args) {
  3. Thread d2 = new Thread(new Demo2());
  4. d2.start();
  5. for (int i = 0; i < 60; i++) {
  6. System.out.println(Thread.currentThread().getName() + i);
  7. }
  8. }
  9. }
  10. class Demo2 implements Runnable {
  11. @Override
  12. public void run() {
  13. for (int i = 0; i < 10; i++) {
  14. System.out.println(Thread.currentThread().getName() + i);
  15. }
  16. }
  17. }

1.4 继承Thread与实现Runnable的区别

2 基本API

与人有生老病死一样,线程也同样要经历开始(等待)、运行、挂起和停止四种不同的状态。这四种状态都可以通过Thread类中的方法进行控制。下面给出了Thread类中和这四种状态相关的方法。

  1. 开始线程

    publicvoid start( );
    publicvoid run( );

  2. 挂起和唤醒线程

    publicvoid resume( ); // 不建议使用
    publicvoid suspend( ); // 不建议使用
    publicstaticvoid sleep(long millis);
    publicstaticvoid sleep(long millis, int nanos);

  3. 终止线程

    publicvoid stop( ); // 不建议使用
    publicvoid interrupt( );

  4. 得到线程状态

    publicboolean isAlive( );
    publicboolean isInterrupted( );
    publicstaticboolean interrupted( );

  5. join方法

    publicvoid join( ) throws InterruptedException;

线程在建立后并不马上执行run方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程不各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。

一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。

2.1 设置及获取线程名字

  1. 构造方法中传入名字 new Thread("张三"){}
  2. 线程对象设置名字 thread.setName()
  3. 线程对象获取名字 thread.getName() Thread.currentThread().hetName()

2.2 休眠线程Sleep

  1. sleep方法有两个重载形式,其中一个重载形式不仅可以设毫秒,而且还可以设纳秒(1,000,000纳秒等于1毫秒)。但大多数操作系统平台上的Java虚拟机都无法精确到纳秒,因此,如果对sleep设置了纳秒,Java虚拟机将取最接近这个值的毫秒。

  2. 在使用sleep方法时必须使用throws或try{…}catch{…}。因为run方法无法使用throws,所以只能使用try{…}catch{…}。当在线程休眠的过程中,使用interrupt方法中断线程时sleep会抛出一个InterruptedException异常。sleep方法的定义如下:

    publicstaticvoid sleep(long millis) throws InterruptedException
    publicstaticvoid sleep(long millis, int nanos) throws InterruptedException
    

2.3 守护线程Daemon

**setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出 **

  1. public class ThreadDemo4 {
  2. /**
  3. * @param args
  4. * 守护线程
  5. */
  6. public static void main(String[] args) {
  7. Thread t1 = new Thread() {
  8. public void run() {
  9. for (int i = 0; i < 10; i++) {
  10. System.out.println(getName() + "...aaaaaaaaaaaaaaaaaaaa");
  11. }
  12. }
  13. };
  14. Thread t2 = new Thread() {
  15. public void run() {
  16. for (int i = 0; i < 5000; i++) {
  17. System.out.println(getName() + "...bb" + i);
  18. }
  19. }
  20. };
  21. t2.setDaemon(true); // 设置为守护线程
  22. t1.start();
  23. t2.start();
  24. }
  25. }

2.4 加入线程Join

join方法的功能就是使异步执行的线程变成同步执行。也就是说,当调用线程实例的start方法后,这个方法会立即返回,如果在调用start方法后后需要使用一个由这个线程计算得到的值,就必须使用join方法。如果不使用join方法,就不能保证当执行到start方法后面的某条语句时,这个线程一定会执行完。而使用join方法后,直到这个线程退出,程序才会往下执行。下面的代码演示了join的用法。

2.5 礼让线程Yield

Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

2.6 设置线程优先级Priority

setPriority不一定起作用的,在不同的操作系统不同的jvm上,效果也可能不同。现在很多jvm的线程的实现都使用的操作系统线程,设置优先级也是使用的操作系统优先级,java层面有10个优先级别,假设操作系统只有3个优先级别,那么jvm可能将1-4级映射到操作系统的1级,5-7级映射到操作系统的2级,剩下的映射到3级,这样的话,在java层面,将优先级设置为5,6,7,其实本质就是一样的了。

另外,操作系统也不能保证设置了优先级的线程就一定会先运行或得到更多的CPU时间。
在实际使用中,不建议使用该方法

3 同步

3.1 同步代码块

  1. public class ThreadDemo6 {
  2. public static void main(String[] args) {
  3. Print p = new Print();
  4. new Thread(){
  5. public void run() {
  6. while (true) {
  7. p.print1();
  8. }
  9. }
  10. }.start();
  11. new Thread(){
  12. public void run() {
  13. while (true) {
  14. p.print2();
  15. }
  16. }
  17. }.start();
  18. }
  19. }
  20. class Print {
  21. public void print1() {
  22. synchronized (ThreadDemo6.class) {
  23. try {
  24. Thread.sleep(1000);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println("鼎");
  29. System.out.println("点");
  30. System.out.println("视");
  31. System.out.println("讯");
  32. }
  33. }
  34. public void print2() {
  35. synchronized (ThreadDemo6.class) {
  36. try {
  37. Thread.sleep(1000);
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. System.out.println("T");
  42. System.out.println("O");
  43. System.out.println("P");
  44. System.out.println("V");
  45. System.out.println("I");
  46. System.out.println("S");
  47. System.out.println("I");
  48. System.out.println("O");
  49. System.out.println("N");
  50. }
  51. }
  52. }

3.2 同步方法

  1. //同步方法,锁对象是this
  2. public synchronized void print1() {
  3. System.out.println("鼎");
  4. System.out.println("点");
  5. System.out.println("视");
  6. System.out.println("讯");
  7. }
  1. //静态同步方法,锁对象是字节码文件
  2. public static synchronized void print1() {
  3. System.out.println("鼎");
  4. System.out.println("点");
  5. System.out.println("视");
  6. System.out.println("讯");
  7. }

3.3 线程安全

  1. package com.topvision.thread;
  2. public class ThreadTicket1 {
  3. public static void main(String[] args) {
  4. /*new Ticket1().start();
  5. new Ticket1().start();
  6. new Ticket1().start();
  7. new Ticket1().start();*/
  8. Ticket2 t2 = new Ticket2();
  9. new Thread(t2).start();
  10. new Thread(t2).start();
  11. new Thread(t2).start();
  12. new Thread(t2).start();
  13. }
  14. }
  15. class Ticket1 extends Thread {
  16. private static int ticktets = 100;
  17. public void run() {
  18. while (true) {
  19. synchronized (Ticket1.class) {
  20. if (ticktets <= 0) {
  21. break;
  22. }
  23. System.out.println("这是第" + ticktets-- + "张票");
  24. }
  25. }
  26. }
  27. }
  28. class Ticket2 implements Runnable {
  29. private int ticktets = 100;
  30. @Override
  31. public void run() {
  32. // TODO Auto-generated method stub
  33. while (true) {
  34. synchronized (this) {
  35. if (ticktets <= 0) {
  36. break;
  37. }
  38. System.out.println("这是第" + ticktets-- + "张票");
  39. }
  40. }
  41. }
  42. }

3.4 死锁

两个线程各自拥有对方需要的锁对象,等待对方释放,因此互相等待,程序卡死。
* 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁
* 尽量不要嵌套使用

  1. package com.topvision.thread;
  2. public class ThreadDemo8 {
  3. /**
  4. * @param args
  5. */
  6. private static String s1 = "筷子左";
  7. private static String s2 = "筷子右";
  8. public static void main(String[] args) {
  9. new Thread() {
  10. public void run() {
  11. while (true) {
  12. synchronized (s1) {
  13. System.out.println(getName() + "...获取" + s1 + "等待" + s2);
  14. synchronized (s2) {
  15. System.out.println(getName() + "...拿到" + s2 + "开吃");
  16. }
  17. }
  18. }
  19. }
  20. }.start();
  21. new Thread() {
  22. public void run() {
  23. while (true) {
  24. synchronized (s2) {
  25. System.out.println(getName() + "...获取" + s2 + "等待" + s1);
  26. synchronized (s1) {
  27. System.out.println(getName() + "...拿到" + s1 + "开吃");
  28. }
  29. }
  30. }
  31. }
  32. }.start();
  33. }
  34. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注