JavaSE多线程

来源:互联网 发布:宋徽宗 知乎 编辑:程序博客网 时间:2024/06/17 13:12

写这篇文章的前提,楼主从学习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)

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

静态同步函数

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

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

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

多线程下单例设计模式

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

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

Class Single{ private static Single s=null; private Single(){} public static Single getSingle(){    if(s==null){     s=new Single();     }}}

当多个线程访问Singel.getSingle()的时候,假如A线程走到if(s=null)满足后,被抢走执行权,执行了B线程,而B线程执行完后拿到了对象,这时候A线程或者执行权。

再走完下面的代码s=new Single(),这时候就出现安全问题(违反了单列设计模式)。

所以加锁,但是考虑效率问题,对代码重构

Class Single{ private static Single s=null; private Single(){} public static Single getSingle(){    if(s==null){//多加一次判断是提高效率问题,如果为空就不访问锁和锁里的方法。     synchronized(Single.class){      if(s==null){     s=new Single();       }     }     }}}
一般写饿汉模式好点,但是面试经常问道。


多线程通讯

wait(),notify(),notifyAll(),用来操作线程为什么定义再了Object类中?
因为同步代码块中的锁是对象,对象代表锁,就必需有锁的一些特性,所以在Object中。
1.这些方法存在与同步中。
2.使用这些方法时必须标识所属的同步的锁。
3.锁可以是任意对象,所以任意对象调用的方法一定定义Object中



通讯问题,模拟输入和输出。出现了两个资源,这时候必需保证锁是唯一的。(需要给一把锁)

输入的时候,输出进不来。反之。


多线程通讯-等待唤醒机制


wait():让线程出于冻结状态,被wait的线程会被存储到线程池中。

notify():唤醒线程池中一个线程(任意)。

notifyAll():唤醒线程池中的所有线程。

通讯唤醒时注意

线程通讯双方的锁必需是同一把锁。

唤醒时,用到也是同一把锁的wait(),或者notify()。

思路

输入:if(flag) wait();

输出:if(!flag) wait();并且flag=flase;notify();唤醒等待线程。

这时候输入if(flag) wait();为假不执行等待方法。

输入:if(flag) wait();并且flag=true;notify();互相唤醒。

改进:

在多通讯唤醒机制时候,把if(flag)wait;改为while(flag)wait;因为if 后wait等待后被唤醒就继续执行下面的方法,而while会重复执行除非条件不成立。

多生产者和多消费者:while判断+wait()+notifyAll()


接口Lock

jdk1.5中JUC包下用于替代同步代码快的一种。此实现允许更灵活的结构

实现类有:ReentrantLock

Lock lock=new ReentrantLock();

lock.lock;//获取锁

code....

lock.unlock;//释放锁

但是

lock.lock;//获取锁

code....throw Exception();//出现了异常,下面的释放锁就不会执行,那么···········别的线程就进不来

lock.unlock;//释放锁

所以lock.unlock最好放在finally(){}中

换了一种表现形式而已,但是注意ReentrantLock类描述:

一个可重入的互斥锁Lock。

接口Condition

condition将Object中的wait,notify,notifyAll分解成截然不同的对象,以便通过这些对象与任意的Lock实现组合使用。

一个lock可以对应多个Condition对象(封装的Object的wait,notify,notifyAll),而一个Object只能对应一组。

可以看出lock和Condition对应的同步代码块和Object没什么区别,但是在使用场景中就有区别。

比如生产者和消费者,如果按照后一种方式实现,Object生产完,必需notifyAll()全部的线程。你不能选择性的调用。

但是后一种方式实现,我创建两个Condition,一个代表生产者,一个代表消费者,那么生产者生成完成后唤醒消费者直接消费者.anotity()。(注意这里只唤醒消费者其中的一个而已。)

所以官方API中体现的灵活性,代表对Object封装成对象后,我针对那个对象的唤醒,而不是原先无状态的方式唤醒,哦了。

 class BoundedBuffer {   final Lock lock = new ReentrantLock();   final Condition notFull  = lock.newCondition(); //创建两组监视器,如果烤鸭满了,就唤醒下面的notEmpty.anotity()就可以,灵活性增加   final Condition notEmpty = lock.newCondition();    final Object[] items = new Object[100];   int putptr, takeptr, count;   public void put(Object x) throws InterruptedException {     lock.lock();     try {       while (count == items.length)          notFull.await();       items[putptr] = x;        if (++putptr == items.length) putptr = 0;       ++count;       notEmpty.signal();     } finally {       lock.unlock();     }   }   public Object take() throws InterruptedException {     lock.lock();     try {       while (count == 0)          notEmpty.await();       Object x = items[takeptr];        if (++takeptr == items.length) takeptr = 0;       --count;       notFull.signal();       return x;     } finally {       lock.unlock();     }   }  }

守护线程(setDeamon)

和线程都一样,唯一区别在于,当前台线程执行完毕后,无论后台线程是否执行完,都强制结束。

join()方法

等待该线程终止

意思是说t1.join()时,其他的线程,比如main会让出cpu的执行资格和执行权,等t1执行完后,main才会执行。

线程优先级(setPriority)

让某个线程执行率高或者低