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()方法的线程优先级高于其他的线程,它让出控制权后,进入排队队列,调度机制将从等待运行的线程队列中选出一个等级最高的线程来运行,那么它有可能被选中来运行。

Java技术——Java多线程学习的更多相关文章

  1. 【转】Java中的多线程学习大总结

    多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...

  2. 【Java之】多线程学习笔记

    最近在学习thinking in java(第三版),本文是多线程这一章的学习总结. --------------------------------------------------------- ...

  3. Java技术大牛需要学习的25个技能

    你需要精通面向对象分析与设计(OOA/OOD).涉及模式(GOF,J2EEDP)以及综合模式.你应该了解UML,尤其是class.object.interaction以及statediagrams. ...

  4. 实操代码研究各种Java技术-java.toutiao.im

    whatsmars https://github.com/javahongxi/whatsmars whatsmars-earth-web springmvc+velocitywhatsmars-ea ...

  5. Java技术——Java中创建对象的5种方式

    此文为译文 原文连接:https://dzone.com/articles/5-different-ways-to-create-objects-in-java-with-ex 0. 前言 作为Jav ...

  6. Java技术——Java中的static关键字解析

    )非静态内部类能够访问外部类的静态和非静态成员,显然一个非静态内部类不能脱离外部类实体被创建,而静态类不能访问外部类的非静态成员,它只能访问外部类的静态成员.这一点和上面static方法的性质类似. ...

  7. Java技术----Java泛型详解

    1.为什么需要泛型 泛型在Java中有很重要的地位,网上很多文章罗列各种理论,不便于理解,本篇将立足于代码介绍.总结了关于泛型的知识.希望能给你带来一些帮助. 先看下面的代码: List list = ...

  8. Java技术——Java泛型详解

    .为什么需要泛型 转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52230032 泛型在Java中有很重要的地位,网上很多文章罗列各种 ...

  9. Java技术——Java反射机制分析

    )生成动态代理. 2. Java反射API 反射API用来生成在当前Java虚拟机中的类.接口或者对象的信息. Class类:反射的核心类,可以获取类的属性,方法等内容信息. Field类:Java. ...

随机推荐

  1. [JQuery] Using skill in JQuery

    Using skill of JQuery 获取兄弟节点 $('#id').siblings() 当前元素的所有兄弟节点 $('#id').prev() 当前元素的前一个兄弟节点 $('#id').p ...

  2. hibernate笔记3--hql查询

    1.Query:他是一种比较面向对象的查询方式,query查询也叫做hql查询(hibernate query language),使用query查询,需要接受一个         hql语句进行查询 ...

  3. Android在应用设置里关闭权限,返回生命周期处理

    问题 在处理6.0运行时权限时,很多人都忽略了这样一个问题: 在一个App应用里,如果已经允许了一个权限比如(读取通讯权限),此刻去调用相机,弹出权限申请对话框,此刻点击拒绝,然后经过处理后弹出去设置 ...

  4. 【Mood-13】Android --如何从初级工程师进化为高级工程师

    一  明确自我定位 现在你是初级工程师,但是你想当个高级工程师,所 以,你就要给自己定个目标,即:我是要成为高级工程师的男人.有了这个定位,并且努力朝着这个目标去努力,然后内心深处就会有一个感觉,这个 ...

  5. Spring MVC + Thymeleaf

    参考网址: https://www.cnblogs.com/litblank/p/7988689.html 一.简介 1.Thymeleaf 在有网络和无网络的环境下皆可运行,而且完全不需启动WEB应 ...

  6. MySQL-数据类型及选择

    一.数据类型 详见:http://www.runoob.com/mysql/mysql-data-types.html 二.类型选择 整形>date,time>enum,char>v ...

  7. javascript模块化---requirejs

    requirejs是异步执行 为什么会出现模块化1.不定什么时候,自己就将全局变量改变了2.函数名的冲突3.依赖关系不好管理如果b.js依赖a.js那么b必须放在a的下面解决的办法1.自执行函数来包装 ...

  8. HDU3954 线段树(区间更新 + 点更新)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3954 , 一道比较好的线段树题,值得做. 题目是NotOnlySuccess大神出的,借此题来膜拜一下 ...

  9. 【转】iOS开发4:关闭键盘

    在 iOS 程序中当想要在文本框中输入数据,轻触文本框会打开键盘.对于 iPad 程序,其键盘有一个按钮可以用来关闭键盘,但是 iPhone 程序中的键盘却没有这样的按钮,不过我们可以采取一些方法关闭 ...

  10. 2018.6.21 css的应用---注册表格

    参与css样式表格的注册表单 <!DOCTYPE html> <head> <meta charset="UTF-8" /> <meta ...