非常详细的JAVA并发基本线程机制

非常详细的JAVA并发基本线程机制

编程问题中相当大的一部分都可以通过使用顺序编程来解决。然而对于某些问题,若果能够并发地执行程序中的多个部分,则会变得非常方便甚至非常必要,因为这些部分要么看起来在并发执行,要么在多处理器环境下可以同时执行。

JAVA是一种多线程语言,并提出了并发问题。实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容程序。多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程。每个任务都作为进程在其自己的地址空间中执行,因此它们是完全独立的;更重要的事,对进程来说,它们之间没有任何彼此通信的需要,因为它们是完全独立的。

JAVA在并发上采取了比较传统的方式,在顺序语言的基础上提供对线程的支持。线程机制是在由执行程序表示的单一进程中创建任务。

在但CPU机器上使用多任务的程序在任意时刻仍旧值在执行一项工作,因此从理论上讲,肯定可以不用任何很污二编写出相同的程序。但是毛病发提供了一个重要的组织结构上的好处:你的程序设计可以极大地简化。而例如仿真,没有并发数的支持是很难解决的。

JAVA的线程机制是抢占式的,这表示调度机制会周期性地中断线程将上下文切换到另一个线程,从而为每个线程都提供时间片,是的每个线程都会分配到数量合理的时间去驱动它的任务。

并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务(子任务)中的每一个都将有执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务,但是你的程序使得每个任务都好像有其自己的CPU一样,其底层机制是切分CPU时间(有需要了解这部分知识的朋友可以在下方留言,可以出一篇详细的博客)。
CPU轮流给每个任务分配其占用时。每个任务都觉得自己再一直在占用CPU,大三事实上CPU时间是划分成片段分配给了所有的任务(例外是程序确实运行在多个CPU之上)。线程的一大好处是可以使你从这个层次抽身出来,即代码不必知道它是运行在具有一个还是多个CPU的机器上。所以,使用线程机制是一种建立透明的、可扩展的程序的方法。

 

JAVA多线程机制的代码介绍篇章(实用篇)


1. 定义任务

package Kim_1;

/*定义任务,需要一种描述任务的方式,可由Runnable接口提供。
 * 想要定义任务,只需要实现Runnable接口并编写run()方法,使得该任务可以执行你的命令
 * */

public class LiftOff implements Runnable{
    protected int countDown = 10;//    Default
    private static int taskcount = 0;//    记任务数
    private final int id = taskcount++;

    public LiftOff(){}
    public LiftOff(int countDown) {
        this.countDown = countDown;
    }
    //    
    public String status() {
        return "#"+id+"("+(countDown > 0 ? countDown:"LiftOff!")+").";
    }

    public void run() {
        while(countDown-->0) {
            System.out.println(status());
            //    Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程;
            Thread.yield();
        }
    }
}

MAIN类:

package Kim_1;

public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();
    }
}

猜猜运行结果是什么勒?

 

 

 

分析对了吗?没对没关系,跟我继续往下,你就能渐渐理解了。
从结果我们可以得知,整个过程我们只有一个过程产生了ListOff对象,即目前只有0号一个LiftOff任务被执行;后面括号中的倒数进行发射为描述的任务。

不过就纳闷了说好的多线程呢?

的确,如果这里我们需要实现多线程的话,还少了一些步骤;因为虽然我们实现了Runnable接口,并重写了run()函数,但是这个方法并无特殊之处——它不会产生任何内在的线程能力。要实现线程行为,你必须显示地讲一个任务附着到线程上。

通过上面的程序我们可以再来理一遍上面的程序-任务-进程图

(理解这个模式,之后就能很简单的区分继承自Thread和实现Runnable接口的区别了)

2. Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器
下面的例子展示了如何使用Thread来驱动LiftOff对象:

package Kim_1;

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println("Waiting for LiftOff");
    }
}

运行结果如下:


