----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------
多 线 程
(一)简述多线程:
在单核CPU执行多个线程的情况下,多线程可以运行“同时”运行多部分代码,这样所带来的负面就是运行效率降低,其实,说是同时运行是不严谨的,因为单核CPU无法进行同时运行,所能做的就是CPU做着快速并且是随机的切换动作。
(二)多线程的两种创建形式:一种:继承Thread类,另一种:实现Runnable接口。
(1)继承Thread类(将类声明为 Thread 的子类,该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例)
开启线程:
线程目的:创建一个线程,去运行指定的代码(执行路径的任务)和其他代码同时运行。
run方法中定义就是线程要运行的任务代码,所以,我们在开启线程时为了运行指定的代码,就只有继承Thread类,并覆写run方法。
但是,需要注意的是:启动线程不能直接使用子类对象调用run方法(这样做就和一般对象调用成员没有什么区别,虽然线程创建了,但是并没有运行。),而是使用子类对象调用线程的start方法,该方法有两个作用,启动线程,调用run方法。直到这个时候线程才开始执行。
ThreadDemo t1=new ThreadDemo();//创建一个线程
ThreadDemo t2=new ThreadDemo();
t1.start();//开启一个线程并调用run方法
t2.start();
获取线程的名称:
在java中,线程一创建就带有从0开始的名称:public Thread() { init(null,null, "Thread-" + nextThreadNum(), 0); },但是,我们想知道当前正在运行线程的名称,那么我们就可以通过Thread.currentThread().getName()方法获取当前线程的名称。
(2)实现Runnable接口
使用接口Runnable,可以扩展类中的功能,让其中的内容作为线程的任务执行。
Demo d=new Demo();
Thread t1=newThread(d);//线程对象一创建就得有任务,然后我们在开启线程
Thread t2=new Thread(d);
t1.start();
t2.start();
此时,利用Thread创建好一个线程之后,需要做的就是将Runnable接口的子类作为Thread类的构造函数的参数进行传递(理由:因为线程的任务都封装在Runnable接口的子类对象的run方法中,所以,要在线程对象创建时就必须明确要运行的任务)
总结:
1, 使用Runnable接口,将线程的任务从线程的子类中分离出来,进行了单独的封装。按照面向对象的思想将任务封装成了对象。
2, 避免了java单继承的局限性,可以使用多实现完成。
3, 一般在开发中,常用的是:实现Runnable接口
(三)线程安全隐患
(1)产生隐患原因:当一个线程在操作共享数据的多条代码过程中,其他线程参与
运算,就会导致线程安全问题的产生。
(2)解决方式:Java对于多线程的安全问题提供了专业的解决方式-----同步代码块
格式:
synchronized(对象){
需要被同步的代码
}
案例:卖票程序
class Ticketimplements Runnable{
private int tick=100;
Object obj=new Object();
public void run(){ //覆写了接口中的run方法
while(true){
synchronized(obj){//这儿只要是对象就可以。
if(tick>0){
try{Thread.sleep(10);}catch(Exceptione){}
System.out.println(Thread.currentThread().getName()+"--theticket is--"+tick--);
}
}
}
}
}
当某个线程在判断玩tick>0这个条件以后,CPU切换到其他线程,此时,容易产生0号与-1,-2号票的问题,这在现实生活中是不允许出现的,此时,我们需要将易产生问题的代码进行同步,让多个线程持有相同的锁,只要某一个线程没有执行完,其它线程就不能执行,这便是同步的好处解决了线程安全隐患问题。
总结:使用同步时,需要注意的地方:
1.必须要有两个或者两个以上的线程
2.必须是多个线程使用同一个锁
3.必须保证同步中只能有一个线程在运行。
(3)单例模式中的安全问题(懒汉式):
class Single{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
s=new Single();
}
return s;
}
}
此单例设计模式容易产生安全问题,如;当线程—0判断完条件以后,CPU切换到线程—2中,判断为null,则满足条件,此时就产生两个对象,无法报证唯一性。
那么解决方法一:加同步函数public static synchronized Single getInstance(){},这种方法虽然解决了对象唯一性的问题,但是,当对象一进来就需要判断锁,这样程序的效率就大大的降低了。
解决方法二:加同步代码块
class Single{
private static Single s=null;
private Single(){}
public static Single getInstance(){
if(s==null){
synchronized(Single.class){//注意:这里的锁不能为this,类名.class为通用锁
if(s==null){
s=newSingle();
}
}
}
return s;
}
}
注意锁的使用问题,这里的锁不能是this和getClass,他们都是非静态的。
使用双重if条件判断语句的原因:当线程—0进来时,使用锁进入第二个if条件判断语句之后,就在那挂着,那么线程—1进来,判断null,判断锁,有线程使用,则进不了,此时,线程—0获取了执行权,返回s,结束线程。线程—1进来拿到锁了,判断第二个if条件不为空,则无法创建对象。此时,再来一个线程—2,判断第一个条件已经不在为空了,此时,直接就进不了,以后的线程都进不来了。使用if判断提高了效率,而加同步代码块,则提高了安全性。
注意:懒汉式,在面试中经常出现,考察点比较多。
总结:
(1)在同步代码块中,可以使用的锁有:
1, 自己创建一个对象当锁使用。
2, 可以使用this
3, 可以使用类名.class(也可以使类名当作锁:this.getClass())
(2)在静态的同步函数中,可以使用的锁是:可以使用类名.class,该函数所属字节码文件对象,因此,我们可以使用getClass方法获取,也就是用当前类名.class表示。
----------------------- android培训、java培训、java学习型技术博客、期待与您交流! ----------------------