Java编程思想学习(十六) 并发编程
线程是进程中一个任务控制流序列,由于进程的创建和销毁需要销毁大量的资源,而多个线程之间可以共享进程数据,因此多线程是并发编程的基础。
多核心CPU可以真正实现多个任务并行执行,单核心CPU程序其实不是真正的并行运行,而是通过时间片切换来执行,由于时间片切换频繁,使用者感觉程序是在并行运行。单核心CPU中通过时间片切换执行多线程任务时,虽然需要保存线程上下文,但是由于不会被阻塞的线程所阻塞,因此相比单任务还是大大提高了程序运行效率。
1.线程的状态和切换:
线程的7种状态及其切换图如下:

2.多线程简单线程例子:
Java中实现多线程常用两种方法是:实现Runnable接口和继承Thread类。
(1).继承Thread类的多线程例子如下:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
//重写Thread类的run方法
public void run() {
. . .
}
}
启动继承Thread类线程的方法:
PrimeThread p = new PrimeThread(143);
p.start();
(2).实现Runnable接口的多线程例子如下:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
. . .
}
}
启动实现Runnable接口线程的方法:
PrimeThread p = new Thread(new PrimeThread(143));
p.start();
由于java的单继承特性和面向接口编程的原则,建议使用实现Runnable接口的方式实现java的多线程。
3.使用Executors线程池:
在JDK5中,在java.util.concurrent包中引入了Executors线程池,使得创建多线程更加方便高效,例子如下:
import java.util.concurrent.*;
public class CachedThreadPool{
public static void main(String[] args){
//创建一个缓冲线程池服务
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0; i<5; i++){
//线程池服务启动线程
exec.execute(
new Runnable(){
//使用匿名内部类实现的java线程
public void run(){
System.out.println(“Thread ” + i + “ is running”);
}
}
);
//线程池服务停止
exec.shoutdown();
}
}
}
Executors.newCachedThreadPool()方法创建缓冲线程池,即在程序运行时创建尽可能多需要的线程,之后停止创建新的线程,转而通过循环利用已经创建的线程。Executors.newFixedThreadPool(intsize)方法创建固定数目的线程池,即程序会创建指定数量的线程。缓冲线程池效率和性能高,推荐优先考虑使用。
Executors.newSingleThreadPool()创建单线程池,即固定数目为1的线程池,一般用于长时间存活的单任务,例如网络socket连接等,如果有多一个任务需要执行,则会放进队列中顺序执行。
4.获取线程的返回值:
实现Runnable接口的线程没有返回值,如果想获取线程的返回值,需要实现Callable接口,Callable是JDK5中引入的现实线程的接口,其call()方法代替Runnable接口的run方法,可以获取线程的返回值,例子如下:
import java.util.concurrent.*;
import java.util.*; class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
this.id = id;
}
public String call(){
return “result of TaskWithResult ” + id;
}
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
List<Future<String>> results = new ArrayList<Future<String>>();
for(int i=0; i<5; i++){
//将线程返回值添加到List中
results.add(exec.submit(new TaskWithResult(i)));
}
//遍历获取线程返回值
for(Future<String> fs : results){
try{
System.out.println(fs.get());
}catch(Exception e){
System.out.println(e);
}finally{
exec.shutdown();
}
}
}
}
输出结果(可能的结果,由于多线程执行顺序不确定,结果不固定):
result of TaskWithResult 0
result of TaskWithResult 1
result of TaskWithResult 3
result of TaskWithResult 4
result of TaskWithResult 5
注解:使用线程池服务的submit()方法执行线程池时,会产生Future<T>对象,其参数类型是线程Callable的call()方法返回值的类型,使用Future对象的get()方法可以获取线程返回值。
5.线程休眠:
在jdk5之前,使用Thread.sleep(1000)方法可以使线程休眠1秒钟,在jdk之后,使用下面的方法使线程休眠:
TimeUnit.SECONDS.sleep(1);
线程休眠的方法是TimeUnit枚举类型中的方法。
注意:不论是Thread.sleep还是TimeUnit的sleep线程休眠方法,都要捕获InterruptedExecutors。
6.线程优先级:
线程的优先级是指线程被线程调度器调度执行的优先级顺序,优先级越高表示获取CPU允许时间的概率越大,但是并不是绝对的,因为线程调度器调度线程是不可控制的,只是一个可能性的问题。可以通过Thread线程对象的getPriority()方法获取线程的优先级,可以通过线程对象的setPriority()方法设置线程的优先级。
Java的线程优先级总共有10级,最低优先级为1,最高为10,Windows的线程优先级总共有7级并且不固定,而Sun的Soloaris操作系统有23级,因此java的线程优先级无法很好地和操作系统线程优先级映射,所有一般只使用MAX_PRIORITY(10),NORM_PRIORITY(5)和MIN_PRIORITY(1)这三个线程优先级。
7.守护线程:
守护线程(DaemonThread)是某些提供通用服务的在后台运行的程序,是优先级最低的线程。当所有的非守护线程执行结束后,程序会结束所有的守护线程而终止运行。如果当前还有非守护线程的线程在运行,则程序不会终止,而是等待其执行完成。守护进程的例子如下:
import java.util.concurrent.*;
public class SimpleDaemons implements Runnable{
public void run{
try{
System.out.println(“Start daemons”);
TimeUtil.SECONDS.sleep(1);
}catch(Exception e){
System.out.println(“sleep() interrupted”);
}finally{
System.out.println(“Finally is running”);
}
}
public static void main(String[] args) throws Exception{
Thread daemon = new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
}
输出结果:
Start daemons
Finally没有执行,如果注释掉daemon.setDaemon(true)设置守护进程这一句代码。
输出结果:
Start daemons
Finally is running
之所以产生这样的结果原因是,main()是这个程序中唯一的非守护线程,当没有非守护线程在运行时,JVM强制推出终止守护线程的运行。
通过Thread对象的setDaemon方法可以设置线程是否为守护线程,通过isDaemon方法可以判断线程对象是否为守护线程。
由守护线程创建的线程对象不论有没有通过setDaemon方法显式设置,都是守护线程。
8.synchronized线程同步:
编程中的共享资源问题会引起多线程的竞争,为了确保同一时刻只有一个线程独占共享资源,需要使用线程同步机制,即使用前对共享资源加锁,使用完毕之后释放锁。
Java中通过synchronized关键字实现多线程的同步,线程同步可以分为以下几种:
(1).对象方法同步:
public synchronized void methodA(){
System.out.println(this);
}
每个对象有一个线程同步锁与之关联,同一个对象的不同线程在同一时刻只能有一个线程调用methodA方法。
(2).类所有对象方法同步:
public synchronized static void methodB(){
System.out.println(this);
}
静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻只能有一个类的一个线程调用该方法。
(3).对象同步代码块:
public void methodC(){
synchronized(this){
System.out.println(this);
}
}
使用当前对象作为线程同步锁,同一个对象的不同线程在同一时刻只能有一个线程调用methodC方法中的代码块。
(4).类同步代码块:
public void methodD(){
synchronized(className.class){
System.out.println(this);
}
}
使用类字节码对象作为线程同步锁,类所有对象的所有线程在同一时刻只能有一个类的一个线程调用methodD的同步代码块。
注意:线程的同步是针对对象的,不论是同步方法还是同步代码块,都锁定的是对象,而非方法或代码块本身。每个对象只能有一个线程同步锁与之关联。
如果一个对象有多个线程同步方法,只要一个线程访问了其中的一个线程同步方法,其它线程就不能同时访问这个对象中任何一个线程同步方法。
9.线程锁:
JDK5之后,在java.util.concurrent.locks包中引入了线程锁机制,编程中可以显式锁定确保线程间同步,例子如下:
import java.util.concurrent.*;
import java.util.concurrent.locks.*; public class Locking{
//创建锁
private ReentrantLock lock = new ReentrantLock();
public void untimed(){
boolean captured = lock.tryLock();
try{
System.out.println(“tryLock(): ” + captured);
}finally{
if(captured){
lock.unlock();
}
}
}
public void timed(){
boolean captured = false;
try{
//对象锁定两秒钟
captured = lock.tryLock(2, TimeUnit.SECONDS);
}catch(InterruptedException e){
Throw new RuntimeException(e);
}try{
System.out.println(“tryLock(2, TimeUnit.SECONDS): ” + captured);
}finally{
if(captured){
lock.unlock();
}
}
}
public static void main(String[] args){
//主线程
final Locking al = new Locking();
al.untimed();
al.timed();
//创建新线程
new Thread(){
{//动态代码块,对象创建时执行
setDaemon(true);//当前线程设置为守护线程
}
public void run(){
//获取al对象的线程锁并锁定al对象
al.lock.lock();
System.out.println(“acquired”);
}
}.start();
//主线程让出CPU
Thread.yield();
al.untimed();
al.timed();
}
}
输出结果:
tryLock(): true
tryLock(2, TimeUnit.SECONDS): true
acquired
tryLock(): false
tryLock(2, TimeUnit.SECONDS): false
由于创建的守护线程锁定对象之后没有释放锁,所有主线程再也无法获取对象锁。
ReentrantLock可以通过lock()锁定对象,也可通过tryLock()方法来锁定对象,对于显式使用线程锁的方法体或代码块必须放在try-catch-finally块中,必须在finally中释放对象锁。
ReentrantLock和Synchronized功能是类似的,区别在于:
(1). Synchronized代码简单,只需要一行代码即可,不用try-catch-finally捕获异常,同时不用显式释放对象锁。
(2).ReentrantLock可以控制锁的锁定和释放状态,也可以指定锁定时间等。
10.volatile传播性:
volatile关键字确保变量的跨程序可见性,使用volatile声明的字段,只要发生了写改动,所有读取该字段的值都会跟着改变,即使使用了本地缓存仍然会被改变。
volatile字段会立即写入主内存中,所有的读取值都是从主内存中读取。
volatile关键字告诉编译器移除线程中的读写缓存,直接从内存中读写。volatile适用的情况:
字段被多个任务同时访问,至少有一个任务是写操作。
volatile字段的读写操作实现了线程同步。
Java编程思想学习(十六) 并发编程的更多相关文章
- (转)《深入理解java虚拟机》学习笔记10——并发编程(二)
Java的并发编程是依赖虚拟机内存模型的三个特性实现的: (1).原子性(Atomicity): 原子性是指不可再分的最小操作指令,即单条机器指令,原子性操作任意时刻只能有一个线程,因此是线程安全的. ...
- Java编程思想学习(十) 正则表达式
正则表达式是一种强大的文本处理工具,使用正则表达式我们可以以编程的方法,构造复杂的文本模式,并且对输入的字符串进行搜索.在我看来,所谓正则表达式就是我们自己定义一些规则,然后就可以验证输入的字符串是不 ...
- Java编程思想学习笔记_6(并发)
一.从任务中产生返回值,Callable接口的使用 Callable是一种具有泛型类型参数的泛型,它的类型参数表示的是从方法call返回的值,而且必须使Executor.submit来去调用它.sub ...
- Java编程思想学习(十五) 注解
注解Annotation又叫元数据,是JDK5中引入的一种以通用格式为程序提供配置信息的方式.使用注解Annotation可以使元数据写在程序源码中,使得代码看起来简洁,同时编译器也提供了对注解Ann ...
- Java编程思想学习(十四) 枚举
关键字enum可以将一组具名的值有限集合创建一种为新的类型,而这些具名的值可以作为常规的程序组件使用. 基本enum特性 调用enum的values()方法可以遍历enum实例,values()方法返 ...
- Java编程思想学习(十二) 数组和容器
一.数组 1).数组的多种初始化方式 下面总结了初始化数组的多种方式,以及如何对指向数组的引用赋值,使其指向另一个数组对象.值得注意的是:对象数组和普通数组的各种操作基本上都是一样的:要说有什么不同的 ...
- (转)《深入理解java虚拟机》学习笔记9——并发编程(一)
随着多核CPU的高速发展,为了充分利用硬件的计算资源,操作系统的并发多任务功能正变得越来越重要,但是CPU在进行计算时,还需要从内存读取输出,并将计算结果存放到内存中,然而由于CPU的运算速度比内存高 ...
- Java编程思想 第21章 并发
这是在2013年的笔记整理.现在重新拿出来,放在网上,重新总结下. 两种基本的线程实现方式 以及中断 package thread; /** * * @author zjf * @create_tim ...
- Java并发编程的艺术,解读并发编程的优缺点
并发编程的优缺点 使用并发的原因 多核的CPU的背景下,催生了并发编程的趋势,通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升. 在特殊的业务场景下先天的就适合于并发编程. 比如在 ...
随机推荐
- Android 多线程----AsyncTask异步任务详解
[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3 ...
- C++基础笔记(三)C++面向对象
C++类 C++类与结构体类似 定义 class 类名{ <成员定义>; ........ }; 文件格式 *.mm 支持 C/C++ *.cpp C++源文件 *.h C++头文件 ...
- JAVA NIO概述(一):I/O模型
NIO是jdk1.4加入的新功能,我们一般成为非阻塞IO,在1.4之前,JAVA中的都是BIO(堵塞IO),BIO有以下几个缺点: 没有数据缓冲区,I/O性能存在问题 没有C/C++中channel( ...
- Win2003_IIS+PHP+MYSQL 全能服务器配置
WIN2003_IIS+PHP+mysql最新版_全能服务器配置 本次配置PHP的服务器环境:Windows2003+IIS6+ASP+PHP5+MySQL5整个配置过程需要是使用拥有管理员权限的系统 ...
- TIF、JPG图片手动添加地理坐标的方法(转载)
题目:为TIF.JPG图片添加地理坐标/平面直角坐标. 图片来源:GOOGLE EARTH.(当然也可以是其他知道四角点坐标的图片) 截图工具:GEtscreen(此软件截图时可以自动生成图片四角点坐 ...
- Java 8新特性——default方法(defender方法)介绍
我们都知道在Java语言的接口中只能定义方法名,而不能包含方法的具体实现代码.接口中定义的方法必须在接口的非抽象子类中实现.下面就是关于接口的一个例子: 1 2 3 4 5 6 7 8 9 10 11 ...
- 使用C#改变鼠标的指针形状
1.在一个无标题的窗体中用MOUSEMOVE事件判断鼠标坐标是否到达窗体的边缘,如果是的话将鼠标指针改为可调整窗体大小的双向箭头. private void Form1_MouseMove(o ...
- PHP基础01:环境搭建
1.只会前端的只是有时候让我感到很苦恼,所以决定从今天开始学习后端,看了一些关于后端语言的比较帖子,决定选择php作为我的第一门后端语言.这个是我自己的学习笔记.方便自己复习,不写下来会太无聊了. 第 ...
- github上一款特别的侧滑
知识分享: 首先看图,我只是大自然的搬运工,想实现这种特效的请点击连接下载github地址忘掉了,....http://download.csdn.net/detail/lj419855402/860 ...
- 20135316王剑桥 linux第十一周课实验笔记
getenv函数 1.获得环境变量值的函数 2.参数是环境变量名name,例如"HOME"或者"PATH".如果环境变量存在,那么getenv函数会返回环境变量 ...