JavaSE多线程

短命女 2022-06-03 05:36 280阅读 0赞

写这篇文章的前提,楼主从学习java开始,自认为技术不错,反正肯费工夫学,为什么我现在又回来准备写一篇关于多线程文章,说真的,现在谁还用多线程?都是封装过的框架,根本不考虑什么多大问题,顶多懂点原理和安全写法就行。堆是共享数据,什么什么的····

主要是因为我面试一家游戏公司,他们根本不用tomcat或者weblogic服务器,直接手写服务器,为毛?因为游戏公司都是基于sockt通讯的,tcp或者http,而且tomcat定制起来很不方便。我打个比方,比如5000人的一个帮派同时贡献帮派经验,这个帮派经验数据怎么保证实时性?

说真的当时绞尽脑汁想了半天,说什么使用消息队列或者是redis缓存起来,或者用什么cvs算法,反正回答的自己都不满意,面试官来了句,创建一个线程,所有人数据都上传到这个线程里进行计算,说的我当时懵B了,就这么简单?所以当web开发经验的人考虑游戏服务端工程师的时候,就要把多线程和网络通讯重新深入的学一遍,至少我认为这是吃饭的家伙了。

这篇文章写的前提,简单的不提浪费时间,太难的浓缩用于面试吹B用,开发谁用这么麻烦的。所以说点稍微难的。

线程状态(有很多分发,我一般习惯分4类)

创建

运行 start,notfy

冻结sleep,wait

消亡 stop

cpu状态

cpu的执行资格:可以被cpu的处理,再处理队列中排队

cpu的执行权:正在被cpu的处理

创建线程的3种方式

1.继承Thread类,重写run方法

2.实现Runnable接口,重写run方法

3.实现Callable接口,重写call方法,此方法和run方法唯一区别在于多了返回值,并且可以对异常进行声明和抛出。

一般都使用Runnable接口方式实现多线程,这很简单,而第三种可能大家陌生,可以百度一下很简单,这个和Runnable作用一样,也是创建线程,唯一多了个返回值,这样好处是你可以得到这个线程的运行状态,说白了就是得到线程的返回值,有啥用?当然有,多线程计算,原先你计算是并行计算,我开两个线程计算不就快点,然后返回值再计算就可以了。

线程安全问题

因为cpu是来回切换的执行(随机)

你执行了if(num>0)进行下一步。这时候cpu又去执行别的线程了,而别的线程执行完了num>0把num-1小于0,这时候你执行的线程有了执行权后,就(if(num>0)因为已经走过了)执行下一步,这时候就出现安全问题。

原先会说加锁,但是为什么加锁?

加锁后,多个cpu在执行同一个资源时候,在访问锁里的资源,如果没上锁就进去,然后锁上。你只要不放锁,在里面睡觉,cpu执行权到别的线程时,线程访问这个资源,因为锁被锁住,也进不去,就会等待。保证安全性,但是性能就成了问题。但是安全和性能话,当然安全重要!!!

解决线程安全问题

同步代码块

synchronized(对象),如果把synchronized看作锁,那么对象就是钥匙,所以这个钥匙是唯一的。不然谁都可以拿钥匙进入这个代码片段。

同步函数

在方法上添加synchronized,作用和同步代码块一样,如果说同步函数和同步代码块区别在于,整体和片段的区别,如果整个方法都存在多线程安全问题。

区别:同步代码块的锁可以自定义锁

同步函数的锁,首先怎么调用方法?当然是对象.方法。所以是对象,所以对象就是锁(this)

  1. 验证,同步代码块和同步函数共同使用,这时候如果同步代码块不用this,那么存在安全问题。

静态同步函数

和同步函数一样,区别在于用的锁绝B不是this,所以锁就是类本身。this.getClass()或者类.class都可以

验证,用同步代码块和静态同步函数共同使用,这时候如果同步代码块的锁不是this.getClass(),那么存在安全问题。

总结:同步前提是,多个线程使用同一个锁。

多线程下单例设计模式

首先恶汉设计模式不存在安全问题,因为资源是存在方法区中的静态对象。只有一份(如果别人通过暴力反射,在私有构造方法加个判断就可以)