Thread构造器只需要一个Runnable对象。调用Thread对象的start()方法为该线程执行必要的初始化操作,然后调用Runnble的run()方法,以便在这个新线程中启动该任务。初学者看到这样的结果可能会觉得很诧异,比较平时习惯了顺序执行的方式;但这也就是多线程的魅力所在,实际上t.start()产生的是对LiftOff.run()的方法调用,并且这个方法还没有完成,但是因为LiftOff.run()是由不同的线程执行的,因此你人就可以执行main()线程中的其他操作。
到这里不可避免还是一头雾水,我们暂时先了解有这么个东西,能运行出这样奇怪的结果,具体细节咱们后面慢慢道来。

有了上面的基础,你可以像方一样很容易地添加更多的线程去驱动更多的任务,结果不方便截图,各位小伙伴可以在自己的PC上运行一下试试,可以显然的看到运行的结果杂乱无章,并且每次运行的结果几乎都不一样,这说明不同任务的执行在线程中被换进换出时混在了一起,这种交换是由线程条肚脐自动控制的,且线程调度机制是非确定性的。
(注意:本例子中,单一线程(main()在创建所以的LiftOff线程,所以id为0-4。但是如果多个线程在创建LiftOff线程,那么就要可能会有多个LiftOff拥有相同的id))
package Kim_1;

public class MoreBasicThreads {
    public static void main(String[] args) {
        for(int i=0;i<5;i++) 
            new Thread(new LiftOff()).start();
        System.out.println("Waiting for LiftOff");    
    }
}

当main()创建Thread对象时,它并可以捕获任何对这些对象的引用。而每个Thread都“注册”了它自己,因此有一个对它的引用,而且在它的任务结束退出其run()并死亡之前,垃圾回收期无法清除它。

3. 使用Executor(执行器)
Executor可以为我们管理Thread对象,从而简化并发编程;它在客户端和任务之间提供了一个间接层;它允许我们管理一部任务的执行,而无需显示地管理线程的声明周期。
我们可以使用Excutor来代替MoreBasicThread.java中显示地创建Thread对象。LiftOff对象知道如何运行具体的任务。ExecutorService知道如何构建恰当的上下文来执行Runnable对象。

package Kim_1;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();//    ExecutorService是具有服务生命周期的Executor
        for (int i = 0; i < 5; i++) {
            exec.execute(new LiftOff());
        }
        exec.shutdown();//    可以防止新任务被提交给这个Executor
    }
}

这一次我们可以发现运行的结果齐整多了。

4. 从任务中产生返回值

package Kim_1;

/*从人物中产生返回值,
 * 通过实现Callable接口,Callable是一种具有类型参数的泛型,它的类型参数表示从方法call()中返回的值的类型
 * 并且必须使用ExcecutorService.submit()方法调用它*/

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class TaskWithResult implements Callable<String>{
    private int id;
    public TaskWithResult(int id) {
        this.id = id;
    }
    public String call() {
        return "result of TaskWithResult" + id;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> result = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++) {
            result.add(exec.submit(new TaskWithResult(i)));
        }
        for (Future<String> fs : result) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                // TODO: handle exception
                System.out.println(e);
                return;
            }catch (ExecutionException e) {
                // TODO: handle exception
                System.out.println(e);
            }finally {
                exec.shutdown();
            }
        }
    }
}

5. 休眠

package Kim_1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SleepingTask extends LiftOff{
    public void run() {
        try {
            while (countDown-->0) {
                System.out.print(status());
                TimeUnit.MILLISECONDS.sleep(1000);
            }
        } catch (Exception e) {
            // TODO: handle exception
            System.err.println("Interrupted");
        }
    }
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            exec.execute(new SleepingTask());
        }
        exec.shutdown();
    }
}

6. 优先级
线程的优先级将该线程的重要性传给了调度器。调度器会倾向于让优先级最高的线程限执行;但这并不意味着优先级低的线程得不到执行,优先级不会造成死锁现象
三个常用的优先级级别常量:MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY

