------- android培训、java培训、期待与您交流! ----------
第一部分:多线程概述‘
1、进程:正在执行的程序。
线程:进程中一个负责程序执行的控制单元(执行路径)。
2、每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。如果有多个执行路径,那就是多线程。
而线程在控制这进程的的执行。
一个进程中至少会有一个线程
3、多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花
费时间的,从而导致了效率的降低。
注:
JVM启动时启动了多条线程,至少有两个线程可以分析的出来:
1.执行main函数的线程,该线程的任务代码都定义在main函数中。
2.负责垃圾回收的线程。
第二部分:创建线程
java已经提供了对线程这类事物的描述,就是Thread类。通过Thread类来创建线程。
1、创建线程的第一种方式:继承Thread类。
步骤:
a、定义类继承Thread
b、复写Thread类中的run方法
目的:将自定义的代码存储在run方法中,让线程运行。
c、调用线程的start方法,该方法两个作用:启动线程,调用run方法
代码示例:
class Demo extends Thread
{
public void run()
{
System.out.println("demo run");
}
}
class
{
public static void main(String[] args)
{
Demo d = new Demo();//创建好线程
d.start();
System.out.println("Hello World!");
}
}
注:
a、通过对发现每次的运行结果都不相同,这是因为多个线程都在获取cpu执行权,cpu执行到谁,谁就运行。因此应该明确一点,在某一时刻只能运行一个线程(多核除外)。cpu在做着快速切换,已达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这体现了多线程的一个特性:随机性。
b、为什么要覆盖run方法?
Thread类是用来描述线程的。该类定义了一个run方法来存储线程要运行的代码。
调用start方法可以开启线程并执行该线程的run方法,而d.run()只是对象调用方法,线程并没有创建。
2、获取线程对象以及名称。
每个线程都有自己的默认名称,格式为:Thread-编号(编号从0开始)。
关键api:
获取当前线程:static Thread currentThread()。
获取线程名称:getName()。
设置线程名称:setName()或者使用构造函数构造函数super(String name)。
<pre name="code" class="java">//以下三句等效。
this.getName();
Thread.currentThread().getName()
currentThread().getName()
调用Thread.currentThread()返回的是线程对象。
3、创建线程的第二种方式:实现Runnable接口。
步骤:
a、定义类实现Runnable接口b、覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中
c、通过Thread类建立线程对象d、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数e、调用Thread类的start方法开启线程,并调用Runnable接口子类的run方法。
代码示例(以售票为例子)
//第一步:实现接口Runnable
class Ticket implements Runnable
{
private static int tick = 100;//使用静态可以让四个线程共享变量tick
//第二步:覆写run方法
public void run()
{
while(true)
{
if(tick>0)
System.out.println(currentThread().getName()+"sale:"+tick--);
}
}
}
class
{
public static void main(String[] args)
{
//第三步:创建Runnable子类对象
Ticket t = new Ticket();
//第四步:建立Thread对象,将Runnable子类对象传递进来作为参数
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//第五步:调用start()方法
t1.start();
t2.start();
t3.start();
t4.start();
}
}
注:a、为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行指定对象,就必须明确该run方法所属对象
b、实现接口Runnable的好处(重点):
(1).将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
(2).避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
第三部分:多线程的安全问题
1、多线程的运行会出现安全问题,出现问题的原因是:
当多条语句在操作同一个多线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。
2、解决方案:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程都不能执行。
在Java中对多线程的安全问题提供了专业的解决方案:使用同步代码快。
synchronized(对象)
{
需要被同步的代码
}
上面的对象参数如同锁,持有锁才可以在同步中执行,没有持有锁的线程即使获得cpu的执行权也无法执行同步的代码。
3、同步的前提:
a、必须要有两个或者两个以上的线程
b、必须是多个线程使用同一个锁。
c、必须保证同步中只有一个线程在执行。
4、同步的好处和弊端:
好处:解决了线程的安全问题。
弊端:多个线程都要判断锁,较为消耗资源。
5、代码示例(以刚才售票的例子为例)
Object obj = new Object();
public void run()
{
while(true)
{
synchronized(obj)
{
if(tick>0)
{
try{Thread.sleep(10);}catch (Exception e){}
System.out.println(currentThread().getName()+"sale:"+tick--);
}
}
}
}
6、同步的另一种方式:使用同步函数
public synchronized void method()//使用this做锁
{
需要同步的代码快
}
这时候有一个问题:同步函数的锁是哪一个?同步函数需要被对象调用,那么函数都有一个所属对象引用,就是this。所以同步函数使用的锁是this。
7、同步函数和同步代码块的区别:
a、同步函数的锁是固定的this。
b、同步代码块的锁是任意的对象。建议使用同步代码块。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,即sysnchronized(this),就可以实现同步。
8、静态同步函数的锁
public static synchronized void method()
需要同步的代码快
}
静态函数进入内存后,内存中没有本类对象,但是一定有该类对应的字节码对象。
字节码对象:当前类名.class(该类的类型是Class)或者getClass()得到。
因此,静态同步函数使用的锁是该方法的字节码文件对象(类名.class)。
第四部分:使用同步的懒汉式单例设计模式
Class Single{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
}
面试时的问题:
a、懒汉式和饿汉式有什么区别
懒汉式是实例的延迟加载
b、懒汉式延迟加载有什么问题
如果多线程会出现安全问题,可以加同步来解决
c、同步的方式
用同步函数或者同步代码快都行,但是有些低效,可以用双重判断来解决低效问题
d、同步时候使用的锁是谁
该类所属的字节码对象
f、请给我写一个延迟加载的单利设计模式
以上代码
注:饿汉式没有安全问题。
Class Single{
private static Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}