java多线程,Java 多线程 详解

Java 多线程
1
u Java 线程及进程
u Java 中的线程类(Thread)
u Java 中的 Runnable 接口
u 两种实现多线程方式的对比分析
u Java 中线程的同步
早期的Winodw3.x 下,进程是最小运行单位.在Window95/NT 下,每个进程还可以
启动几个线程,比如每下载一个文件可以单独开一个线程.在 Windows 95/NT 下,
线程是最小单位.WINDOWS 的多任务特性使得线程之间独立进行,但是它们彼此
共享虚拟空间,也就是共用变量,线程有可能会同时操作一片内存.
n (1) 线程的划分尺度小于进程,使得多线程程序的并发性高
n (2)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极
大地提高了程序的运行效率.
n (3)每个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,
但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线
程执行控制.
n 1. 虚拟的 CPU,由 java.lang.Thread 类封装和虚拟;
n 2. CPU 所执行的代码,传递给 Thread 类对象;
n 3. CPU 所处理的数据,传递给 Thread 类对象。
Java 中的线程类(Thread)
n Java 的线程是通过 java.lang.Thread 类来控制的,一个 Thread 类的对象
代表一个线程,而且只能代表一个线程.通过Thread 类和它定义的对象,我
们可以获得当前线程对象,获取某一线程的名称,可以实现控制线程暂停
一段时间等功能,
Java 多线程
2
n package com.yzy;
n import java.sql.*;
n class Thread1 extends Thread {
n private int n;
n public Thread1(int n) {
n this.n = n;
n }
n public void run() {
n for (int i = 0&#894; i < 100&#894; i++) {
n System.out.println("第" + n + "个线程的第" + i + "次运行")&#894;
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
n Thread1 t1 = new Thread1(1)&#894;
n Thread1 t2 = new Thread1(2)&#894;
n t1.run()&#894;单线程运行
n t2.run()&#894;单线程运行
n System.out.println("主线程结束")&#894;
n }
n }
n 程序执行完t1.run(),再执行t2.run(),最后结束主线程,显然这是一个单
Java 多线程
3
线程程序.
n 一个代码段被执行,一定是在某个线程上运行,代码与线程密不可分,同一
段代码可以与多个线程相关联,在多个线程上执行的也可以是相同的一段
代码.
n 我们对上面的代码进行修改,让 Thread1 继承 Thread,用 Thread1 的对象
调用它继承的方法 start(),修改代码如下:
n package com.yzy&#894;
n import java.sql.*&#894;
n class Thread1 extends Thread {
n private int n&#894;
n public Thread1(int n) {
n this.n = n&#894;
n }
n public void run() {
n for (int i = 0&#894; i < 100&#894; i++) {
n System.out.println("第" + n + "个线程的第" + i + "次运行")&#894;
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
n Thread1 t1 = new Thread1(1)&#894;
n Thread1 t2 = new Thread1(2)&#894;
Java 多线程
4
n t1.start()&#894;// 多线程运行
n t2.start()&#894;// 多线程运行
n System.out.println("主线程结束")&#894;
n }
n }
n 从结果可以看到:主线程先结束,然后两个线程交替运行.
n 上面的代码让Thread1 类继承了Thread 类,也就是Thread1 具备了Thread
类的全部特点,程序没有直接调用Thread1 类对象的run 方法,而是调用了
该类对象从 Thread 类继承来的 start 方法.运行后,能够看到两个 for 循
环处的代码同时交替运行.
n 在单线程中,主线程必须等到Thread1.run 函数返回后才能继续往下执行,
而在多线程中 ,main() 函数调用 Thread1.start() 方法启动了
Thread1.run()函数后,main 函数不等待 thread1.run 返回就继续运
行,Thread.run()函数在一边独自运行,不影响原来的 main()函数的运行,
这好比将一个 2G 的 CPU 分成了两个 1G 的 CPU,在一个 CPU 上运行 main 函
数,而Thread1.run 在另一个CPU 上运行.但他们都在向同一个显示器上输
出,所以我们看到两个 for 循环处的代码同时交替运行.
Java 中的 Runnable 接口
n 在 JDK 文档中,类 Thread 有一个构造方法 Thread(Runnable target),从
JDK 文档中查看Runnable 接口类的帮助,该接口中只有一个run 方法,当使
用 Thread(Runnable target)方法创建线程对象时,需为该方法传递一个
实现了 Runnable 接口的类对象, 这样创建的线程将调用那个实现
Runnable 接口的类对象中的 run()方法为其运行代码,而不再调用 Thread
Java 多线程
5
类的 run 方法了.
n package com.yzy&#894;
n class Thread1 implements Runnable {
n private int n&#894;
n public Thread1(int n) {
n this.n = n&#894;
n }
n public void run() {
n for (int i = 0&#894; i < 200&#894; i++) {
n System.out.println("第" + n + "个线程"
n + Thread.currentThread().toString() + "的第" + i + "次运行")&#894;
n }
n }
n }
两种实现多线程方式的对比分析
n 既然直接继承 Thread 类和实现 Runnable 接口都能实现多线程,那么这两
种实现多线程方式在应用上有什么区别呢
n 为了回答这个问题,我们通过编写一个应用程序,来进行比较分析.我们用
程序来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的 100
张车票,一个售票点用一个线程来表示.
n package com.yzy&#894;
n class ThreadTest extends Thread {
n private int tickets = 100&#894;
Java 多线程
6
n public void run() {
n while (true) {
n if (tickets > 0) {
n System.out.println(Thread.currentThread().getName()
n + " is saling ticket " + tickets)
&#894;
n }
n }
n }
n }
n public class ThreadDemo {
n public static void main(String[] args) {
n ThreadTest t1 = new ThreadTest ()&#894;
n t1.start()&#894;
n t1.start()&#894;
n t1.start()&#894;
n t1.start()&#894;
n }
n 在上面的代码中,我们用 ThreadTest 类模拟售票处的售票过程,run 方法
中的每一次循环总票数减 1,模拟卖出一张车票,同时该车票号打印出来,
直到剩余的票数到零为止.在ThreadDemo 类的main 方法中,我们创建了一
个线程对象,并重复启动四次,希望通过这种方式产生四个线程,但结果我
们发现其实只有一个线程在运行,这个结果告诉我们:一个线程对象只能
启动一个线程,无论你调用多少遍 start()方法,结果都只有一个线程.
Java 多线程
7
修改 ThreadDemo,在 main 方法中创建四个 ThreadTest 对象
n public class ThreadDemo{
n public static void main(String[] args) {
n new Thread1().start()&#894;
n new Thread1().start()&#894;
n new Thread1().start()&#894;
n new Thread1().start()&#894;
n }
n }
n 从运行结果我们看到的是票号被打印了四遍,即四个线程各自卖各自的
100 张票,而不是去卖共同的 100 张票.
n 我们创建了四个 ThreadTest 对象, 就等于创建了四个资源, 每个
ThreadTest 对象中都有 100 张票,每个线程在独立地处理各自的资源.
n 经过上面的实验和分析,可以总结出,要实现这个铁路售票模拟程序,我们
只能创建一个资源对象(该对象中包含要发售的那 100 张票),但要创建多
个线程去处理同一个资源对象,并且每个线程上所运用的是相同的程序代
码.
n package com.yzy&#894;
n class Thread1 implements Runnable {
n private int tickets = 100&#894;
n public void run() {
n while (true) {
n if (tickets > 0) {
Java 多线程
8
n System.out.println(Thread.currentThread().getName()
n + " is saling ticket " + tickets)
&#894;
n }
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
n Thread1 t = new Thread1()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n }
n }
n 上面的程序中,创建了四个线程,每个线程调用的是同一个 ThreadTest 对
象中的 run()方法,访问的是同一个对象中的变量(tickets)的实例,这个
程序满足了我们的需求.
n 实现 Runnable 接口相对于继承了 Thread 类来说,有如下好处:
n 1)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟 CPU(线程)
同程序的代码,数据有效分离,较好地体现了面向对象的设计思想.
n 2)可以避免由于 Java 的单继承性带来的局限.我们经常遇到这样的情况,
即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能
Java 多线程2
9
同时有两个父类,所以不能用继承Thread 类的方式,那么,这个类只能采用
实现 Runnable 接口的方式了.
n 3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的,
当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码.
多个线程可以操作相同的数据,与它们的代码无关.当共享访问的对象时,
即它们共享相同的数据.当线程被构造时,需要的代码和数据通过一个对
象作为构造函数实参传递进去,这个对象就是一个实现了Runnable 接口的
类的实例.
n 在上节车票的程序代码中,极可能碰到一种意外,就是同一张票号被打印
两次或多次,也可能出现打印0 甚至负数的票号.这个意外发生在下面这部
分代码处:
n if(tickets>0){
n System.out.println(Thread.currentThread().getName()+”is saling ticket”
+tickets)
&#894;
n }
n 假设 tickets 的值为 1 的时候,线程 1 刚执行完 if(tickets>0)这行代码,
正准备执行下面的代码,就在这时,操作系统CPU 又切回到了线程2 上执行,
此时tickets 的值仍为1,线程2 执行完上面两行代码,tickets 的值变为0
后,CPU 又切回到了线程 1 上执行,线程 1 不会再执行 if(tickets>0)这行
代码,因为先前已经比较过了,并且比较的结果为真,线程 1 将直接往下执
行这行代码:
n System.out.println(Thread.currentThread().getName()+” is saling
ticket ”+tickets)
&#894;
Java 多线程
10
n 要想立即见到这种意外,可用在程序中调用 Thread.sleep()静态方法来刻
意造成线程间的这种切换,Thread.sleep()方法迫使线程执行到该处后暂
停执行,让出 CPU 给别的线程,在给定时间(这里是毫秒)后,CPU 回到刚才
暂停的线程上执行.
n class Thread1 implements Runnable {
n private int tickets = 100&#894;
n public void run() {
n while (true) {
n if (tickets > 0) {
n try {
n Thread.sleep(10)&#894;
n } catch (Exception e) {
n e.printStackTrace()&#894;
n }
n System.out.println(Thread.currentThread().getName()
n + " is saling ticket " + tickets)
&#894;
n }
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
n Thread1 t = new Thread1()&#894;
Java 多线程
11
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n }
n }
n 在上面的程序代码中,我们故意造成线程执行完 if(tickets>0)语句后,执
行 Thread.sleep(10),以让出 CPU 给别的线程.
同步代码块
n 如何避免上面的这种意外,如何让我们的程序是线程安全的呢?这就是我
们要为大家讲解的如何实现线程间的同步问题.要解决上面的问题,我们
必须保证下面这段代码的原子性:
n System.out.println(Thread.currentThread().getName()+”is saling
ticket”+tickets--);
n 即当一个线程运行到 if(tickets>0)后,CPU 不去执行其他线程中的,可能
影响当前线程中的下句代码的执行结果的代码块,必须等到下一句执行完
后才能去执行其他线程中的有关代码块.这段代码就好比一座独木桥,任
一时刻,只能有一个人在桥上行走,程序中不能有多个线程同时在这两句
代码之间执行,这就是线程同步.
n 在上面的代码中,我们将这些需要具有原子性的代码,放入 synchronized
语句内,形成了同步代码块.在同一时刻只能有一个线程可以进入同步代
码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代
码块内运行.synchronized 语句的格式为:
Java 多线程
12
n synchronized(object){代码段}//object 可以是任意的一个对象
n 所以例子中用 private String str=new String(“”);随便产生了一个对
象,用在后面的同步代码块.
n 编译运行后,程序打印完最后一张票后,就停止了卖票的操作,这说明了改
写后的线程是安全的.
n 同步处理后,程序的运行速度比原来没有使用同步处理前更慢了,因为系
统要不停地对同步监视器进行检查,需要更多的开销.同步是以牺牲程序
的性能为代价的.如果我们能够确定程序没有安全性的问题,就没有必要
使用同步控制.
n 我们将程序代码略作修改,改变 String str=new String(“”)这行代码的
位置,将 str 对象放到 run 方法中定义:
n class Thread1 implements Runnable {
n private int tickets = 100&#894;
n public void run() {
n String str = new String("")&#894;
n while (true) {
n synchronized (str) {
n if (tickets > 0) {
n try {
n Thread.sleep(10)&#894;
n } catch (Exception e) {
n e.printStackTrace()&#894;
n }
Java 多线程
20091214
13
n System.out.println(Thread.currentThread().getName()
n + " is saling ticket " + tickets)
&#894;
n }
n }
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
n Thread1 t = new Thread1()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n }
n }
n 编译运行后,发现结果又不正常了,问题是 run 方法被四个线程所调用,相
当于run 方法被调用了四次,对每一次调用,程序都产生一个不同的str 局
部对象,这四个线程使用的同步监视器完全是四个不同的对象,所以彼此
之间不能同步.
同步函数
n 除了可以对代码进行同步外,也可以对函数实现同步,只要在需要同步的
函数定义前加上 synchronized 关键字即可.
Java 多线程
14
n package com.yzy&#894;
n class Thread1 implements Runnable {
n private int tickets = 100&#894;
n public void run() {
n String str = new String("")&#894;
n while (true) {
n sale()&#894;
n }
n }
n public synchronized void sale() {
n if (tickets > 0) {
n try {
n Thread.sleep(10)&#894;
n } catch (Exception e) {
n e.printStackTrace()&#894;
n }
n System.out.println(Thread.currentThread().getName()
n + "is saling ticket" + tickets)
&#894;
n }
n }
n }
n public class SimpleSwing {
n public static void main(String[] args) {
Java 多线程
15
n Thread1 t = new Thread1()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n new Thread(t).start()&#894;
n }
n }
n 编译运行后的结果同上面同步代码块方式的运行结果完全一样,可见,在
函数定义前使用 synchronized 关键字也能够很好地实现线程间的同步.
n 在同一类中,使用 synchronized 关键字定义的若干方法,可以在多个线程
之间同步,当有一个线程进入了 synchronized 修饰的方法(获得监视器),
其他线程就不能进入同一个对象的所有使用了synchronized 修饰的方法,
直到第一个线程执行完它所进入的synchronized 修饰的方法为止(离开监
视器).
代码块与函数间的同步
n 掌握了同步代码块与同步函数两种方式,能否在代码块与函数之间实现同
步呢? 线程同步靠的是检查同一对象的标志,只要让代码块与函数使用
同一个监视器对象,就能实现代码块与函数同步
n public class Demo {
n public static void main(String[] args) {
n Thread1 t = new Thread1()&#894;
n new Thread(t).start()&#894;
n t.type = "method"&#894;
Java 多线程 20091214
16
n new Thread(t).start()&#894;
n for (int i = 0&#894; i < 100&#894; i++) {
n try {
n if ("method".equals(t.type.trim())) {
n Thread.sleep(9)&#894;
n t.type = ""&#894;
n } else {
n t.type = "method"&#894;
n Thread.sleep(5)&#894;
n }
n } catch (Exception e) {
n }
n }
n }
n }
n class Thread1 implements Runnable {
n int tickets = 100&#894;
n String str = ""&#894;
n String type = ""&#894;
n @Override
n public void run() {
n if ("method".equals(type.trim())) {
n while (true) {
Java 多线程
17
n sale()&#894;
n }
n } else {
n while (true) {
n synchronized (str) {
n if (tickets > 0) {
n try {
n Thread.sleep(10)&#894;
n } catch (Exception e) {
n e.printStackTrace()&#894;
n }
n System.out.println(Thread.currentThread().toString()
n + "正在卖第" + tickets+
"张票")&#894;
n } else {
n break&#894;
n }
n }
n }
n }
n }
n public synchronized void sale() {
n if (tickets > 0) {
n try {
Java 多线程 18
n Thread.sleep(10)&#894;
n } catch (Exception e) {
n e.printStackTrace()&#894;
n }
n System.out.println(Thread.currentThread().getName() + "正在读第"
n + tickets+
"张票")&#894;
n }
n }
n }
Tags:  java多线程

延伸阅读

最新评论

发表评论