JavaSE多线程
写这篇文章的前提,楼主从学习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)
让某个线程执行率高或者低
还没有评论,来说两句吧...