黑马程序员-多线程

来源:互联网 发布:java sftp 编辑:程序博客网 时间:2024/05/23 20:20

------- android培训、java培训、期待与您交流! ----------

线程:进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间标示,线程负责的是应用程序的执行顺序

进程:正在进行中的程序。进程就是一个应用程序运行时的内存分配空间


一个进程至少有一个线程在运行。如果出现多个线程就称为多线程。每个线程在栈中都有自己的执行空间,自己的方法区,自己的变量

但是:多线程并不代表多个线程同时运行,而是因为CPU执行效率很高,看起来像是同时处理几个线程一样。实质还是一个一个的线程在执行


jvm在启动时,首先有一个主线程,负责程序的执行,调用的是main方法。所以主线程执行的代码都在main方法中。

当产生垃圾时,收垃圾的动作,由专门的一个线程去负责。这样可以提高效率


既然线程作为一个事物,那就自然会有关于线程的类,这个类为Thread

这个类中有一个run方法,专门设置线程内容,所以从java的角度考虑的话,线程就是一个类继承了Thread这个类复写了run方法。

所以任何对象都可以作线程,那么就解释了为什么有的线程方法在object类中,一个是wait,一个是notify,另一个是notifyAll

wait : 等待,将线程处于等待状态。

notify:唤醒,将指定的等待状态的线程唤醒

notifyAll:将所有等待的线程唤醒


Thread类中的主要方法

构造: Thread() :空参数构造

     Thread(Runnable r) : 这个是将Runnable类型的对象传入进来,后面会介绍

     Thread(String name) : 这个是创建了一个有名字的线程。

成员方法:

static Thread currentThread  返回这个thread类的对象引用

String getName :获取线程名称

int getProperty  获取优先级

void interrupt  中断线程

public boolean  interrupted 判断线程是否被中断

boolean  isAlive   判断线程是否是活动的

void  join  等待该线程终止 当线程终止后才能执行主线程

void run 线程所执行的内容

static void sleep(long miles) 使线程进入睡眠状态,过多少秒后继续执行

void  start   运行该线程

String toString  返回线程信息



其中有几个方法需要说明

线程的名称是通过构造或者setName来设置。如果没有设置的话,系统会自动以Thread-编号的形式来定义。编号从0开始

线程要运行的代码都写在run方法中

但是执行这个线程则需要start方法

start方法的作用: 启动线程,调用run方法

wait和sleep的区别

wait:可以指定时间也可以不指定,只能由对应的notify或者notifyAll来唤醒

线程一旦wait后将放弃执行权。释放锁

sleep:必须指定时间,时间到后自动变为运行状态(临时阻塞)

会释放执行权,但是不会释放锁

锁就是线程进行同步时的一个标示,同步时会讲到


创建线程的第一种方式:继承Thread 由子类复写run方法

一共有以下步骤:

1.定义类继承Thread类

2.复写run方法

3.创建子类对象

4.调用start方法


线程的流程解析

1.首先,线程由start方法来运行,调用run方法。

2.这时,因为cpu的它并非直接运行,而是先进入临时阻塞状态(线程具备cpu执行资格,但是没有执行权)。

3.当获取执行权后,运行该线程

4.如果使用了sleep或者wait,线程将进入冷冻状态

5.到了一定时间后,sleep执行完毕或者执行了notify语句,则将线程继续停留在临时阻塞状态。唯一不同是同步时,wait不会再持有锁,而sleep持有锁。

6.抢夺到了CPU执行权后,继续运行,运行完了之后,则run方法结束,线程结束


所以线程结束的 方法就是:run方法结束。

如果因为程序处于冷冻状态而无法结束的话,就使用interrupt方法,强制将其结束,因为是强制的,所以会抛出异常。


创建线程的第二种方式:实现runnable接口

1.定义类实现Runnable接口

2.覆盖接口中的run方法

3.通过Thread创建线程对象

4.将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数

5.调用Thread对象的start方法,开启线程,运行run方法


如果要创建线程对象,则必须new Thread或者new Thread的子类,所以这里将实现了runnable的接口传递。

为什么会有第二种形式:因为继承的局限性


如果当一个类复写了thread并且实现了runnable,那么执行的是哪个run方法呢?


new Thread(new Runnable()

{

public void run()

{

System.out.println("runnable run");

}

})

{

public void run()

{

System.out.println("subthread run");

}

}


如果出现这种情况,会优先于thread类中的run方法


当线程数量多时,一般会出现安全隐患

1.数据错误

造成原因:同时处理共享数据

解决方法:添加同步,添加一个锁,当此线程处理共享数据时,其他线程无法处理。可避免数据错误

2.死锁

因为这个线程需要A锁,进去后进入冻结状态,而另一个需要B锁,进去后也处于冻结状态,当冻结状态恢复时,进入了A的线程现在需要B锁,而进入B的线程现在需要A锁。这样互相等待对面释放锁而造成的阻塞叫做死锁。

class getLock

{

public static final Object LOCKA;

public static final Object LOCKB;

}

class DeadLock 

{

private boolean  flag ;

DeadLock(boolean flag)

{

this.flag = flag ;

}

if(flag)

{

synchronized(LOCKA)

{

System.out.println(true - LOCKA);

synchronized(LOCKB)

{

System.out.println(true - LOCKB);

}

}

}

else 

{

synchronized(LOCKB)

{

System.out.println(false - LOCKB);

synchronized(LOCKA)

{

System.out.println(false - LOCKA);

}

}

}

}

上述的假设创建两个线程A传入参数true   B传入参数false

A持有A锁打印了true-A后进入冻结状态,这时B抢到了CPU执行权,进入了B锁中这时因为A持有了A锁,B无法获取锁,所以处于阻塞状态,这时A又抢回了执行权

但是也没有B锁,所以两个线程就互相等待对方释放锁,就形成了死锁。


由此可看出,死锁其实就是同步嵌套了同步出现了问题


综上所述,当遇到了多线程安全问题时,先考虑两点

1.多个线程是否在操作共享数据

2.是否有多条语句对共享数据进行运算

如果有则添加同步。

但是同步也有安全问题:

锁是不是同一把锁。

如果不是,同步失败。

是否有两个线程以上,如果没有,不需同步。


同步代码块:synchronized{语句}  

同步函数: 函数上添加synchronized修饰符


各种同步使用的锁的类型

同步代码块:使用的锁是任意对象,最好取当前类的对象

同步函数:因为是对象调用函数,所以取得是this

静态函数:因为没有产生实例对象,所以使用的是类的字节码对象,*.class


线程间通信

思路:多线程使用同一个资源

这时,就将此资源定义成一个类,然后多线程依次定义成类。

Lock接口: 多线程在JDK1.5以后推出一个Lock接口,专门解决同步问题

替代了同步来解决多线程安全问题


Lock接口没有直接操作等待唤醒的方法,而是将这些方式又单独封装到一个对象中,这个对象就是condition。

将Object中三个方法进行单独封装,并提供了功能一致的方法:await  signal  signalAll


用法:创建一个锁对象,再创建两个线程操作锁的对象。

这样就可以实现同步了


0 0
原创粉丝点击