• 学习笔记之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支持接口,且允许一个类中同时实现若干个接口。
  • 相关阅读:
    windows命令提示符常用命令
    JAVA中定义不同进制整数
    进制转换
    win10配置jdk环境变量
    AI Gossip
    搜狗大数据总监、Polarr 联合创始人关于深度学习的分享交流 | 架构师小组交流会
    后端渲染实践——看掘金社区是如何实践的
    谢孟军:The State of Go | ECUG Con 精粹系列
    让你的 CDN 费用省 50% 以上!图片瘦身的正确姿势
    七牛云大数据平台建设实践
  • 原文地址:https://www.cnblogs.com/pegasus923/p/3995855.html
Copyright © 2020-2023  润新知