经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
java核心技术-多线程基础
来源:cnblogs  作者:丁可乐  时间:2018/11/8 9:56:35  对本文有异议

进程、线程

? 进程(Process) 是程序的运行实例。例如,一个运行的 Eclipse 就是一个进程。进程是程序向操作系统申请资源(如内存空间和文件句柄)的基本单位。线程(Thread)是进程中可独立执行的最小单位。一个进程可以包含多个线程。进程和线程的关系,好比一个营业中的饭店与其正在工作的员工之间的关系。

1.1 线程的创建、启动与运行

在 Java 中实现多线程主要用两种手段,一种是继承 Thread 类,另一种就是实现 Runnable 接口。(当然还有Callable和线程池)。下面我们就分别来介绍这两种方式的使用,其他请关注此博客下文。

(1).继承Thread的类

  1. public class PrimeThread extends Thread{
  2. //线程执行体
  3. @Override
  4. public void run(){
  5. for(int i = 0; i < 100; i++){
  6. if(i % 2 == 0){
  7. System.out.println(Thread.currentThread().getName() + "=" + i);
  8. }
  9. }
  10. }
  11. }
  1. public class TestThread {
  2. public static void main(String[] args) {
  3. //新建一个线程
  4. PrimeThread p1 = new PrimeThread();
  5. //启动一个线程
  6. p1.start();
  7. PrimeThread p2 = new PrimeThread();
  8. p2.start();
  9. for(int i = 0; i < 100; i++ ){
  10. if(i % 2 == 0){
  11. System.out.println(Thread.currentThread().getName() + "=" + i);
  12. }
  13. }
  14. }
  15. }

(2).实现 Runnable接口

  1. public class Ticket implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while(ticket > 0){
  6. System.out.println(Thread.currentThread().getName() + "=" + --ticket);
  7. }
  8. }
  9. }
  1. public class TestThread2 {
  2. public static void main(String[] args) {
  3. Ticket ticket = new Ticket();
  4. //虽然是实现了Runnable接口 本质上只是实现了线程执行体 启动工作还是需要Thread类来进行
  5. Thread t1 = new Thread(ticket,"售票窗口一");
  6. t1.start();
  7. Thread t2 = new Thread(ticket,"售票窗口二");
  8. t2.start();
  9. Thread t3 = new Thread(ticket,"售票窗口三");
  10. t3.start();
  11. }
  12. }

两种实现方式的对比:

1.从面向对象编程角度看:第一种创建方式(继承Thread类) 是一种基础继承的技术,第二种创建方式(以Runnable接口实例为构造器参数直接通过new创建Thread实例)是一种基础组合的技术。方式二不仅会避免单继承的尴尬,也会降低类与类之间的耦合性。

2.从对象共享角度看:第二种创建方式意味着多个线程实例可以共享同一个Runnable实例。而第一种方式则需要依赖static关键字来完成操作。

1.2 线程的控制

Java的调度方法

同优先级线程组成先进先出队列(先到先服务),使用时间片策略

对高优先级,使用优先调度的抢占式策略

Thread类的相关方法:

  • sleep(long millis) : 是 Thread 类中的静态方法,使当前线程进入睡眠状态
  • join() / join(long millis) : 是一个实例方法,使当前线程进入阻塞状态
  • interrupt() : 中断阻塞状态的线程
  • isAlive() : 判断当前线程是否处于存活状态
  • yield() : 线程让步
  1. public class TestThread3 {
  2. public static void main(String[] args) throws Exception {
  3. Thread t1 = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for(int i = 1; i < 100;i++){
  7. if(i % 2 == 0){
  8. System.out.println(Thread.currentThread().getName() + "=" + i);
  9. }
  10. }
  11. }
  12. },"线程1");
  13. t1.start();
  14. //线程1在 sleep之前就执行完了
  15. t1.sleep(10000);
  16. //join方法 迫使t2 必须等线程1 执行完 才能执行 然而 t1输出完自己的 睡着了 t2被迫等了10秒
  17. t1.join();
  18. Thread t2 = new Thread(new Runnable() {
  19. @Override
  20. public void run() {
  21. for(int i = 1; i < 100;i++){
  22. if(i % 2 != 0){
  23. System.out.println(Thread.currentThread().getName() + "=" + i);
  24. }
  25. }
  26. }
  27. },"线程2");
  28. t2.start();
  29. }
  30. }

1.3 线程的同步

线程同步:模拟售票程序,实现三个窗口同时售票 100 张 (1.1案例)

问题:当三个窗口同时访问共享数据时,产生了无序、重复、超额售票等多线程安全问题

解决:将需要访问的共享数据“包起来”,视为一个整体,确保一次只能有一个线程执行流访问该“共享数据”

Java给上述问题提供了几种相应的解决方法

(1).同步代码块

synchronized(同步监视器){

//需要访问的共享数据

}

同步监视器 : 俗称“锁”,可以使用任意对象的引用充当,注意确保多个线程持有同一把锁(同一个对象)

(2).同步方法

同步方法 : 在方法声明处加 synchronized. 注意:非静态同步方法隐式的锁 ---- this

例如:

public synchronized void show(){}

(3).同步锁

同步锁 : Lock 接口

同步代码块

  1. public class SafeTicket implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while(true){
  6. //使用同步代码块
  7. synchronized (this) {
  8. if(ticket > 0){
  9. System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" + --ticket);
  10. }
  11. }
  12. }
  13. }
  14. }

