• 欢迎来到本博客,希望可以y一起学习与分享

Java多线程与并发

Java benz 6个月前 (03-16) 9次浏览 0个评论 扫描二维码

进程与线程的区别

进程:是资源分配的最小单位,操作系统会给进程分配cpu和内存资源等。进程可以说是程序的一次运行,而一个进程可以有多个线程
线程:是操作系统调用执行的最小单位,同时一个线程可以有多个协程
一个进程由系统给分配资源,然后进程中的多个线程共享该进程的所有资源。线程不能脱离进程而存在。
协程:比进程更加轻量级的存在。协程不是被操作系统内核所管理的,而完全是由程序所控制的。线程之间的切换需要消耗资源,但是协程之间的切换不需要消耗资源

典型的调度算法

(1)FIFS算法(先来先服务调度算法)
就是那个那个进程先进来,操作系统就先执行那个进程,这就是先来先服务
(2)SJF算法(短作业优先调度算法)
从等待队列中选择一个或者若干个估计运算时间最短的作业,将他们调入内存运行。
缺点:

  • 1)对长作业不利。
  • 2)没有考虑到作业的优先级。
  • 3)由于作业的长短只根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意的缩短其作业的估计运行时间,致使该算法不一定能真正做到算作业优先调度。
  • 4) 注意:SJF调度算法的平均等待时间、平均周转时间最少。

(3)优先级调度算法
按照优先级的高低,来选择调入进程调入内存运行。
(4)时间片轮转
指定每个进程的运行时间,如果在这个时间内,进行没有完成,将进程进入就绪态,等到下一次轮到了在继续执行。

线程的start()和run()方法的区别

start()方法: 它会启动一个新线程,并将其添加到线程池中,待其获得CPU资源时会执行run()方法,start()不能被重复调用。

run()方法:它和普通的方法调用一样,不会启动新线程。只有等到该方法执行完毕,其它线程才能获得CPU资源。

run()只是简单的调用,start()是开启新的线程

运行结果:

main is running
main is running
mythread is running

结果说明:
(01) Thread.currentThread().getName()是用于获取“当前线程”的名字,当前线程是指正在cpu中执行的线程。
(02) mythread.run()是在“主线程main”中调用的,该run()方法直接运行在“主线程main”上。
(03) mythread.start()会启动“线程mythread”,“线程mythread”启动之后,会调用run()方法;此时的run()方法是运行在“线程mythread”上。

Thread和Runnable区别

1、Thread是类,Runnable是接口,而且Thread是实现了Runnable接口的类,使得run()支持多线程
2、因类的单一继承原则,推荐多使用Runnable接口
网上结论就是:

1、效果上没区别,写法上的区别而已。
2、没有可比性,Thread实现了Runnable接口并进行了扩展,我们通常拿来进行比较只是写法上的比较,而Thread和Runnable的实质是实现的关系,不是同类东西。

无论你使用Runnable还是Thread,都有一个new Thread的过程,效果上最后都是new Thread,然后执行run方法。写法上的区别无非就是你是new Thead还是new你自定义的thread,如果你有复杂的线程操作需求,那就自定义Thread,如果只是简单的在子线程run一下任务,那就自己实现runnable,当然如果自己实现runnable的话可以多一个继承(自定义Thread必须继承Thread类,java单继承规定导致不能在继承别的了)。

如何给run()方法传参

1、构造函数传参
2、成员变量传参
3、回调函数传参

如何实现处理线程的返回值

1、主线程等待法 缺点:需要自己实现循环等待的逻辑,当需要等待的变量较多时,代码异常臃肿。
2、使用thread类的join()阻挡当前线程以等待子线程处理完毕。 缺点:控制力度不够精细。
3、通过callable接口实现,通过FutureTask Or 线程池获取。
一、那么,直接上代码吧,我们首先开始第一种方法。先创建一个类CycleWait,如下所示:

在循环到CycleWait执行完成时,会输出结果 we have date now。
二、去掉循环体,使用join方法。给一返回结果一样。

三、使用FutureTask获得结果,进行控制。

四、线程池的方式。好处:可以实现提交多个myCallable方法的线程,是线程池并发的去处理结果。

线程状态

(1)初始状态(new):新创建一个线程对象,但是还没有调用start();
(2)运行状态(runnable):java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行状态”。线程创建之后,其他线程(比如main线程)调用该对象的start()方法,改状态的线程位于可运行的线程池中,等待被线程调度,获取cpu使用权,此时处于就绪态,就绪态的线程获取cpu时间片后变成运行中状态。
(3)阻塞状态(blocked):线程没有获得锁资源进入阻塞态。
(4)等待(waiting):进入该状态的线程需要等待其他线程做出一些特定动作(通知或者中断)

  1. 没有设置TimeOut参数的Object.wait()方法
  2. 没有设置TimeOut参数的Thread.join()方法
  3. LockSupport.park()方法