而饿汉式为什么出现线程安全问题,看代码

  1. Class Single{
  2. private static Single s=null;
  3. private Single(){}
  4. public static Single getSingle(){
  5. if(s==null){
  6. s=new Single();
  7. }
  8. }
  9. }
  10. 当多个线程访问Singel.getSingle()的时候,假如A线程走到if(s=null)满足后,被抢走执行权,执行了B线程,而B线程执行完后拿到了对象,这时候A线程或者执行权。
  11. 再走完下面的代码s=new Single(),这时候就出现安全问题(违反了单列设计模式)。
  12. 所以加锁,但是考虑效率问题,对代码重构
  13. Class Single{
  14. private static Single s=null;
  15. private Single(){}
  16. public static Single getSingle(){
  17. if(s==null){//多加一次判断是提高效率问题,如果为空就不访问锁和锁里的方法。
  18. synchronized(Single.class){
  19. if(s==null){
  20. s=new Single();
  21. }
  22. }
  23. }
  24. }
  25. }一般写饿汉模式好点,但是面试经常问道。
  26. 多线程通讯
  27. wait(),notify(),notifyAll(),用来操作线程为什么定义再了Object类中?
  28. 因为同步代码块中的锁是对象,对象代表锁,就必需有锁的一些特性,所以在Object中。
  29. 1.这些方法存在与同步中。
  30. 2.使用这些方法时必须标识所属的同步的锁。
  31. 3.锁可以是任意对象,所以任意对象调用的方法一定定义Object
  32. 通讯问题,模拟输入和输出。出现了两个资源,这时候必需保证锁是唯一的。(需要给一把锁)
  33. 输入的时候,输出进不来。反之。
  34. 多线程通讯-等待唤醒机制
  35. wait():让线程出于冻结状态,被wait的线程会被存储到线程池中。
  36. notify():唤醒线程池中一个线程(任意)。
  37. notifyAll():唤醒线程池中的所有线程。
  38. 通讯唤醒时注意
  39. 线程通讯双方的锁必需是同一把锁。
  40. 唤醒时,用到也是同一把锁的wait(),或者notify()。
  41. 思路
  42. 输入:if(flag) wait();
  43. 输出:if(!flag) wait();并且flag=flase;notify();唤醒等待线程。
  44. 这时候输入if(flag) wait();为假不执行等待方法。
  45. 输入:if(flag) wait();并且flag=true;notify();互相唤醒。
  46. 改进:
  47. 在多通讯唤醒机制时候,把if(flag)wait;改为while(flag)wait;因为if wait等待后被唤醒就继续执行下面的方法,而while会重复执行除非条件不成立。
  48. 多生产者和多消费者:while判断+wait()+notifyAll()
  49. 接口Lock
  50. jdk1.5JUC包下用于替代同步代码快的一种。此实现允许更灵活的结构
  51. 实现类有:ReentrantLock
  52. Lock lock=new ReentrantLock();
  53. lock.lock;//获取锁
  54. code....
  55. lock.unlock;//释放锁
  56. 但是
  57. lock.lock;//获取锁
  58. code....throw Exception();//出现了异常,下面的释放锁就不会执行,那么···········别的线程就进不来
  59. lock.unlock;//释放锁
  60. 所以lock.unlock最好放在finally(){}中
  61. 换了一种表现形式而已,但是注意ReentrantLock类描述:
  62. 一个可重入的互斥锁Lock
  63. 接口Condition
  64. conditionObject中的wait,notify,notifyAll分解成截然不同的对象,以便通过这些对象与任意的Lock实现组合使用。
  65. 一个lock可以对应多个Condition对象(封装的Objectwaitnotify,notifyAll),而一个Object只能对应一组。
  66. 可以看出lockCondition对应的同步代码块和Object没什么区别,但是在使用场景中就有区别。
  67. 比如生产者和消费者,如果按照后一种方式实现,Object生产完,必需notifyAll()全部的线程。你不能选择性的调用。
  68. 但是后一种方式实现,我创建两个Condition,一个代表生产者,一个代表消费者,那么生产者生成完成后唤醒消费者直接消费者.anotity()。(注意这里只唤醒消费者其中的一个而已。)
  69. 所以官方API中体现的灵活性,代表对Object封装成对象后,我针对那个对象的唤醒,而不是原先无状态的方式唤醒,哦了。
  70. class BoundedBuffer {
  71. final Lock lock = new ReentrantLock();
  72. final Condition notFull = lock.newCondition(); //创建两组监视器,如果烤鸭满了,就唤醒下面的notEmpty.anotity()就可以,灵活性增加
  73. final Condition notEmpty = lock.newCondition();
  74. final Object[] items = new Object[100];
  75. int putptr, takeptr, count;
  76. public void put(Object x) throws InterruptedException {
  77. lock.lock();
  78. try {
  79. while (count == items.length)
  80. notFull.await();
  81. items[putptr] = x;
  82. if (++putptr == items.length) putptr = 0;
  83. ++count;
  84. notEmpty.signal();
  85. } finally {
  86. lock.unlock();
  87. }
  88. }
  89. public Object take() throws InterruptedException {
  90. lock.lock();
  91. try {
  92. while (count == 0)
  93. notEmpty.await();
  94. Object x = items[takeptr];
  95. if (++takeptr == items.length) takeptr = 0;
  96. --count;
  97. notFull.signal();
  98. return x;
  99. } finally {
  100. lock.unlock();
  101. }
  102. }
  103. }
  104. 守护线程(setDeamon
  105. 和线程都一样,唯一区别在于,当前台线程执行完毕后,无论后台线程是否执行完,都强制结束。
  106. join()方法
  107. 等待该线程终止
  108. 意思是说t1.join()时,其他的线程,比如main会让出cpu的执行资格和执行权,等t1执行完后,main才会执行。
  109. 线程优先级(setPriority
  110. 让某个线程执行率高或者低

发表评论

表情:
评论列表 (有 0 条评论,280人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JavaSE 线(2)

    死锁 多线程同步的时候, 如果同步代码嵌套, 使用相同锁, 就有可能出现死锁. 为了避免死锁出现,最好不要同步代码块嵌套。 例子 饭桌上每个人都只有一边筷子,

    相关 JavaSE线

    写这篇文章的前提,楼主从学习java开始,自认为技术不错,反正肯费工夫学,为什么我现在又回来准备写一篇关于多线程文章,说真的,现在谁还用多线程?都是封装过的框架,根本不考虑什么

    相关 javaSE_线

    线程的创建和启动: A:继承Thread类创建线程 1.定义Thread类的子类,并重写run方法。 2.创建对象,调用start方法启动线程.   B:实现Runn

    相关 Javase线(1)

    多线程(1) 多线程的概述 了解多线程之前需要先了解线程,而线程依赖于进程而存在,所以先了解进程。 什么是进程 进程就是正在运行的程序。是系统进行资源分配和