zoukankan      html  css  js  c++  java
  • 学习笔记之JAVA多线程

    Java程序设计实用教程 by 朱战立 & 沈伟

    孙鑫Java无难事

    Java 多线程与并发编程专题(http://www.ibm.com/developerworks/cn/java/j-concurrent/)

    Java 线程简介(http://www.ibm.com/developerworks/cn/education/java/j-threads/j-threads.html)

    http://www.cnblogs.com/liuling/p/2013-9-13-01.html

    书是入门级的,后面的专题是出自业界的技术文章。

    • 进程拥有自己独立的内存空间、数据等运行中需要的系统资源,它的状态和拥有的资源是完全独立的。
    • 当系统频繁的进行进程切换(进程调度),就要占用大量系统资源(主要是CPU时间)。
    • 有些子任务,它们使用的系统资源基本没变化,这时可以不进行系统使用资源的切换,这就提出了线程技术。
    • 线程是轻量级的进程。不能单独运行,必须在程序之内运行。一个进程之内的所有线程使用的系统资源是一样的。
    • 一个进程之内的线程切换,叫线程调度。
    • 对有些设计问题,可以将一个进程按不同功能划分为多个线程。
    • 多线程不仅使一个程序同时完成多项任务,为此消耗的资源也比进程方法少很多。
    • 线程生命周期内,有五种状态,即创建、可运行、运行中、不可运行(阻塞)和死亡状态。
    • Thread类和Runnable接口支持了线程的功能。它们都在java.lang包中,不需要import。
    • 继承Thread类,并实现run()方法,来实现多线程。
    • sleep()发出系统定义的中断异常,catch模块处理该中断异常,实现当前线程休眠。线程休眠就进入不可运行状态(阻塞),休眠时间一到,又进入可运行状态,排队等待线程调度程序调度进入运行状态。
    • main()也是一个线程,称为主线程。
    • 为了给多线程中每个线程执行的时间和机会,通常使用sleep()来暂停当前进程执行,这样当前进程由运行转入阻塞,另一个由阻塞转入运行。
     1 public class TestSimpleThread extends Thread {
     2     public TestSimpleThread(String str)
     3     {
     4         super(str);
     5     }
     6     
     7     public void run()
     8     {
     9         for (int i = 0; i < 5; i ++)
    10         {
    11             System.out.println(i + " " + getName());
    12             try
    13             {
    14                 sleep((long)(Math.random() * 1000));
    15             } catch (InterruptedException e) {}
    16         }
    17         System.out.println(getName() + " Finish!");
    18     }
    19     
    20     public static void main(String[] args)
    21     {
    22         new TestSimpleThread("Java").start();
    23         new TestSimpleThread("C++").start();
    24     }
    25 }
    • 任何实现Runnable接口的类都支持多线程。实际上Thread类就实现了。
    • Runnable接口中只有一个方法run()。
    • this是执行线程体的目标对象。
    • 用继承Thread类方法比用实现Runnable接口方法更简单。继承Thread类时,定义的对象可以直接调用Thread类的方法;而实现Runnable接口必须定义Thread类的对象。
    • Runnable接口主要用于多继承。Java不支持多继承。如果设计一个有线程功能的Java Applet程序,由于Java Applet程序必须继承Applet类,因此不能再继承Thread类,只能通过继承Applet类并实现Runnable接口来完成设计。
     1 public class TestSimpleRunnable implements Runnable
     2 {
     3     private String str;
     4     private Thread myThread;
     5     
     6     public TestSimpleRunnable(String str)
     7     {
     8         this.str = str;
     9     }
    10     
    11     public void myStart()
    12     {
    13         myThread = new Thread(this, str);
    14         myThread.start();
    15     }
    16     
    17     public void run()
    18     {
    19         for (int i = 0; i < 5; i ++)
    20         {
    21             System.out.println(i + " " + myThread.getName());
    22             try
    23             {
    24                 Thread.sleep((long)(Math.random() * 1000));
    25             } catch (InterruptedException e) {}
    26         }
    27         System.out.println(myThread.getName() + " Finished!");
    28     }
    29     
    30     public static void main (String[] args)
    31     {
    32         new TestSimpleRunnable("Java").myStart();
    33         new TestSimpleRunnable("C++").myStart();
    34     }
    35 }
    • 可以用Thread类提供的yield(),sleep()等方法和Object类提供的wait()和notify()方法在程序中改变线程的状态。
    • 创建状态时,仅仅是一个空的线程对象,还没被分配可运行的系统资源(主要是没有分配CPU时间)。
    • 运行线程必须具备两个条件:可使用的CPU,由线程调度程序分配;运行线程的代码和数据,由run()方法中提供。
    • 调用start()之后,线程处于可运行状态。
    • 可运行状态的线程在优先级队列中排队,等待操作系统的线程调度程序调度。
    • 可运行状态的线程来自三种情况:线程创建完毕;处于运行状态线程的时间片到;处于阻塞状态的线程的阻塞条件解除。
    • 线程调度程序按一定的线程调度原则,从等待运行的处于可运行状态的的线程队列中选择一个,获得CPU的使用权,变成运行状态。
    • 处于运行状态的线程首先执行run()。
    • 不可运行状态,就是处理器空闲也不能执行该线程。
    • 进入不可运行状态的原因通常有:线程调用sleep();调用Object类的wait();输入输出流中发生线程阻塞。
    • 进入死亡状态两种情况:自然消亡,run()执行完;应用程序停止运行。
    • 线程分组提供了统一管理多个线程而不需单独管理的机制。
    • Java语言的java.lang包中ThreadGroup子包提供了实现线程分组的类。
    • 一个线程放在一个线程组中后,它就是这个线程组中的永久成员,不能再把它加入其他线程组中。
    • 建立线程时,如果不指定线程组,系统会放到缺省线程组,即main。
    • 由于程序中没为线程名定义成员变量(因此没有定义构造方法),所以系统自动调用Thread类的构造方法给每个线程对象一个默认名。
     1 public class EnumerateTest extends Thread
     2 {
     3     public void listCurrentThreads()
     4     {
     5         ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
     6         
     7         int numThreads = currentGroup.activeCount();
     8         System.out.println("numThreads = " + numThreads);
     9         Thread[] listOfThreads = new Thread[numThreads - 1];
    10         
    11         currentGroup.enumerate(listOfThreads);
    12         
    13         for (int i = 0; i < numThreads - 1; i ++)
    14         {
    15             System.out.println("Thread #" + i + " = " + listOfThreads[i].getName());
    16         }
    17     }
    18     
    19     public static void main (String[] args)
    20     {
    21         EnumerateTest a = new EnumerateTest();
    22         EnumerateTest b = new EnumerateTest();
    23         a.start();
    24         b.start();
    25         a.listCurrentThreads();
    26     }
    27 }
    • 线程调度程序在进行线程调度时要考虑线程的优先级,另外执行顺序还与操作系统的线程调度方式有关。
    • 线程的运行具有不确定性。
    • 操作系统线程调度方式:抢先式调度,更高优先级线程一旦可运行就被安排运行;独占方式,一直执行到完毕或者由于某种原因主动放弃CPU。
    • 线程优先级范围从1到10:MIN_PRIORITY为1,MAX_PRIORITY为10,NORM_PRIORITY为5。
     1 public class TestThreadPrio extends Thread
     2 {
     3     private String name;
     4     
     5     public TestThreadPrio (String name)
     6     {
     7         this.name = name;
     8     }
     9     
    10     public void run()
    11     {
    12         for (int i = 0; i < 2; i ++)
    13         {
    14             System.out.println(name + " " + getPriority());
    15             try
    16             {
    17                 Thread.sleep((int)(Math.random() * 100));                
    18             }
    19             catch(InterruptedException e) {}
    20         }
    21     }
    22     
    23     public static void main (String args[])
    24     {
    25         Thread t1 = new TestThreadPrio("Thread1");
    26         t1.setPriority(Thread.MIN_PRIORITY);
    27         Thread t2 = new TestThreadPrio("Thread2");
    28         t2.setPriority(3);
    29         Thread t3 = new TestThreadPrio("Thread3");
    30         t3.setPriority(Thread.NORM_PRIORITY);
    31         Thread t4 = new TestThreadPrio("Thread4");
    32         t4.setPriority(7);
    33         Thread t5 = new TestThreadPrio("Thread5");
    34         t5.setPriority(Thread.MAX_PRIORITY);
    35         
    36         t1.start();
    37         t2.start();
    38         t3.start();
    39         t4.start();
    40         t5.start();
    41     }
    42 }
    • 前面的是独立的、非同步的线程。
    • 线程间共享的数据,以及线程状态、行为的相互影响有两种:互斥和同步。
    • 共享资源指在程序中并行运行的若干线程操作相同的数据资源。
    • 生产者消费者模式I:生产者一次或多次提供货物,若干个消费者同时消费。
    • 因三个类都是public类,所以要分别存在三个文件中。
    • 生产者消费者模式I时,一定要保证操作的互斥,即一个共享资源每次只能由一个线程操作。这样保证并行运行的多个线程对共享资源操作的正确性。
    • 互斥锁是基于共享资源的互斥性设计的,用来标记多个并行运行的线程共享的资源。
    • JAVA关键字synchronized用来给共享资源加互斥锁。
    • 为共享资源加互斥锁有两种方法:锁定一个对象和一段代码;锁定一个方法。
    • 多个线程对同一个对象的互斥使用方式,该对象也成为互斥对象。
    • 锁定一个方法,锁定的是该方法所属类的对象,锁定的范围是整个方法,即在一个线程执行整个方法期间对该方法所属类的对象加互斥锁。
    • 互斥锁保证了并行运行的两个线程对共享资源队列操作的正确性。
     1 public class Queue 
     2 {
     3     private int count;
     4     private int front;
     5     private int rear;
     6     private char[] dat = new char[10];
     7     
     8     public Queue()
     9     {
    10         count = 0;
    11         front = 0;
    12         rear = 0;
    13     }
    14     
    15     public void push(char c)
    16     {
    17         if (count >= 10)
    18         {
    19             System.out.println("队列已满");
    20             return;
    21         }
    22         dat[rear] = c;
    23         System.out.println("插入的字符: " + c);
    24         rear ++;
    25         count ++;        
    26     }
    27     
    28     public synchronized char pop()
    29     {
    30         if (count <= 0)
    31         {
    32             System.out.println("队列已空");
    33             return ' ';
    34         }
    35         char temp = dat[front];
    36         System.out.println("删除的字符: " + temp);
    37         try
    38         {
    39             Thread.sleep((int)(Math.random() * 100));
    40         } catch (InterruptedException e) {}
    41         front ++;
    42         count --;
    43         return temp;
    44     }
    45 }
     1 public class Consumer implements Runnable
     2 {
     3     private Queue qu;
     4     
     5     public Consumer(Queue s)
     6     {
     7         qu = s;
     8     }
     9     
    10     public void run()
    11     {
    12         for (int i = 0; i < 3; i ++)
    13         {
    14             qu.pop();
    15         }
    16     }
    17 }
     1 public class TestQueue 
     2 {
     3     public static void main(String args[])
     4     {
     5         Queue qu = new Queue();
     6         for (char c = 'a'; c <= 'd'; c ++)
     7             qu.push(c);
     8         Runnable sink = new Consumer(qu);
     9         Thread t1 = new Thread(sink);
    10         Thread t2 = new Thread(sink);
    11         t1.start();
    12         t2.start();
    13     }
    14 }
    • 生产者消费者模式II:消费者操作执行的前提条件是生产者操作已经生产了;生产者操作执行的前提条件是消费者已经消费了。
    • 信号量是一个标志,表示一种操作是否已执行完,另一种操作是否可以执行了。
    • wait()的所谓等待,是把当前线程从运行状态转入阻塞;notify()的所谓唤醒,是把等待线程从阻塞状态转入可运行。
    • wait()所在的代码段一定要加互斥锁synchornized。因为wait()把当前线程从运行状态转为阻塞后,还要释放互斥锁锁定的共享资源(否则其他同步线程无法运行),这样操作不允许中间被打断。
    • 信号量的作用是控制线程的同步操作。
    • wait()和notify()是配对的一组方法。
    • Thread类的sleep()和Object类的wait()有根本的不同:Thread类的sleep()只是延缓一段时间再执行后续代码。sleep()使处于运行状态的线程进入阻塞,但休眠时间一到,就从阻塞自动转入可运行。Object类的wait(),也是当前线程从运行转入阻塞,但wait()等待时间不确定,什么时候被唤醒依赖于其他线程的操作。另外sleep()休眠期间,若该段代码或方法加了互斥锁,则互斥锁锁定的共享资源不释放,而wait()将释放互斥锁锁定的共享资源,否则其他同步线程无法运行。
     1 public class Storage 
     2 {
     3     private int goods;
     4     private boolean available;
     5     
     6     public Storage(int g)
     7     {
     8         goods = g;
     9         available = false;
    10     }
    11     
    12     public synchronized void put(int g)
    13     {
    14         while (available == true)
    15         {
    16             try 
    17             {
    18                 wait();
    19             } catch (InterruptedException e) {}
    20         }
    21         available = true;
    22         
    23         goods = g;
    24         System.out.println("put goods = " + goods);
    25         notify();
    26     }
    27     
    28     public synchronized int get()
    29     {
    30         while (available == false)
    31         {
    32             try
    33             {
    34                 wait();
    35             } catch (InterruptedException e) {}
    36         }
    37         available = false;
    38         
    39         int temp = goods;
    40         System.out.println("get goods = " + goods);
    41         goods = 0;
    42         notify();
    43         return temp;
    44     }
    45 }
     1 public class Producer extends Thread
     2 {
     3     private Storage tb;
     4     
     5     public Producer(Storage c)
     6     {
     7         tb = c;
     8     }
     9     
    10     public void run()
    11     {
    12         for (int i = 11; i < 16; i ++)
    13         {
    14             tb.put(i);
    15         }
    16     }
    17 }
     1 public class Consumer extends Thread
     2 {
     3     private Storage tb;
     4     
     5     public Consumer(Storage c)
     6     {
     7         tb = c;
     8     }
     9     
    10     public void run()
    11     {
    12         int g;
    13         for (int i = 11; i < 16; i ++)
    14         {
    15             g = tb.get();
    16         }
    17     }
    18 }
     1 public class TestStorage 
     2 {
     3     public static void main(String[] args)
     4     {
     5         Storage com = new Storage(0);
     6         
     7         Producer p = new Producer(com);
     8         
     9         Consumer c = new Consumer(com);
    10         
    11         p.start();
    12         c.start();
    13     }
    14 }
    •  Java不支持多继承,但Java支持接口,且允许一个类中同时实现若干个接口。
  • 相关阅读:
    2021.1.28 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2021.1.23 个人rating赛补题报告
    2020.12.14 个人训练赛补题报告
    2020.11.28 2020团体程序设计天梯赛补题报告
    2020.12.3 Codeforces Beta Round #73(Div2)补题报告
    Xhorse VVDI Prog V5.0.6 is Ready for BCM2 Adapter
    Program 2021 Ford Bronco All Keys Lost using VVDI Key Tool Plus
    Xhorse VVDI Prog V5.0.4 Software Update in July 2021
    How to use Xhorse VVDI2 to Exchange BMW FEM/BDC Module?
  • 原文地址:https://www.cnblogs.com/pegasus923/p/3995855.html
Copyright © 2011-2022 走看看