package Kim_1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * */
public class SimplePriorities implements Runnable{
    private int countDown = 5;
    private volatile double d;
    private int priority;
    public SimplePriorities(int priority){
        this.priority = priority;
    }
    //toString()方法被覆盖,用来打印线程的名字
    //这里为自动生成的名称(你也可以在构造器里自己设置这个名称)
    public String toString(){
        return Thread.currentThread()+":"+countDown;
    }
    public void run() {
        Thread.currentThread().setPriority(priority);
        while(true) {
            for (int i = 0; i < 100000; i++) {
                d+=(Math.PI + Math.E)/(double)i;
                if (i%1000==0) {
                    Thread.yield();
                }
                System.out.println(this);
                if (--countDown==0) {
                    return;
                }
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) 
            exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
        exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
        exec.shutdown();

    }
}

7.让步
yield()方法。当已经知道run()方法的循环的一次迭代过程中所需要的工作,就可以将线程调度机制一个暗示:你的工作已经做得差不多了,可以让别的线程来使用CPU了。这个暗示将通过调用yield()方法来做出。当调用yield()时,你也是在建议具有相同优先级瑟其他线程可以运行。
注意:虽然yield()可以对线程的产生分布给予了良好的处理机制,但是对于任何重要的控制或在调整应用时,都不能依赖于yield();

8.后台线程
后台线程是指在程序运行时在后台提供的一种通用服务线程,并且这种线程并不属于程序中不可或缺的部分,即当所有的非后台程序结束时,程序也就终止了,同时会杀死进程中的所以后台线程。比如:执行main()的就是一个非后台线程。
必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。实例中因为main()线程别设定为短暂睡吗,所以可以观察到所以后台线程启动后的结果。
//: concurrency/SimpleDaemons.java
// Daemon threads don’t prevent the program from ending.

package Kim_1;

import java.util.concurrent.TimeUnit;

public class SimpleDaemons implements Runnable {
  public void run() {
try {
  while(true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.print(Thread.currentThread() + " " + this);
  }
} catch(InterruptedException e) {
    System.out.print("sleep() interrupted");
}
  }
  public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; i++) {
  Thread daemon = new Thread(new SimpleDaemons());
  //    将daemon设为后台线程
  daemon.setDaemon(true); // Must call before start()
  daemon.start();
}
System.out.print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
  }
} /* Output: (Sample)
All daemons started
Thread[Thread-0,5,main] SimpleDaemons@530daa
Thread[Thread-1,5,main] SimpleDaemons@a62fc3
Thread[Thread-2,5,main] SimpleDaemons@89ae9e
Thread[Thread-3,5,main] SimpleDaemons@1270b73
Thread[Thread-4,5,main] SimpleDaemons@60aeb0
Thread[Thread-5,5,main] SimpleDaemons@16caf43
Thread[Thread-6,5,main] SimpleDaemons@66848c
Thread[Thread-7,5,main] SimpleDaemons@8813f2
Thread[Thread-8,5,main] SimpleDaemons@1d58aae
Thread[Thread-9,5,main] SimpleDaemons@83cc67
...
*///:~

9.加入一个线程
一个线程可以在其他线程智商调用join()方法,其效果十等待一段时间知道第二个线程结束才能继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(t.isAlive()为false)。join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法(追interrupt需要处理异常,用到try-catch语句块)

从下方运行结果我们可以看出,Sleeper被中断或者是正常结束,Joiner将和Sleeper一同结束。
//: concurrency/Joining.java
// Understanding join().
package Kim_1;

class Sleeper extends Thread {
  private int duration;
  public Sleeper(String name, int sleepTime) {
super(name);
duration = sleepTime;
start();
  }
  public void run() {
try {
  sleep(duration);
} catch(InterruptedException e) {
  System.out.println(getName() + " was interrupted. " +
"isInterrupted(): " + isInterrupted());
  return;
}
System.out.println(getName() + " has awakened");
  }
}

class Joiner extends Thread {
  private Sleeper sleeper;
  public Joiner(String name, Sleeper sleeper) {
super(name);
this.sleeper = sleeper;
start();
  }
  public void run() {
   try {
  sleeper.join();
} catch(InterruptedException e) {
    System.out.println("Interrupted");
}
   System.out.println(getName() + " join completed");
  }
}

public class Joining {
  public static void main(String[] args) {
Sleeper
  sleepy = new Sleeper("Sleepy", 1500),
  grumpy = new Sleeper("Grumpy", 1500);
Joiner
  dopey = new Joiner("Dopey", sleepy),
  doc = new Joiner("Doc", grumpy);
grumpy.interrupt();
  }
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*///:~
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值