0.前言

转载请标明出处:http://blog.csdn.net/seu_calvin/article/details/52400927

学习多线程之前需要先了解以下几个概念。

进程:进程是以独立于其他进程的方式运行的,进程间是互相隔离的。一个进程无法直接访问另一个进程的数据。进程的资源诸如内存和CPU时间片都是由操作系统来分配。

线程:线程是在进程中执行的一个任务。线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。每个线程有独立的运行栈和程序计数器(PC),别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。线程切换开销小。

多线程:指的是这个程序(一个进程)运行时产生了不止一个线程。用多线程只有一个目的,那就是提高了CPU资源的利用率。

并行:多个CPU实例或者多台机器同时执行一段处理逻辑,是真正的同时。

并发:通过CPU调度算法,让用户看上去是同时执行,实际上从CPU操作层面不是真正的同时。并发往往在场景中有公用的资源(多个线程同时访问同一数据才会出现并发)。

线程安全:代码在多线程下运行和在单线程下运行永远都是获得相同的结果。

1.创建多线程的三种方式

1.1 Thread和Runnable的比较

Java 5以前实现多线程有两种实现方法:一种是继承Thread类;另一种是实现Runnable接口。

实例代码就不举了,都很简单。下面会介绍一些两种的区别和容易被忽略的地方。

实现Runnable接口相比继承Thread类有如下优势:

(1)可以避免由于Java的单继承特性而带来的局限。

(2)适合多个相同程序代码的线程区处理同一资源的情况。比如下面这个买票的例子。

//使用Thread实现
public static class MyThread extends Thread{
private int ticket = 5;
public void run(){
for (int i=0;i<10;i++) {
if(ticket > 0){
System.out.println("ticket = " + ticket--);
}
}
}
} public class ThreadDemo{
public static void main(String[] args){
new MyThread().start();
new MyThread().start();
}
}
//每个线程单独卖了5张票,即独立的完成了买票的任务
//输出结果为
ticket = 5
ticket = 4
ticket = 5
ticket = 3
ticket = 2
ticket = 1
ticket = 4
ticket = 3
ticket = 2
ticket = 1
//通过实现Runnable接口实现
public static class MyThread implements Runnable{
private int ticket = 5;
public void run(){
for (int i=0;i<10;i++) {
if(ticket > 0){
System.out.println("ticket = " + ticket--);
}
}
}
} public class RunnableDemo{
public static void main(String[] args){
MyThread my = new MyThread();
//同样也new了2个Thread对象,但只有一个Runnable对象
// 2个Thread对象共享这个Runnable对象中的代码
new Thread(my).start();
new Thread(my).start();
}
}
//输出结果为
ticket = 5
ticket = 3
ticket = 2
ticket = 1
ticket = 4

注意:

(1)上面第二段代码ticket输出的顺序并不是54321,这是因为线程执行的时机难以预测。因为ticket--并不是原子操作。这就需要加入同步操作。确保同一时刻只有一个线程在执行每次for循环中的操作。

(2)调用Thread.start()方法才会启动新线程;如果直接调用Thread.run() 方法,它的行为就和普通方法是一样的。

(3)start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行行态(Runnable),什么时候运行是由操作系统决定的。

(4)在Java中,每次程序运行至少启动2个线程。一个是main主线程,一个是垃圾收集线程。

1.2 Callback

Java 5以后创建线程还有第三种方式:实现Callable接口,该接口中的call方法可以在线程执行结束时产生一个返回值,代码如下所示:

public static class MyTask implements Callable<Integer> {
private int upperBounds; public MyTask(int upperBounds) {
this.upperBounds = upperBounds;
} @Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1; i <= upperBounds; i++) {
sum += i;
}
return sum;
} } public static void main(String[] args) throws Exception {
List<Future<Integer>> list = new ArrayList<>();
ExecutorService service = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++) {
list.add(service.submit(new MyTask((int) (Math.random() * 100))));
} for(Future<Integer> future : list) {
int sum = 0;
while(!future.isDone()) ; //如果任务已完成isDone返回true
sum += future.get(); //get()方法获取返回值
System.out.println(sum); //打印十次求和
//future.cancel(true); 中断该线程的执行
//isCancelled(); 如果在任务正常完成前将其取消返回true
}
}