(5)超时等待(timed_waiting):该状态不同于waiting,他可以在指定的时间后自动返回

  1. Thread.sleep()方法
  2. 设置了TimeOut参数的Object.wait()方法
  3. 设置了TimeOut参数的Thread.join()方法
  4. LockSupport.parkNanos()方法
  5. LockSupport.parkUntil()方法

(6)终止(terminated):表示该线程已经执行完毕。


注意:调用start()进入的是就绪态而不是running状态。

线程状态的转换

sleep()和wait()的区别

基本区别:
1、sleep()是Thread类的方法,wait()是Object类中定义的方法
2、sleep()方法可以在任何地方使用
3、wait()方法只能在synchronized方法或synchronized块中使用
最主要区别:
Thread.sleep()只会让出CPU,不会导致锁行为改变;
Object.wait() 不仅让出CPU,还会释放已经占有的同步资源锁。
验证:

1、在我们的第一个线程先start之后呢,我们的主线程就sleep 10 ms,次让第二个线程start.
* 那么第一个线程开始执行之后,获得同步锁,然后wait一秒钟,在第一个线程wait一秒钟的时候,第二个线程已经开始
* 执行,如果此时,我们的线程一并没有释放lock的话,那么线程二就会被阻塞,不能执行代码里面的逻辑,如果释放锁的话,
* 第二个线程是可以执行的。

2、掉个个之后呢?可以看出B是连续执行的,也就是说B sleep一秒钟的同时,并没有放出资源,等到休眠玩一秒钟,又继续执行,B完全执行完才会释放出资源。

notify与notifyAll的区别

两个概念
1、锁池EntryList
2、等待池WaitSet
锁池
假设线程 A 已经拥有了某个对象(不是类)的锁,而其它线程 B、C 想要调用这个对象的某个 synchronized 方法(或者块),由于这些 B、C 线程在进入对象的 synchronized 方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程 A 所占用,此时 B、C 线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池。
等待池
假设线程 A 调用了某个对象的 wait() 方法,线程 A 就会释放该对象的锁(因为 wait() 方法必须在 synchronized中使用,所以执行 wait() 方法前线程 A 已经持有了该对象的锁),同时线程 A 就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。如果此时线程 B 调用了相同对象的 notifyAll() 方法,则处于该对象等待池中的线程就会全部进入该对象的锁池中去准备争夺锁的拥有权。而如果此时线程 B 调用的是相同对象的 notify() 方法,则仅仅会有一个处于该对象等待池中的线程(随机)进入该对象的锁池中去准备争夺锁的拥有权。
区别
notifyAll 会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。

yield

概念
当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU(不会让出锁)使用的暗示,但是线程调度器可能会忽略这个暗示。

上面例子所示,两个线程,各循环10以内的数字,每当执行到5 时,就会给程序调度器一个暗示,表示可以让出线程。程序执行结果如下:

可以多执行几次,可以看到每次执行结果,不一定是到5了让出资源,可能会一直执行下去,表示虽然给程序调度器暗示可以让出资源,但是资源调度器却忽略了这个暗示。

总结:

yield 和 sleep 的异同
1)yield, sleep 都能暂停当前线程,sleep 可以指定具体休眠的时间,而 yield 则依赖 CPU 的时间片划分。

2)yield, sleep 两个在暂停过程中,如已经持有锁,则都不会释放锁资源。

3)yield 不能被中断,而 sleep 则可以接受中断。

如何中断线程

已抛弃的方法:
stop()、suspend()、resume() 突然停止,会导致线程的一些清理工作无法完成,造成锁被释放,引发数据不同步的问题。
目前使用的方法
interrupt(),通知线程应该中断了
1、如果线程处于阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
2、如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
所以interrupt()不能强制中断线程,需要被调用的线程配合中断。

Java中有几种生成线程的方法

两种:
①实现Runnable接口,重写run()
②继承Thread类

Synchronize的使用

锁住代码块,锁住类的实例方法,锁住类(或者静态方法)
(1)synchronize锁住代码块时,这需要取得指定对象的锁才能够进入代码块。
(2)synchronize锁住实例方法时,需要获取当前实例对象的锁
(3)synchronize锁住类或者静态方法时,需要获得当前类的锁

CountDownLatch和CyclicBarrier的区别

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
CountDownLatch和CyclicBarrier都有让多个线程等待同步然后再开始下一步动作的意思,但是CountDownLatch的下一步的动作实施者是主线程,具有不可重复性;而CyclicBarrier的下一步动作实施者还是“其他线程”本身,具有往复多次实施动作的特点。

死锁的产生

四个必要条件:
(1)不可抢占:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(2)循环等待:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求
(3)保持与请求:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。

怎么预防死锁,怎么避免死锁

预防死锁:破坏四个必要条件中的一个,或几个,就可以预防死锁
避免死锁:典型的是——银行家算法


文章 Java多线程与并发 转载需要注明出处
喜欢 (0)

您必须 登录 才能发表评论!