java多线程

java多线程是java一个很重要的概念,也是平时开发中经常遇到的问题,面试也是经常会问到的,掌握多线程的基本知识显得尤为重要。

1、线程和进程

首先我们要搞清楚线程和进程之间的概念,线程是进程的子集,在Android中,通常认为一个应用程序就是一个进程,但是一个进程包含多个线程,每个线程并行执行不同的任务,相互独立,线程可以看做是操作系统最小的运行单元。

进程之间是使用不同的内存空间,但线程是共享同一块内存空间。

2、创建线程

在java中创建线程一般有三种方式:

1、继承Thread类,重写run方法,调用start方法启动

2、实现Runnable接口,重写run方法,调用start方法启动

3、实现Callable接口,可传递相关参数,重写call方法,调用call方法启动

run()和start()方法区别

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。而且调用start方法并不一定会立马启动线程,线程只是进入可运行状态,线程具体什么时候启动需要系统的调度来实现。如果直接调用run方法而不调用start方法,不会新创建线程,直接执行线程的线程体也就是run方法里面的代码

3、线程状态

New:新建状态,new出来,还没有调用start
Runnable:可运行状态,调用start进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度
Blocked:阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
Waiting:等待状态,不活动,不运行任何代码,等待线程调度器调度,wait sleep
Timed Waiting:超时等待,在指定时间自行返回
Terminated:终止状态,包括正常终止和异常终止

4、线程的阻塞、等待、终止

线程阻塞是指程序会一直等待该方法完成期间不做其他事情,ServerSocket的accept()方法就是一直等待客户端连接。这里的阻塞是指调用结果返回之前,当前线程会被挂起,直到得到结果之后才会返回。

程序运行在进入synchronized关键字修饰的代码时,线程状态会切换到阻塞,所有线程需要同步进入synchronized关键字修饰的代码,这时候并行执行的线程变成了同步执行。

而调用sleep方法或者object的wait方法,则会让线程进入等待状态,不会执行线程中任何代码,待sleep休眠时间到了或者调用了object的notify方法后,线程才会由等待状态再次切换成可运行状态,等待系统调度继续运行。

在Java中wait和seelp方法的不同;

Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。

谈谈wait/notify关键字的理解

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。

调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

线程终止的方法:   

1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 

2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。 

3.  使用interrupt方法中断线程。 

5、yield、join

这两个都是thread方法

yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制,但是这样的优先级是无法得到保障的。使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。

join()方法是为了保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。

6、线程安全

在多线程的环境下,如果多个线程对同一份数据做操作,容易导致数据不一致的情况发生,这就是线程不安全。

要实现线程安全,需要保证数据操作的两个特性:

  • 原子性:对数据的操作不会受其他线程打断,意味着一个线程操作数据过程中不会插入其他线程对数据的操作
  • 可见性:当线程修改了数据的状态时,能够立即被其他线程知晓,即数据修改后会立即写入主内存,后续其他线程读取时就能得知数据的变化
    以上两个特性结合起来,其实就相当于同一时刻只能有一个线程去进行数据操作并将结果写入主存,这样就保证了线程安全。
  • 有序性:程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致。本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。

线程安全的实现通常有以下几种方式:

1.synchronized:修饰方法或者代码块,确保线程进入时是并行的,通过对象锁的机制来实现线程间通信,通常配合object的wait/notify一起使用,修饰静态方法获取的是Class对象的锁,修饰方法是获取当前对象this的锁,修饰括号内容是获取括号内对象的锁。

Synchronized关键字保证了数据读写一致和可见性等问题,“以时间换空间”。

2.volatile:修饰某个变量,确保这个变量对所有线程的可见性,但并没有保证操作的原子性,还是会出现A、B线程同时读取到共享数据,然后各自修改导致结果覆盖的问题,所以无法保证线程安全。

synchronized和volatile比较

  • synchronized既能保证共享变量可见性,也可以保证锁内操作的原子性;volatile只能保证可见性;
  • volatile不需要同步操作,所以效率更高,不会阻塞线程,但是适用情况比较窄

7、ThreadLocal机制

ThreadLocal机制是由于修饰线程的局部变量,ThreadLocal类并不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立。通过threadlocal修饰的变量,会和当前线程状态进行绑定关联,这种变量在线程的生命周期内起作用,可以减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。ThreadLocal采用了“以空间换时间”的方式。

8、线程池

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。从JDK1.5开始,Java API提供了Executor框架让你可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)。

线程池的种类

1.FixedThreadPool:可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队
2.SingleThreadExecutor:单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的区别只有数量
3.CachedThreadPool:按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个,每次提交任务如果有空闲线程则由空闲线程执行,没有空闲线程则创建新的线程执行,适用于大量的需要立即处理的并且耗时较短的任务
4.ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE

9、synchronized与ReentrantLock的区别

1、对于Synchronized来说,它是java语言的关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock它是JDK 1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句块来完成

2、synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用; ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活。

3、synchronized是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成; ReentrantLock则可以中断,可通过trylock(long timeout,TimeUnit unit)设置超时方法或者将lockInterruptibly()放到代码块中,调用interrupt方法进行中断。

4、synchronized为非公平锁 ReentrantLock则即可以选公平锁也可以选非公平锁,通过构造方法new ReentrantLock时传入boolean值进行选择,为空默认false非公平锁,true为公平锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值