java之多线程

什么叫做进程?

进程是操作系统中的一个任务,他是包含了某些资源的内存区域。一个进程可以包含了一个或多个执行单元称作线程,这些线程可以被看做是同时执行的(实际是轮流占用CPU资源,快速切换,达到看似同时执行)。每个进程还有一个私有虚拟的地址空间,该空间只能被包含的线程所访问。当操作系统创建一个进程之后,该进程会自动申请一个名为主线程的线程。

什么叫做线程?

一个线程是进程的一个顺序执行流,同类的线程可以共享一块内存空间和一组系统资源。线程在切换的时候负荷很小,因此,线程也被成为轻进程。一个进程可以包含多组线程。

进程和线程的区别?

一个进程最少有一个线程,线程的划分尺度要比进程小,为什么这么谁呢。因为每个进程在执行过程中都有一块独立的内存单元,进程在切换时候需要消耗大量资源。而多个线程是共享这块内存的,在线程切换过程中并不需要切换内存单元,所以极大的提高了运行效率。多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将这些线程看做独立运行的程序来进行进程调度和资源分配。

并发与并行?

多个线程同时执行,这只是在宏观上看的。事实上是OS将CPU运行时间划分为许多时间片,获得时间片的线程执行,其他的线程等待。由于切换速度非常快,所以在宏观上是同时执行的,但微观上并不是,这种叫做并发。而并行是无论微观还是宏观都是同时执行的,这里通过多个CPU可以实现并行执行程序。

创建线程

使用Thread创建线程并启动线程

通过继承Thread类,并且重写Thread类中run方法来定义一个具体的线程。重写run方法的目的在于定义线程要执行的逻辑。启动线程不是调用run方法,而是调用线程的start()方法。start()方法将线程纳入线程调度序列,是当前线程获取时间片段后可以执行。Thread类是一个线程类,每个实例表示一个可以并发运行的线程。

class SampleThread extends Thread {

	public void run() {
		System.out.println("通过继承Thread类重写run方法实现");
	}

	public static void main(String[] args) {
		SampleThread thread = new SampleThread();
		thread.start();
	}
}

使用Runnable创建并启动线程

实现Runnable接口并且重写run方法定义线程体,然后创建线程的时候将Runnable实例传入并启动线程。这样做的好处是,将定义线程与线程要执行的任务分开来,来减少耦合。并且,java是单继承的,如果一个类继承了Thread类,就无法继承其他类了。(Thread类也是实现Runnable接口的)

在Runnable接口中,只定义了抽象run方法:

 public abstract void run();
class SampleThread implements Runnable {

	public void run() {
		System.out.println("通过继承Thread类重写run方法实现");
	}

	public static void main(String[] args) {
		SampleThread sample = new SampleThread();
		Thread thread = new Thread(sample);
		thread.start();
	}
}

使用匿名内部类创建线程

Thread thread = new Thread(){
			public void run() {

			};
		};

中断线程

当执行完run方法,或者没有捕获异常而使线程终止。但是如果想强制终止线程,可以使用interrupt方法来请求终止线程。

线程中断是改变线程的中断状态,中断之后可能是死亡、可能是等待新的任务、可能是继续执行到下一步,这个要看程序本身。线程会不断检测这个中断标识位,以判断是否应该被中断。但是如果线程被阻塞,在使用interrupt终止线程,会抛出InterruptedException异常。

使用isInterrupted来检测线程是否被中断

boolean isInterrupted()

boolean interrupted():测试当前线程是否被中断,但是这一调用会产生副作用,他将当前线程中断状态设置为false

void interrupt():发送中断请求,中断状态被设置为true

线程状态

线程可以有6中状态:

New:新创建

Runnable:可运行

Blocked:被阻塞

Waiting:等待

Timed waiting:计时等待

Terminated:被终止

通过getState()方法检测当前线程状态

新创建线程

使用new操作符创建一个线程,如new Thread(t),该线程还没有开始运行。当一个线程处于新创建状态时,说明程序还没有运行线程中的代码。

