疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

Java多线程并发库API使用


 

   Java多线程并发库API使用

  1. 传统线程技术回顾

  继承线程与实现Runnable的差异?为什么那么多人都采取第二种方式?

  因为第二种方式更符合面向对象的思维方式。创建一个线程,线程要运行代码,而运行的代码都封装到一个独立的对象中去。一个叫线程,一个叫线程运行的代码,这是两个东西。两个东西一组合,就表现出了面向对象的思维。如果两个线程实现数据共享,必须用Runnable的方式。

  查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

  问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?子类的run方法。

  示例代码

 

  1 new Thread(new Runnable() {

  2 public void run() {

  3 while (true) {

  4 System.out.println("run:runnable");

  5 }

  6 }

  7 }) {

  8 public void run() {

  9 while (true) {

  10 try {

  11 Thread.sleep(1000);

  12 } catch (Exception e) {

  13 }

  14 System.out.println("run:thread");

  15 }

  16 }

  17 }.start();

 

  该线程会运行重写的Thread中的run方法,而不是Runnable中的run方法,因为在传统的Thread的run方法是:

  1 public void run() {

  2 if (target != null) {

  3 target.run();

  4 }

  5 }

  如果想要运行Runnable中的run方法,必须在Thread中调用,但是此时我重写了Thread中的run方法,导致if (target != null) { target.run(); }不存在,所以调用不了Runnable中的run方法。

  注意:多线程的执行,会提高程序的运行效率吗?为什么会有多线程下载?

  不会,有时候还会降低程序的运行效率。因为CPU只有一个,在CPU上下文切换的时候,可能还会耽误时间。

  多线程下载:其实你的机器没有变快,而是你抢了服务器的带宽。如果你一个人下载,服务器给你提供的是20K的话,那么10个人的话,服务器提供的就是200K。这个时候你抢走200k,所以感觉变快了。

  2. 传统定时器技术回顾(jdk1.5以前)Timer、TimerTask

  Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。

  scheduleAtFixedRate(TimerTask task, Date firstTime, long period):

  安排指定的任务在指定的时间开始进行重复的固定速率执行。

  schedule(TimerTask task, long delay, long period): 安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。

  作业调度框架 Quartz(专门用来处理时间时间的工具)。你能够用它来为执行一个作业而创建简单的或复杂的调度。{ http://www.oschina.net/p/quartz}

  问题:每天早晨3点来送报纸。

  问题:每个星期周一到周五上班,周六道周日不上班。

  示例代码:(间隔不同时间,执行不同事件)

 

  1 package com.chunjiangchao.thread;

  2

  3 import java.util.Timer;

  4 import java.util.TimerTask;

  5 /**

  6 * 重复执行某项任务,但是时间间隔性不同是2,4这种状态

  7 * @author chunjiangchao

  8 *

  9 */

  10 public class TimerDemo02 {

  11

  12 private static long count = 1;

  13 public static void main(String[] args) {

  14 Timer timer = new Timer();

  15 timer.schedule(new Task(), 1000);

  16 /*

  17 测试打印结果如下:

  18 执行任务,当前时间为:1460613231

  19 执行任务,当前时间为:1460613235

  20 执行任务,当前时间为:1460613237

  21 执行任务,当前时间为:1460613241

  22 */

  23

  24 new Thread(new Runnable() {

  25 public void run() {

  26 while (true) {

  27 System.out.println("run:runnable");

  28 }

  29 }

  30 }) {

  31 public void run() {

  32 while (true) {

  33 try {

  34 Thread.sleep(1000);

  35 } catch (Exception e) {

  36 }

  37 System.out.println("run:thread");

  38 }

  39 }

  40 }.start();

  41

  42

  43 }

  44

  45 static class Task extends TimerTask{

  46

  47 @Override

  48 public void run() {

  49 System.out.println("执行任务,当前时间为:"+System.currentTimeMillis()/1000);

  50 new Timer().schedule(new Task(), 2000*(1+count%2));

  51 count++;

  52 }

  53

  54 }

  55

  56 }

 

  3. 传统线程互斥技术

  本道例题:关键在于说明:要想实现线程间的互斥,线程数量必须达到两个或者两个以上,同时,访问资源的时候要用同一把锁(这个是必须的)。如果两个线程都访问不同的同步代码块,而且它们的锁对象都不相同,那么这些线程就没有达到同步的目的。

  示例代码:(访问同一个资源对象,但是锁对象不同,同样没有达到同步的目的)

  

 

  1 package com.chunjiangchao.thread;

  2 /**

  3 * 线程间同步与互斥

  4 * @author chunjiangchao

  5 * 因为print1方法与print3方法锁对象相同,所以在调用的时候,会产生互斥的现象,而print2的锁是当前正在执行对象print2方法的对象,

  6 * 与print1和print3同时执行,打印结果就不是期望的结果

  7 *

  8 */

  9 public class TraditionalThreadSynchronizedDemo {

  10

  11 public static void main(String[] args) {

  12 final MyPrint myPrint = new MyPrint();

  13 //A

  14 new Thread(new Runnable() {

  15

  16 @Override

  17 public void run() {

  18 myPrint.print1("chunjiangchao");

  19 }

  20 }).start();

  21 //B

  22 // new Thread(new Runnable() {

  23 //

  24 // @Override

  25 // public void run() {

  26 // myPrint.print2("fengbianyun");

  27 // }

  28 // }).start();

  29 //C

  30 new Thread(new Runnable() {

  31

  32 @Override

  33 public void run() {

  34 myPrint.print3("liushaoyue");

  35 }

  36 }).start();

  37 }

  38 static class MyPrint{

  39 public void print1(String str){

  40 synchronized (MyPrint.class) {

  41 for(char c :str.toCharArray()){

  42 System.out.print(c);

  43 pSleep(200);

  44 }

  45 System.out.println("print1当前已经打印完毕");

  46 }

  47

  48 }

  49 public synchronized void print2(String str){

  50 for(char c :str.toCharArray()){

  51 System.out.print(c);

  52 pSleep(200);

  53 }

  54 System.out.println("print2当前已经打印完毕");

  55 }

  56 public synchronized static void print3(String str){

  57 for(char c :str.toCharArray()){

  58 System.out.print(c);

  59 pSleep(200);

  60 }

  61 System.out.println("print3当前已经打印完毕");

  62 }

  63 private static void pSleep(long time){

  64 try {

  65 Thread.sleep(time);

  66 } catch (InterruptedException e) {

  67 e.printStackTrace();

  68 }

  69 }

  70

  71 }

  72

  73 }

  

 

  4. 传统线程同步通信技术

  在设计的时候,最好将相关的代码封装到一个类中,不仅可以方便处理,还可以实现内部的高耦合。

 

  经验总结:要用到共同数据(包括同步锁)或共同算法的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。

  同步通信,互斥的问题不是写在线程上面的,而是直接写在资源里面的,线程是直接拿过来使用就可以了。好处就是,以后我的类,交给任何一个线程去访问,它天然就同步了,不需要考虑线程同步的问题。如果是在线程上面写,明天又有第三个线程来调用我,还得在第三个线程上面写互斥写同步。(全部在资源类的内部写,而不是在线程的代码上面去写)

  示例代码:(子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100, 如此循环50次,请写出程序。)

 

  1 package com.chunjiangchao.thread;

  2

  3 public class TraditionalThreadCommunication {

  4 /**

  5 * 经验:涉及到线程互斥共享,应该想到将同步方法写在资源里面,而不是写在线程代码块中 在资源中判断标记的时候,最好用while语句

  6 */

  7 public static void main(String[] args) {

  8 final Output output = new Output();

  9 new Thread(new Runnable() {

  10 public void run() {

  11 for (int i = 0; i < 50; i++) {

  12 output.sub(10);

  13 }

  14 }

  15 }).start();

  16 for (int i = 0; i < 50; i++) {

  17 output.main(i);

  18 }

  19 }

  20 }

  21

  22 class Output {

  23 private boolean flag = true;

  24

  25 public synchronized void sub(int i) {

  26 while (!flag) {// 用while比用if更加健壮,原因是即使线程被唤醒了,也判断一下是不是真的该它执行了

  27 // 防止伪唤醒的事件发生。

  28 try {

  29 this.wait();

  30 } catch (InterruptedException e) {

  31 }

  32 }

  33 for (int j = 0; j < 10; j++) {

  34 System.out.println(i + "子线程运行" + j);

  35 }

  36 flag = false;// 记得要改变一下标记的状态

  37 this.notify();// 最后要唤醒其他要使用该锁的线程

  38 }

  39

  40 public synchronized void main(int i) {

  41 while (flag) {

  42 try {

  43 this.wait();

  44 } catch (InterruptedException e) {

  45 }

  46 }

  47 for (int j = 0; j < 100; j++) {

  48 System.out.println(i + "主线程运行" + j);

  49 }

  50 flag = true;

  51 this.notify();

  52 }

  53 }

 

  5. 线程范围内共享变量的概念与作用

  线程范围内的数据共享:不管是A模块,还B模块,如果他们在同一个线程上运行,那么他们操作的数据应该是相同。而不应该是不管A模块和B模块在哪个线程上面运行他们的数据在每个线程中的数据是一样的。(应该是各自线程上的数据是独立的)

  线程间的事务处理:

 

  不能出现这样的情况:thread1转入的data,还没有来得及操作。CPU时间片转入到thread2,该thread2来执行,转入、转出,最后直接提交事务。导致thread1的数据出现错误。

  线程范围内的变量有什么用?

  我这件事务在线程范围内搞定,不要去影响别的线程的事务。但是我这个线程内,几个模块之间是独立的,这几个模块又要共享同一个对象。它们既要共享又要独立,在线程内共享,在线程外独立。【对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。】

  示例代码(不同线程之间共享同一个Map对象,但是Map中的每个元素表示的是不同线程的数据)

 

  1 package com.chunjiangchao.thread;

  2

  3 import java.util.HashMap;

  4 import java.util.Map;

  5 import java.util.Random;

  6

  7 /**

  8 * 线程范围内共享数据

  9 * @author chunjiangchao

  10 *

  11 */

  12 public class ThreadScopeShareDataDemo {

  13 //所有线程共享的数据是datas,但是datas中的每个元素key是Thread,每个元素针对每个线程来说是独立的,value代表不同线程处理的数据

  14 private static Map datas = new HashMap();

  15 public static void main(String[] args) {

  16 for(int i=0;i<2;i++){

  17 new Thread(new Runnable() {

  18

  19 @Override

  20 public void run() {

  21 int nextInt = new Random().nextInt();

  22 datas.put(Thread.currentThread(), nextInt);

  23 ///A模块与B模块是独立的,但是A与B共享当前当前线程中的数据

  24 new ModuleA().getThreadData();

  25 new ModuleB().getThreadData();

  26 }

  27 }).start();

  28 }

  29 /*

  30 打印的结果为

  31 Thread-1的ModuleA获取的变量为:-918049793

  32 Thread-0的ModuleA获取的变量为:-1424853148

  33 Thread-0的ModuleB获取的变量为:-1424853148

  34 Thread-1的ModuleB获取的变量为:-918049793

  35

  36 */

  37 }

  38 static class ModuleA{

  39 public void getThreadData(){

  40 System.out.println(Thread.currentThread().getName()+"的ModuleA获取的变量为:"+datas.get(Thread.currentThread()));

  41 }

  42 }

  43 static class ModuleB{

  44 public void getThreadData(){

  45 System.out.println(Thread.currentThread().getName()+"的ModuleB获取的变量为:"+datas.get(Thread.currentThread()));

  46 }

  47 }

  48

  49 }

 

  6. ThreadLocal类及应用技巧

  ThreadLocal就相当于一个Map。

  一个ThreadLocal代表一个变量,故其中只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象,如果有一个一百个变量要线程共享?那么就应该定义一个对象来装着一百个变量,然后在ThreadLocal中存储这一个对象。

  问题:怎么在线程结束的时候得到通知?提示:监听虚拟机结束。

  最重要的一点,就是里面涉及到的设计方法。