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()来暂停当前进程执行,这样当前进程由运行转入阻塞,另一个由阻塞转入运行。
 public class TestSimpleThread extends Thread {
public TestSimpleThread(String str)
{
super(str);
} public void run()
{
for (int i = 0; i < 5; i ++)
{
System.out.println(i + " " + getName());
try
{
sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println(getName() + " Finish!");
} public static void main(String[] args)
{
new TestSimpleThread("Java").start();
new TestSimpleThread("C++").start();
}
}
  • 任何实现Runnable接口的类都支持多线程。实际上Thread类就实现了。
  • Runnable接口中只有一个方法run()。
  • this是执行线程体的目标对象。
  • 用继承Thread类方法比用实现Runnable接口方法更简单。继承Thread类时,定义的对象可以直接调用Thread类的方法;而实现Runnable接口必须定义Thread类的对象。
  • Runnable接口主要用于多继承。Java不支持多继承。如果设计一个有线程功能的Java Applet程序,由于Java Applet程序必须继承Applet类,因此不能再继承Thread类,只能通过继承Applet类并实现Runnable接口来完成设计。
 public class TestSimpleRunnable implements Runnable
{
private String str;
private Thread myThread; public TestSimpleRunnable(String str)
{
this.str = str;
} public void myStart()
{
myThread = new Thread(this, str);
myThread.start();
} public void run()
{
for (int i = 0; i < 5; i ++)
{
System.out.println(i + " " + myThread.getName());
try
{
Thread.sleep((long)(Math.random() * 1000));
} catch (InterruptedException e) {}
}
System.out.println(myThread.getName() + " Finished!");
} public static void main (String[] args)
{
new TestSimpleRunnable("Java").myStart();
new TestSimpleRunnable("C++").myStart();
}
}
  • 可以用Thread类提供的yield(),sleep()等方法和Object类提供的wait()和notify()方法在程序中改变线程的状态。
  • 创建状态时,仅仅是一个空的线程对象,还没被分配可运行的系统资源(主要是没有分配CPU时间)。
  • 运行线程必须具备两个条件:可使用的CPU,由线程调度程序分配;运行线程的代码和数据,由run()方法中提供。
  • 调用start()之后,线程处于可运行状态。
  • 可运行状态的线程在优先级队列中排队,等待操作系统的线程调度程序调度。
  • 可运行状态的线程来自三种情况:线程创建完毕;处于运行状态线程的时间片到;处于阻塞状态的线程的阻塞条件解除。
  • 线程调度程序按一定的线程调度原则,从等待运行的处于可运行状态的的线程队列中选择一个,获得CPU的使用权,变成运行状态。
  • 处于运行状态的线程首先执行run()。
  • 不可运行状态,就是处理器空闲也不能执行该线程。
  • 进入不可运行状态的原因通常有:线程调用sleep();调用Object类的wait();输入输出流中发生线程阻塞。
  • 进入死亡状态两种情况:自然消亡,run()执行完;应用程序停止运行。
  • 线程分组提供了统一管理多个线程而不需单独管理的机制。
  • Java语言的java.lang包中ThreadGroup子包提供了实现线程分组的类。
  • 一个线程放在一个线程组中后,它就是这个线程组中的永久成员,不能再把它加入其他线程组中。
  • 建立线程时,如果不指定线程组,系统会放到缺省线程组,即main。
  • 由于程序中没为线程名定义成员变量(因此没有定义构造方法),所以系统自动调用Thread类的构造方法给每个线程对象一个默认名。
 public class EnumerateTest extends Thread
{
public void listCurrentThreads()
{
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); int numThreads = currentGroup.activeCount();
System.out.println("numThreads = " + numThreads);
Thread[] listOfThreads = new Thread[numThreads - 1]; currentGroup.enumerate(listOfThreads); for (int i = 0; i < numThreads - 1; i ++)
{
System.out.println("Thread #" + i + " = " + listOfThreads[i].getName());
}
} public static void main (String[] args)
{
EnumerateTest a = new EnumerateTest();
EnumerateTest b = new EnumerateTest();
a.start();
b.start();
a.listCurrentThreads();
}
}
  • 线程调度程序在进行线程调度时要考虑线程的优先级,另外执行顺序还与操作系统的线程调度方式有关。
  • 线程的运行具有不确定性。
  • 操作系统线程调度方式:抢先式调度,更高优先级线程一旦可运行就被安排运行;独占方式,一直执行到完毕或者由于某种原因主动放弃CPU。
  • 线程优先级范围从1到10:MIN_PRIORITY为1,MAX_PRIORITY为10,NORM_PRIORITY为5。
 public class TestThreadPrio extends Thread
{
private String name; public TestThreadPrio (String name)
{
this.name = name;
} public void run()
{
for (int i = 0; i < 2; i ++)
{
System.out.println(name + " " + getPriority());
try
{
Thread.sleep((int)(Math.random() * 100));
}
catch(InterruptedException e) {}
}
} public static void main (String args[])
{
Thread t1 = new TestThreadPrio("Thread1");
t1.setPriority(Thread.MIN_PRIORITY);
Thread t2 = new TestThreadPrio("Thread2");
t2.setPriority(3);
Thread t3 = new TestThreadPrio("Thread3");
t3.setPriority(Thread.NORM_PRIORITY);
Thread t4 = new TestThreadPrio("Thread4");
t4.setPriority(7);
Thread t5 = new TestThreadPrio("Thread5");
t5.setPriority(Thread.MAX_PRIORITY); t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
  • 前面的是独立的、非同步的线程。
  • 线程间共享的数据,以及线程状态、行为的相互影响有两种:互斥和同步。
  • 共享资源指在程序中并行运行的若干线程操作相同的数据资源。
  • 生产者消费者模式I:生产者一次或多次提供货物,若干个消费者同时消费。
  • 因三个类都是public类,所以要分别存在三个文件中。
  • 生产者消费者模式I时,一定要保证操作的互斥,即一个共享资源每次只能由一个线程操作。这样保证并行运行的多个线程对共享资源操作的正确性。
  • 互斥锁是基于共享资源的互斥性设计的,用来标记多个并行运行的线程共享的资源。
  • JAVA关键字synchronized用来给共享资源加互斥锁。
  • 为共享资源加互斥锁有两种方法:锁定一个对象和一段代码;锁定一个方法。
  • 多个线程对同一个对象的互斥使用方式,该对象也成为互斥对象。
  • 锁定一个方法,锁定的是该方法所属类的对象,锁定的范围是整个方法,即在一个线程执行整个方法期间对该方法所属类的对象加互斥锁。
  • 互斥锁保证了并行运行的两个线程对共享资源队列操作的正确性。
 public class Queue
{
private int count;
private int front;
private int rear;
private char[] dat = new char[10]; public Queue()
{
count = 0;
front = 0;
rear = 0;
} public void push(char c)
{
if (count >= 10)
{
System.out.println("队列已满");
return;
}
dat[rear] = c;
System.out.println("插入的字符: " + c);
rear ++;
count ++;
} public synchronized char pop()
{
if (count <= 0)
{
System.out.println("队列已空");
return ' ';
}
char temp = dat[front];
System.out.println("删除的字符: " + temp);
try
{
Thread.sleep((int)(Math.random() * 100));
} catch (InterruptedException e) {}
front ++;
count --;
return temp;
}
}
 public class Consumer implements Runnable
{
private Queue qu; public Consumer(Queue s)
{
qu = s;
} public void run()
{
for (int i = 0; i < 3; i ++)
{
qu.pop();
}
}
}
 public class TestQueue
{
public static void main(String args[])
{
Queue qu = new Queue();
for (char c = 'a'; c <= 'd'; c ++)
qu.push(c);
Runnable sink = new Consumer(qu);
Thread t1 = new Thread(sink);
Thread t2 = new Thread(sink);
t1.start();
t2.start();
}
}
  • 生产者消费者模式II:消费者操作执行的前提条件是生产者操作已经生产了;生产者操作执行的前提条件是消费者已经消费了。
  • 信号量是一个标志,表示一种操作是否已执行完,另一种操作是否可以执行了。
  • wait()的所谓等待,是把当前线程从运行状态转入阻塞;notify()的所谓唤醒,是把等待线程从阻塞状态转入可运行。
  • wait()所在的代码段一定要加互斥锁synchornized。因为wait()把当前线程从运行状态转为阻塞后,还要释放互斥锁锁定的共享资源(否则其他同步线程无法运行),这样操作不允许中间被打断。
  • 信号量的作用是控制线程的同步操作。
  • wait()和notify()是配对的一组方法。
  • Thread类的sleep()和Object类的wait()有根本的不同:Thread类的sleep()只是延缓一段时间再执行后续代码。sleep()使处于运行状态的线程进入阻塞,但休眠时间一到,就从阻塞自动转入可运行。Object类的wait(),也是当前线程从运行转入阻塞,但wait()等待时间不确定,什么时候被唤醒依赖于其他线程的操作。另外sleep()休眠期间,若该段代码或方法加了互斥锁,则互斥锁锁定的共享资源不释放,而wait()将释放互斥锁锁定的共享资源,否则其他同步线程无法运行。
 public class Storage
{
private int goods;
private boolean available; public Storage(int g)
{
goods = g;
available = false;
} public synchronized void put(int g)
{
while (available == true)
{
try
{
wait();
} catch (InterruptedException e) {}
}
available = true; goods = g;
System.out.println("put goods = " + goods);
notify();
} public synchronized int get()
{
while (available == false)
{
try
{
wait();
} catch (InterruptedException e) {}
}
available = false; int temp = goods;
System.out.println("get goods = " + goods);
goods = 0;
notify();
return temp;
}
}
 public class Producer extends Thread
{
private Storage tb; public Producer(Storage c)
{
tb = c;
} public void run()
{
for (int i = 11; i < 16; i ++)
{
tb.put(i);
}
}
}
 public class Consumer extends Thread
{
private Storage tb; public Consumer(Storage c)
{
tb = c;
} public void run()
{
int g;
for (int i = 11; i < 16; i ++)
{
g = tb.get();
}
}
}
 public class TestStorage
{
public static void main(String[] args)
{
Storage com = new Storage(0); Producer p = new Producer(com); Consumer c = new Consumer(com); p.start();
c.start();
}
}
  • Java不支持多继承,但Java支持接口,且允许一个类中同时实现若干个接口。

学习笔记之JAVA多线程的更多相关文章

  1. 学习笔记《Java多线程编程实战指南》三

    3.1串行.并发与并行 1.串行:一件事做完接着做下一件事. 2.并发:几件事情交替进行,统筹资源. 3.并行:几件事情同时进行,齐头并进,各自运行直到结束. 多线程编程的实质就是将任务处理方式由串行 ...

  2. 学习笔记《Java多线程编程实战指南》二

    2.1线程属性 属性 属性类型及用途  只读属性  注意事项 编号(id) long型,标识不同线程  是  不适合用作唯一标识 名称(name) String型,区分不同线程  否  设置名称有助于 ...

  3. 学习笔记《Java多线程编程实战指南》一

    1.1什么是多线程编程 多线程编程就是以线程为基本抽象单位的一种编程范式,和面向对象编程是可以相容的,事实上Java平台中的一个线程就是一个对象.多线程编程不是线程越多越好,就像“和尚挑水”的故事一样 ...

  4. 学习笔记《Java多线程编程实战指南》四

    JAVA线程同步机制 线程同步机制:是一套用于协调线程间的数据访问及活动的机制,该机制用于保障线程安全以及实现这些线程的共同目标.java平台提供的线程同步机制包括锁.volatile关键字.fina ...

  5. 学习笔记之Java程序设计实用教程

    Java程序设计实用教程 by 朱战立 & 沈伟 学习笔记之JAVA多线程(http://www.cnblogs.com/pegasus923/p/3995855.html) 国庆休假前学习了 ...

  6. 转:学习笔记:delphi多线程学识

    学习笔记:delphi多线程知识 最近一直在温习旧的知识,刚好学习了一下Java的线程安全方面的知识,今天想起之前一直做的Delphi开发,所以还是有必要温习一下,看看这些不同的编程语言有什么不同之处 ...

  7. Android(java)学习笔记216:多线程断点下载的原理(Android实现)

    之前在Android(java)学习笔记215中,我们从JavaSE的角度去实现了多线程断点下载,下面从Android角度实现这个断点下载: 1.新建一个Android工程: (1)其中我们先实现布局 ...

  8. Android(java)学习笔记159:多线程断点下载的原理(Android实现)

    之前在Android(java)学习笔记215中,我们从JavaSE的角度去实现了多线程断点下载,下面从Android角度实现这个断点下载: 1. 新建一个Android工程: (1)其中我们先实现布 ...

  9. 学习笔记:delphi多线程知识

    最近一直在温习旧的知识,刚好学习了一下Java的线程安全方面的知识,今天想起之前一直做的Delphi开发,所以还是有必要温习一下,看看这些不同的编程语言有什么不同之处. Delphi的线程同步方法: ...

随机推荐

  1. 如何在一个frame中调用另一个frame中的javascript函数

    1.htm <script language="javascript">function test(){alert("测试")}</scrip ...

  2. Spring事务隔离级别和传播特性

    相信每个人都被问过无数次Spring声明式事务的隔离级别和传播机制吧!今天我也来说说这两个东西. 加入一个小插曲, 一天电话里有人问我声明式事务隔离级别有哪几种, 我就回答了7种, 他问我Spring ...

  3. tomcat server.xml配置详解

    由于 Tomcat 基于 Java,实际上在各种 Linux 发行版里的配置方法都大同小异,只是我看见在 Arch Linux 环境里搭建 Tomcat 的文章比较少,所以在 Arch Linux 实 ...

  4. java实现多继承

    方法:  接口+组合 理由:通过接口实现客户端的使用时多继承类的多类, 通过组合实现客户端内部类的实现相关功能(而且有些共用的功能可以不总是多次实现). public interface GMapOb ...

  5. fedora下的dropbox

  6. STM32的can现场总线实验心得

    最近在搞stm32实验板的can现场总线实验,之前只是搞过STC51的串口通信,相比之下,发觉can总线都挺复杂的.开始时,知道自己是新手,只知道can总线跟串行通信,485通信,I2C通信一样都是用 ...

  7. bzoj 1176 Mokia(CDQ分治,BIT)

    [题目链接]  http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=96974 [题意] 定义查询操作与修改操作:1 x y z 为 ...

  8. bzoj 2154 Crash的数字表格(莫比乌斯反演及优化)

    Description 今天的数学课上,Crash小朋友学习了最小公倍数(Least Common Multiple).对于两个正整数a和b,LCM(a, b)表示能同时被a和b整除的最小正整数.例如 ...

  9. Java 核心技术-集合-集合框架

    说在前面的话: 关于Core Java 集合方面的博文网上已经写烂了,为啥我还要写呢? 答:他们写的都很好,我也学到不少东西,如果把我当做一个系统的话,学习别人.看书.读源码是输入,但是往往形不成一个 ...

  10. 最简单的计算MD5方法

    原来写过一个计算MD5的程序,是用了一个叫MD5.pas的单元,使用起来还算简单,但还有更简单的办法,安装了indy就会有IdHashMessageDigest单元(delphi 7默认安装indy) ...