可运行线程

一旦调用start()方法,线程处于runnable状态,运行状态中的线程可能正在运行,也可能没有运行,这个要看操作系统提供的运行时间。其实通过中断就是为了让其他线程获得运行的时间。

被阻塞线程和等待线程

被阻塞线程和等待线程,他们都是暂时不活动,不运行任何代码且消耗最少的资源。直到线程调度器重新激活它。

当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。当所有其他线程释放该锁,并且调度器允许线程持有他的时候,该线程变为非阻塞状态。

当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。如调用Object.wait方法或Thread.join方法,或者等待Lock或Condition是,出现这种情况。

计时状态

有几个方法有一个超时参数,调用他们导致线程进入计时状态,这一状态将一直到超时期满或者接到适当的通知。带计时的方法Thread.sleep()、Object.wait()、Thread.join()、Lock.tryLock()、Condition.await

被终止线程

有两个原因导致线程终止:

run方法正常退出而自然死亡

因为没有捕获一个异常而终止了run方法而意外死亡

线程操作API

获取线程信息

Thread Thread.currentThread()方法:获取当前运行代码段的线程

Thread thread = Thread.currentThread();

boolean isAlive():测试线程是否处于活动状态

boolean isDaemon():测试线程是否是守护线程

boolean isInterrupted():测试线程是否已经被中断

long getId():获取线程标识符

String getName():返回线程名称

int getPriority():返回线程优先级

Thread.state getState():获取线程状态

线程优先级

线程的切换是由线程调度控制的,我们无法通过代码无法干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片的几率

线程优先级被分为10个等级,分别为1-10,其中1最小,10最大。线程提供了三个常量值表示最低、最高以及默认优先级

Thread.MIN_PRIORIITY

Thread.MAX_PRIORITY

Thread.NORMAL_PRIORITY

通过setPriority()方法进行设置

守护线程

java中线程分为两类:User Thread(用户线程)和Daemon Thread(守护线程)。Daemon线程是为其他线程运行提供便利的,如垃圾回收器就是一个守护线程。Daemon与User Thread没有什么不同,主要的不同就是虚拟机的离开:当所有User Thread全部运行退出后,只剩下Daemon Thread,这时候虚拟机也就退出了。守护进程并非只有虚拟机可以提供,用户也可以设定通过:setDaemon(boolean on)

不要轻易设置守护线程,因为你不知道User Thread什么时候完成,如果当User Thread运行完毕后,你定义的这个Daemon Thread还没运行完毕,也会被强制退出

sleep()方法

sleep(long mills)方法是使当前进程阻塞状态,进入阻塞的时间为指定的毫秒数,阻塞时间过去后进入Runable状态,等待分配时间片。这个方法在声明的时候抛出了InterruptedException,所以在使用该方法的时候也要捕获这个异常。

yield()方法

static void yield():主动让出当次时间片回到Runnable状态,等待分配时间片。

join()方法

用于等待当前线程结束,该方法会抛出InterruptedException。

线程同步

当多个线程同时读写同一临界资源的时候会发生线程并发安全问题。比如:当两个线程读取相同的对象的时候,并且每个一个线程都修改了该对象的状态,这时候错误就出现了,这样的情况称为竞争条件。

如果想解决线程安全问题,需要将异步操作变为同步操作:

异步操作:多线程并发操作,各自执行各自的

同步操作:有先后顺序的操作,你执行我不执行

锁对象

java提供了两种机制防止代码块受并发访问的干扰,一种是都熟悉的使用synchronized关键字,另一种是使用ReentrantLock类。

ReentrantLock:

Lock lock = new ReentrantLock();
		lock.lock();
		try{
			//临界区代码块,这样能够保证任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句
			//当调用lock时,他们被堵塞,直到第一个线程释放锁对象
		}finally{
			lock.unlock();
		}

unlock释放锁对象一定要放到finally中,如果临界区代码抛出异常,finally中能够释放锁对象,否则其他线程永远都将阻塞。