2. 线程的各种状态

上图线程的各种状态很容易理解,这里着重介绍一下阻塞状态以及相关知识点。

(1)sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。线程调度有优先级之分,取值范围是1~10,优先级可以被继承。Thread类有以下三个静态常量:

//static int MAX_PRIORITY
//线程可以具有的最高优先级,取值为10
//static int MIN_PRIORITY
//线程可以具有的最低优先级,取值为1
//static int NORM_PRIORITY
//分配给线程的默认优先级,取值为5
Thread.setPriority();
Thread.getPriority();//分别用来设置和获取线程的优先级。

(2)调用wait(),使该线程处于等待池,直到notify()/notifyAll()、或被打断,线程被唤醒被放到锁定池,释放同步锁使线程回到可运行状态(Runnable)。

(3)对Running状态的线程加同步锁使其进入锁定池,同步锁被释放进入可运行状态(Runnable)。

(4)Thread.yield()方法可以让一个running状态的线程转入runnable。

(5)如何唤醒阻塞:如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞那就无能为力了,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统,等待获取某个对象锁时阻塞也不会对中断做出反应。

3. 各种方法的作用介绍

(1)wait():导致线程进入等待状态,直到其他线程调用此对象的notify() 方法或notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用wait(0)一样。

(2)notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会随机选择唤醒其中一个线程。

(3)notifyAll():唤醒在此对象监视器上等待的所有线程。调用以上三个方法中任意一个,当前线程必须是锁的持有者,否则会抛出IllegalMonitorStateException。

(4)sleep():Thread 类专属的静态方法,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),wait()方法进入等待状态时会释放同步锁,而sleep() 方法不会释放同步锁。所以,当一个线程无限sleep 又没有任何人去interrupt它的时候,程序就会有大麻烦。wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。 wait()通常被用于线程间交互,sleep()通常被用于暂停执行。

(5)join():定义在Thread类中,如下代码解释。这种方法就可以确保两个线程的同步性。

Thread t1 = new Thread(计数线程一);
Thread t2 = new Thread(计数线程二);
t1.start();
t1.join(); // 等待计数线程一执行完成,再执行计数线程二
t2.start();

(6)yield():线程放弃运行,将CPU的控制权让出。这里需要注意:sleep和该方法都会将当前运行线程的CPU控制权让出,但sleep() 方法在指定的睡眠期间一定不会再得到运行机会;而执行yield()方法的线程优先级高于其他的线程,它让出控制权后,进入排队队列,调度机制将从等待运行的线程队列中选出一个等级最高的线程来运行,那么它有可能被选中来运行。

最新文章

  1. javascript运动系列第三篇——曲线运动
  2. LNMP编译安装基于centos7.2
  3. AS3全局与局部坐标转换
  4. 利用jQuery实现CheckBox全选/全不选/反选
  5. Hashing function
  6. CentOS7_RAID5_LVM_SAMBA
  7. javascript 中 typeof 的使用
  8. SQL Server 创建表 添加主键 添加列常用SQL语句【转】
  9. LINQ to XML 从逗号分隔值 (CSV) 文件生成 XML 文件
  10. HDU-1037(水水水题)
  11. 我用的比较少的CSS选择器
  12. iOS开源库--最全的整理 分类: ios相关 2015-04-08 09:20 486人阅读 评论(0) 收藏
  13. SQL注入相关的知识【Mysql为例子】
  14. 【一天一道LeetCode】#141. Linked List Cycle
  15. java基础基础总结----- 常用DOS命令(一)
  16. RabbitMQ 入门指南——初步使用
  17. 何凯文每日一句打卡||DAY14
  18. Angular 下的 directive (part 2)
  19. Webwork【04】Configuration 详解
  20. 有用的sql语句积累

热门文章

  1. xml 文件转化Dictionary
  2. 面向对象(OOP)一
  3. 工作中遇到的有关echarts地图和百度地图的问题
  4. 使用后台程序的第一个表单Form
  5. 为Visual Studio 2012添加MSDN离线帮助
  6. IOS UISwitch控件的基本使用
  7. 【洛谷2522】[HAOI2011] Problem b(莫比乌斯反演)
  8. python_48_Python3中字符编码与转码
  9. Java MD5加密算法工具类
  10. React后台管理系统-首页Home组件