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 extendsThread {2 publicTestSimpleThread(String str)3 {4 super(str);5 }6

7 public voidrun()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 voidmain(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 implementsRunnable2 {3 privateString str;4 privateThread myThread;5

6 publicTestSimpleRunnable(String str)7 {8 this.str =str;9 }10

11 public voidmyStart()12 {13 myThread = new Thread(this, str);14 myThread.start();15 }16

17 public voidrun()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 voidmain (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 extendsThread2 {3 public voidlistCurrentThreads()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 voidmain (String[] args)20 {21 EnumerateTest a = newEnumerateTest();22 EnumerateTest b = newEnumerateTest();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 extendsThread2 {3 privateString name;4

5 publicTestThreadPrio (String name)6 {7 this.name =name;8 }9

10 public voidrun()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 voidmain (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 classQueue2 {3 private intcount;4 private intfront;5 private intrear;6 private char[] dat = new char[10];7

8 publicQueue()9 {10 count = 0;11 front = 0;12 rear = 0;13 }14

15 public void push(charc)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 charpop()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 returntemp;44 }45 }

1 public class Consumer implementsRunnable2 {3 privateQueue qu;4

5 publicConsumer(Queue s)6 {7 qu =s;8 }9

10 public voidrun()11 {12 for (int i = 0; i < 3; i ++)13 {14 qu.pop();15 }16 }17 }

1 public classTestQueue2 {3 public static voidmain(String args[])4 {5 Queue qu = newQueue();6 for (char c = 'a'; c <= 'd'; c ++)7 qu.push(c);8 Runnable sink = newConsumer(qu);9 Thread t1 = newThread(sink);10 Thread t2 = newThread(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 classStorage2 {3 private intgoods;4 private booleanavailable;5

6 public Storage(intg)7 {8 goods =g;9 available = false;10 }11

12 public synchronized void put(intg)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 intget()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 returntemp;44 }45 }

1 public class Producer extendsThread2 {3 privateStorage tb;4

5 publicProducer(Storage c)6 {7 tb =c;8 }9

10 public voidrun()11 {12 for (int i = 11; i < 16; i ++)13 {14 tb.put(i);15 }16 }17 }

1 public class Consumer extendsThread2 {3 privateStorage tb;4

5 publicConsumer(Storage c)6 {7 tb =c;8 }9

10 public voidrun()11 {12 intg;13 for (int i = 11; i < 16; i ++)14 {15 g =tb.get();16 }17 }18 }

1 public classTestStorage2 {3 public static voidmain(String[] args)4 {5 Storage com = new Storage(0);6

7 Producer p = newProducer(com);8

9 Consumer c = newConsumer(com);10

11 p.start();12 c.start();13 }14 }

Java不支持多继承,但Java支持接口,且允许一个类中同时实现若干个接口。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值