同步方法:

  1. public class SafeTicket implements Runnable{
  2. private int ticket = 100;
  3. @Override
  4. public void run() {
  5. while(true){
  6. //使用同步代码块
  7. sale();
  8. }
  9. }
  10. public synchronized void sale(){
  11. if(ticket > 0){
  12. System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" +
  13. --ticket);
  14. }
  15. }
  16. }

同步锁

  1. public class SafeTicket implements Runnable{
  2. private int ticket = 100;
  3. private Lock l = new ReentrantLock();
  4. @Override
  5. public void run() {
  6. while(true){
  7. l.lock();
  8. try {
  9. if(ticket > 0){
  10. System.out.println(Thread.currentThread().getName() + " 完成售票,余票:" +
  11. --ticket);
  12. }
  13. } finally {
  14. l.unlock();//释放锁
  15. }
  16. }
  17. }
  18. }

死锁

死锁 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于 死锁 状态或系统产生了 死锁 ,这些永远在互相等待的进程称为 死锁 进程

  1. public class TestDeadLock {
  2. public static void main(String[] args) {
  3. final StringBuffer s1 = new StringBuffer();
  4. final StringBuffer s2 = new StringBuffer();
  5. new Thread() {
  6. public void run() {
  7. synchronized (s1) {
  8. s2.append("A");
  9. try {
  10. Thread.sleep(10);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. synchronized (s2) {
  15. s2.append("B");
  16. System.out.print(s1);
  17. System.out.print(s2);
  18. }
  19. }
  20. }
  21. }.start();
  22. new Thread() {
  23. public void run() {
  24. synchronized (s2) {
  25. s2.append("C");
  26. synchronized (s1) {
  27. s1.append("D");
  28. System.out.print(s2);
  29. System.out.print(s1);
  30. }
  31. }
  32. }
  33. }.start();
  34. }
  35. }

1.4 线程的通信

在 java.lang.Object 类中:

wait() : 使当前“同步监视器”上的线程进入等待状态。同时释放锁

notify() / notifyAll() : 唤醒当前“同步监视器”上的(一个/所有)等待状态的线程

注意:上述方法必须使用在同步中

场景1:使用两个线程打印 1-100 线程1和线程2交替打印

  1. public class MyThread implements Runnable{
  2. int i = 0;
  3. @Override
  4. public void run() {
  5. while(true){
  6. synchronized (this) {
  7. this.notify();
  8. if(i <= 100){
  9. System.out.println(Thread.currentThread().getName() + "=" + i++);
  10. }
  11. try {
  12. this.wait();
  13. } catch (InterruptedException e) {
  14. }
  15. }
  16. }
  17. }
  18. }
  1. public class TestThread4 {
  2. public static void main(String[] args) {
  3. MyThread myThread = new MyThread();
  4. Thread t1 = new Thread(myThread,"线程1");
  5. Thread t2 = new Thread(myThread,"线程2");
  6. t1.start();
  7. t2.start();
  8. }
  9. }

经典例题:生产者/消费者问题

  • 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,
  • 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
  • 如果店中有空位放产品了再通知生产者继续生产;
  • 如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
  1. public class TestProduct {
  2. public static void main(String[] args) {
  3. Clerk clerk = new Clerk();
  4. Productor pro = new Productor(clerk);
  5. Customer cus = new Customer(clerk);
  6. new Thread(pro).start();
  7. new Thread(cus).start();
  8. }
  9. }
  10. // 店员
  11. class Clerk {
  12. private int product;
  13. // 进货
  14. public synchronized void getProduct() {
  15. if (product >= 20) {
  16. System.out.println("产品已满!");
  17. try {
  18. wait();
  19. } catch (InterruptedException e) {
  20. }
  21. } else {
  22. System.out.println("生产者生产了第" + ++product + " 个产品");
  23. notifyAll();
  24. }
  25. }
  26. // 卖货
  27. public synchronized void saleProduct() {
  28. if (product <= 0) {
  29. System.out.println("缺货!");
  30. try {
  31. wait();
  32. } catch (InterruptedException e) {
  33. }
  34. } else {
  35. System.out.println("消费者消费了第" + --product + " 个产品");
  36. notifyAll();
  37. }
  38. }
  39. }
  40. // 生产者
  41. class Productor implements Runnable {
  42. private Clerk clerk;
  43. public Productor() {
  44. }
  45. public Productor(Clerk clerk) {
  46. this.clerk = clerk;
  47. }
  48. public Clerk getClerk() {
  49. return clerk;
  50. }
  51. public void setClerk(Clerk clerk) {
  52. this.clerk = clerk;
  53. }
  54. @Override
  55. public void run() {
  56. while (true) {
  57. clerk.getProduct();
  58. }
  59. }
  60. }
  61. // 消费者
  62. class Customer implements Runnable {
  63. private Clerk clerk;
  64. public Customer() {
  65. }
  66. public Customer(Clerk clerk) {
  67. this.clerk = clerk;
  68. }
  69. public Clerk getClerk() {
  70. return clerk;
  71. }
  72. public void setClerk(Clerk clerk) {
  73. this.clerk = clerk;
  74. }
  75. @Override
  76. public String toString() {
  77. return "Customer [clerk=" + clerk + "]";
  78. }
  79. @Override
  80. public void run() {
  81. while(true){
  82. clerk.saleProduct();
  83. }
  84. }
  85. }
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号