每个该类对象都有自己的ReentrantLock()对象,如果两个线程访问同一个该类对象,则提供串行服务。但当访问不同的对象的时候,每个线程都有不同的锁对象,两个线程不会发生堵塞。操作不同的实例,线程间是不会受影响的。

条件对象

考虑,如果当一个线程进入临界区后发现需要某个条件才能够运行,这时候他已经获得了锁对象,但是无法向下执行,java中通过条件对象(条件变量)来解决。

一个锁对象可以有多个条件对象,使用newCondition()方法获得条件对象,当到了可能需要某种条件才能向下执行的时候(可以通过判断,来确定是否条件满足)使用await()方法,这时候当前线程被阻塞,并放弃了锁。当另一线程执行完毕后,条件满足时调用signalAll()方法,这样才能激活因为一个条件而等待的所有线程。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
                lock.lock();
		try{
                         if(//判断某一个条件是否满足,如果不满足)
                                         conditon.await();
                         //临界区代码块,这样能够保证任何时刻只有一个线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句
			//当调用lock时,他们被堵塞,直到第一个线程释放锁对象
                        ...
                       //其他线程执行完之后,可能满足当前条件,重新激活
                      condition.signalAll();
               }finally{
			lock.unlock();
		}

使用synchronized关键字

将方法用synchronized关键字声明。从jdk1.0,java就为每个对象提供了一个内部锁,如果一个方法使用synchronized关键字修饰,那么对象锁将保护整个方法,即要调用该方法,线程必须获得内部锁对象。内部锁对象也有一个相关条件,wait()方法添加一个线程到等待集中,notify/notofyAll方法接触等待线程的阻塞状态。

即wait()方法相当于上面的await()方法,notify/notifyAll相当于上面的signal/signalAll方法。

	public synchronized void method(){

		if(//条件状态)
				wait();
	    ....
	    notifyAll();
	}

这种直接给方法进行加锁是当前方法都需要加锁,还有一种是通过同步阻塞。

synchronized (同步监视器—锁对象){
            //代码块
}

选择合适的锁对象:

synchronized需要对一个对象上锁以保证线程同步,那么这个对象应该保证,当多个需要同步的线程在访问该同步块时,看到的应该是同一个锁对象,否则达不到同步效果,通常使用this作为锁对象。

那么什么情况下使用显示的线程锁lock/unlock,什么时候使用synchronized关键字?

最好的情况下是都不使用,java.util.concurrnt包中的一种机制,为你所处理代码加锁。例如可以使用阻塞队列来完成

如果使用synchronized关键字适合你的程序,那么尽量使用它,可以减少代码量,防止出错。

ExecutorService实现线程池

线程池作用:控制线程数量、重用线程

当一个程序中若创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源,以及切换线程带来的危险,从而导致系统崩溃,可以使用线程池解决这个问题。

原理:首先创建一些线程,他们的集合成为线程池,当服务器从客户端接受一个请求后,从线程池取出一个空闲线程,服务完成后不关闭线程而是还回到线程池中。在线程池编程模型下,任务是提交给线程池的,不是提交给某个线程,线程池拿到任务后,在线程池中找有无空闲线程,将任务交给空闲线程。一个线程只能执行一个任务,但可以同时向线程池提交多个任务。

实现方式:

1、

ExecutorService pool = Executors.newCachedThreadPool();

创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用他们。

2、

ExecutorService pool = Executors.newFixedThreadPool(nThreads);

创建一个固定集合的线程池,共享无界队列方式来运行这些线程。

3、

ExecutorService pool = Executors.newScheduledThreadPool(corePoolSize)

创建一个线程池,安排在给定延迟后运行命令或者定期执行。

4、

ExecutorService pool = Executors.newSingleThreadExecutor();

创建一个使用单个work线程的Executor,以无界序列方式来运行该线程。

class SampleThread  {
	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(2);
		Handler handler = new Handler();
		for(int i =0 ;i<5;i++){
			pool.execute(handler);
		}
	}

}

class Handler implements Runnable{

	@Override
	public void run() {

		String name = Thread.currentThread().getName();
		for(int i=0;i<10;i++){
			System.out.println("当前执行的线程为:"+name);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("任务执行完毕");
	}

}

BlockingQueue

BlockingQueue是双缓冲序列,在多线程并发时,若需要使用队列,我们可以使用Query 。但是同步操作会降低并发对Query的操作,可以使用BlockingQuery。

BlockingQueue内部使用两条队列,可允许两个两个线程同时向队列一个做存储,一个做读操作。在保证并发安全的同时提高了队列的存取效率

当BlockingQueue为空的时候向其队列取东西,则线程将被中断进度等待状态,直到BlockingQueue有东西,该线程才会被唤醒继续操作。如果BlockingQueue队列是满的,再向里面放入东西的时候,该线程也会被中断进入等待状态,直到BlockingQueue中有空间,才会被唤醒继续操作。

  抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用

add(e):向队列中加入元素,如果BlockingQueue可以容纳,则返回true,否则抛出异常

offer(e):向队列中加入元素,如果BlockingQueue可以容纳,则返回true,否则返回false

put(e):向队列中加入元素,如果BlockingQueue可以容纳,则插入,如果不能插入则该线程进入阻塞状态,直到能够被插入

下面的方法也是这样的原理

BlockingQueue的四个实现类

ArrayBlockingQueue:规定大小的BlockingQueue,需要指定一个int型参数指定其大小,所含的对象是FIFO顺序排序的。

LinkedBlockingQueue:大小不定的BlockingQueue,如果构造函数指定一个大小,则其为BlockingQueue队列大小,如果不指定则为Integer.MAX_VALUE大小,所含对象是FIFO顺序排序的。

PriorityBlockingQueue:与LinkedBlockQueue类似,但是不是FIFO顺序排序的。而是依据对象的自然顺序或者构造函数指定的Comparator决定的顺序。

SynchronousBlockingQueue:是一种特殊的BlockingQueue,对其操作需要方和取交替执行。

其中LinkedBlockingQueue和ArrayBlockingQueue比较起来,它们背后所用的数据结构不一样,导致LinkedBlockingQueue的数据吞吐量要大于ArrayBlockingQueue,但在线程数量很大时其性能的可预见性低于ArrayBlockingQueue。

查看其源码会发现,对队列每个操作都加上了ReentrantLock锁对象。

测试offer方法,如果插入超过两秒没有进入序列返回false

	BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);

		for(int i =0;i<20;i++){
			try {
				boolean result = queue.offer(i, 2, TimeUnit.SECONDS);			//插入2秒后如果没有成功返回false
				System.out.println("存入是否成功:"+ result);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

测试poll()方法,如果超过2秒没有取出元素,则返回false

		BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(10);

		for(int i =0;i<10;i++){
			queue.offer(i);
		}
		for(int i=0;i<20;i++){
			try {
				Integer result = queue.poll(2, TimeUnit.SECONDS);   //如果两秒后取不出元素,则返回null
				System.out.println("结果:"+result);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-12 03:42:08

java之多线程的相关文章

Java基础--多线程的方方面面

1,什么是线程?线程和进程的区别是什么? 2,什么是多线程?为什么设计多线程? 3,Java种多线程的实现方式是什么?有什么区别? 4,线程的状态控制有哪些方法? 5,线程安全.死锁和生产者--消费者 6,线程的优化有哪些方法? 1,什么是线程?线程和进程的区别是什么? 线程是程序执行的最小单元. 区别: 进程是操作系统进行资源处理和分配的最小单位,而一个进程可以包含多个线程,并共享进程的资源. 2,什么是多线程?为什么设计多线程? 介绍之前,我们需要理解并行和并发的定义: 并行:同一个时刻有多

Java的多线程编程模型5--从AtomicInteger开始

Java的多线程编程模型5--从AtomicInteger开始 2011-06-23 20:50 11393人阅读 评论(9) 收藏 举报 java多线程编程jniinteger测试 AtomicInteger,一个提供原子操作的Integer的类.在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字.而AtomicInteger则通过一种线程安全的加减操作接口. 来看AtomicInteger提供的接口. //获取当前的值 publ

java实现多线程下载

本篇博客可认为是对 使用java实现http多线程下载 一文的再次解读. 首先,从宏观来说 java实现多线程下载这个功能的实现由以下几部分组成: 1 建立多个线程去分别下载文件的一部分. 2 将多个线程下载的文件(还在内存中),写入硬盘中的一个文件. 3 断点续传 GET /Path/FileName HTTP/1.0 Host: www.server.com:80 Accept: */* User-Agent: GeneralDownloadApplication Connection: c

从jvm的角度来看java的多线程

最近在学习jvm,发现随着对虚拟机底层的了解,对java的多线程也有了全新的认识,原来一个小小的synchronized关键字里别有洞天.决定把自己关于java多线程的所学整理成一篇文章,从最基础的为什么使用多线程,一直深入讲解到jvm底层的锁实现. 多线程的目的 为什么要使用多线程?可以简单的分两个方面来说: 在多个cpu核心下,多线程的好处是显而易见的,不然多个cpu核心只跑一个线程其他的核心就都浪费了: 即便不考虑多核心,在单核下,多线程也是有意义的,因为在一些操作,比如IO操作阻塞的时候

Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专家又往往建议我们远离它.比如Thread这个很基础的类,其中很重要的线程状态字段,就是用volatile来修饰,见代码 /* Java thread status for tools, * initialized to indicate thread 'not yet started' */   p

Java入门——多线程(二)

Java入门——多线程(二) 线程的状态 要想实现多线程,必须在主线程中创建新的线程对象.任何线程一般具有5种状态. 创建状态:用构造方法创建一个线程对象之后,新的线程就处于该状态.已经有了相应的内存空间和其他资源和其他资源. 就绪状态:线程进入线程队列排队,等待CPU服务. 运行状态:CPU处理,自动调用run()方法. 阻塞状态:就是在执行过程中暂时挂起.原因有:人为挂起,CPU的决定,sleep(),suspend(),wait()等方法.只有当引起阻塞的原因被消除后,线程才能转入就绪状态

Java Tread多线程(1)实现Runnable接口

作者 : 卿笃军 原文地址:http://blog.csdn.net/qingdujun/article/details/39347245 本文演示,Tread多线程实现Runnable接口,以及简单的说明为什么有这种创建线程的方法. 一.创建线程的2中方法: 1)继承Thread类实现多线程,参见我的上一篇文章:Java Tread多线程(0)一个简单的多线程实例 : 2)第二种方法就是实现Runnable接口,创建一个新线程. 二.为什么要有这两种方法创建线程呢? ①主要原因:就是方法1)不

java中多线程模拟(多生产,多消费,Lock实现同步锁,替代synchronized同步代码块)

import java.util.concurrent.locks.*; class DuckMsg{ int size;//烤鸭的大小 String id;//烤鸭的厂家和标号 DuckMsg(){ } DuckMsg(int size, String id){ this.size=size; this.id=id; } public String toString(){ return id + " 大小为:" + size; } } class Duck{ private int

[Java] 转:多线程 (并发)总结

一概念 二创建多线程方法 三线程常用方法不完整可以自己查阅JDK文档 四线程的生命周期与转换 五同步 六竞争者消费者 七线程池 八JDK 线程工具 线程基础: 1. 创建 2. 状态切换 3. sleep与wait的区别 前者使线程阻塞固定时间后进入Runnable状态,后者使用notify后可以处于可执行状态. 4. synchroized 与 Lock 区别 synchroized 可以针对当前对象.某变量设置相应的对象锁 lock 控制粒度更细,使用ReentrantLook.look()

黑马程序员——java基础——多线程

 黑马程序员--java基础--多线程 ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 进程:是一个正在执行中的程序.每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 线程:就是进程中的一个独立的控制单元.线程在控制着进程的执行.一个进程中至少有一个线程. 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区.自己的变量.