Java多线程详解

什么是多线程?

多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

单核CPU时:有多个线程,A线程先运行一段时间然后切换成B线程,如此继续切换。中间切换时会有卡顿,但是只占很短的时间片,在用户眼中,就好像有多个线程在执行。

多核CPU时:多个线程确实在并发运行,每个CPU运行一个线程。如果不同CPU上的线程之间有信息需要同步,当同步时,需要将信息从一个CPU的存储空间复制去另外一个CPU的存储空间,会有一定开销。(CPU处理数据时,也是先把内存的数据拿到CPU的存储空间再进行处理)

注:我们编程的时候还是按照单线程编程,但是框架处理的时候会根据具体情况调用单线程或多线程处理

 

单核CPU什么时候用多线程?

单核CPU的情况下,如果频繁切换线程,因为中间会有暂停时间片的原因,多线程运行的时间会大于单线程运行的时间。

那么,我们在单核的情况下,究竟什么时候应该用多线程呢?

1.应用情况:耗时或大量占用处理器的任务阻塞用户界面操作,使用多线程。

2.性能情况:各个任务必须等待外部资源,就是当CPU被浪费时使用多线程。比如发送一个请求,等待响应的时间就是被浪费的,这个时间可以用来运行其他线程

 

多线程的不利方面:

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多; 

多线程需要协调和管理,所以需要CPU时间跟踪线程; 

线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;

线程太多会导致控制太复杂,最终可能造成很多Bug;

 

单核CPU多线程的线程状态

 

JAVA多线程中,线程何时进入阻塞状态?

阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU的使用权,暂时停止运行。知道线程进入就绪状态(Runnable),此时才有机会转入到运行状态。阻塞状态分为以下几种:
1. 等待阻塞(watiting):运行的线程执行wait()方法,JVM会把该线程放入等待池中。
2. 超时等待阻塞(time-waiting):就是sleep(),运行时的线程会执行带有超时时间的wait()方法,JVM会把该线程放入等待池中。当超时后,线程重新转入就绪状态。
3. 同步阻塞(synchronized):运行时的线程获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
4. 其他阻塞:运行的线程执行sleep或者join方法,或者执行I/O请求时,JVM会把该线程置为阻塞状态,当sleep状态超时,join等待线程终止或者超时是,线程重新转入就绪状态。

 

sleep()和wait方法是否持有锁?

sleep是Thread类的方法,导致此线程暂停执行指定时间,但是依然保持着监控状态,过了指定时间会自动恢复(CPU中有一个时间戳标记)。锁是用来线程同步的,sleep(long mills)虽然让出了CPU,但是不会让出锁,其他线程可以利用CPU时间片了,但如果其他线程要获取sleep(long mills)拥有的锁才能执行,则会因为无法获取锁而不能执行,继续等待。

当调用sleep方法后,当前线程进入阻塞状态。目的是让出CPU给其他线程运行的机会。但是由于sleep方法不会释放锁对象,所以在一个同步代码块中调用这个方法后,线程虽然休眠了,但其他线程无法访问它的锁对象。这是因为sleep方法拥有CPU的执行权,它可以自动醒来无需唤醒。而当sleep()结束指定休眠时间后,这个线程不一定立即执行,因为此时其他线程可能正在运行。

wait方法是Object类里的方法,当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时释放了锁对象,等待期间可以调用里面的同步方法,其他线程可以访问,等待时不拥有CPU的执行权,否则其他线程无法获取执行权。当一个线程执行了wait方法后,必须调用notify(随机唤醒一个等待该资源的线程,具体唤醒谁由操作系统决定)或者notifyAll方法才能唤醒,而且是随机唤醒,若是被其他线程抢到了CPU执行权,该线程会继续进入等待状态。由于锁对象可以时任意对象,所以wait方法必须定义在Object类中,因为Obeject类是所有类的基类。

注意:

sleep()睡眠时间只是大概的时间,并不一定精确。如果此时切换到的其他线程进行某些操作的时候,很可能会造成睡眠时间延后,但是其等待时间越长,优先级越高。

sleep和wait唤醒后,都是从上次中断的地方继续执行

多个线程想访问上锁的区域时,若发现锁被一个线程占用,会不断的询问CPU锁是否被释放。多个线程宏观来说是同时询问,而实际上是CPU分配时间片询问。

 

 

wait()和notify()的通常用法

Java多线程开发中,我们常用到wait()和notify()方法来实现线程间的协作,简单的说步骤如下:
1. A线程取得锁,执行wait(),释放锁;
2. B线程取得锁,完成业务后执行notify(),再释放锁;
3. B线程释放锁之后,A线程取得锁,继续执行wait()之后的代码;
 

notify 和 notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

 

如何线程同步?

join()和synchronized(),join内部实现也是synchronized,但是join开销大。

注意:要在线程启动后调用

oo这把锁,t2线程先持有,t2执行完才开始执行t1join方法会造成当前线程wait,就如你看到的这里的wait(0),是当前线程wait,并不是调用者wait,正如join方法的说明一样,Waits for this thread to die. 你的程序里,就是说主线程等到t1线程执行完以后再执行,主线程的wait状态,应该是由t1执行完成之后调用的notify解除。

加锁一定要尽量锁住的区域小,不然导致其他线程等待时间会过长

 

线程如何互斥访问资源?

为这个共享变量约定一个信号量。当前线程不满足该条件时,将他们wait,去执行其他线程。此线程要等满足条件后再唤醒。

 

多线程和多进程的区别

我们比较的是同一个程序里的多线程和多进程。

多线程因为在同一个进程里,所以可以共享内存和其他资源,比如迅雷里10个线程一齐下载一个文件,这个文件是由进程打开的,然后10个线程都可以往里写入东西。如果是10个进程就不行了,操作系统不允许一个文件由两个进程同时写入。

另外,Chrome就是一个典型的多进程程序,里面每个标签页、扩展、插件都是单独的进程,各自独占资源,相互隔离,一个进程出错死掉只会影响一个页面或者插件,再也不会出现Flash插件出错崩溃导致整个浏览器崩溃的情况了(一个线程崩溃不一定进程崩溃)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值