黑马程序员-学习日记
黑马程序员_ JAVA中的多线程
------- android培训、java培训、期待与您交流! ----------
一:线程的概述:
A:进程和线程的区别:
进程:正在执行的程序,代表着一个应用程序对应的内存空间。
线程:用于控制进程中运算执行流程的控制单元,或者称为执行路径。
一个进程中至少有一个线程负责该应用程序的执行。当一个进程中有了多个线程时,就成为多线程。多线程的出现的对CPU的提高了效率,提高了CPU的使用率。但是线程过多,会导致CPU的资源耗费,降低性能。
B:Java中 JVM本身也是一个多线程的应用程序。其中,有一个主线程负责java程序运行,至少还有另一个线程垃圾回收线程,负责堆内存的内存管理。主线程要运行的代码都存放在main方法中。
C:线程中的几个方法:
多线程的创建,为了对各个线程进行标识,他们有一个自己默认的名称。
格式:Thread-编号,编号从0开始。
static Thread .currentThread():获取当前线程对象;
String getName():获取线程名称;
void setName():设置 线程的名称;
Thread(String name):构造函数,线程对象一建立就可以指定名称。
二:什么时候使用多线程呢?
当多部分代码需要同时执行时。这时就要开辟多条执行路径,来完成多部分代码的同时执行。
常见的多线程程序: 下载工具,聊天工具。
三:线程的创建。
1 继承Thread类:
a,定义类继承Thread类。
b,覆盖Thread类中的run方法,将需要被多线程执行的代码定义到该run方法当中。
c,建立Thread类的子类创建线程对象。
d,调用start方法,开启线程并调用该线程的run方法。
特点:
1.当类去描述事物,事物中有属性和行为。如果行为中有部分代码需要被多线程所执行,同时还在操作属性。就需要该类继承Thread类,产生该类的对象作为线程对象。可是这样做会导致每一个对象中都存储一份属性数据。无法在多个线程*享该数据。加上静态,虽然实现了共享但是生命周期过长。
2.如果一个类明确了自己的父类,那么很遗憾,它就不可以在继承Thread。因为java不允许类的多继承。
2.实现Runnable接口:
a,定义类实现Runnable接口。
b,覆盖Runnable接口中的run方法,将需要被多线程执行的代码定义到该run方法当中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
为什么这么做呢?因为,要被多线程执行的代码都存放了Runnable接口的子类中。所以必须要明确线程对象要执行的run方法所属的对象。
e,调用Thread类的start方法,开启线程并调用Runnable接口子类对象的run方法。
特点:
1.描述事物的类中封装了属性和行为,如果有部分代码需要被多线程所执行。同时还在操作属性。那么可以通过实现Runnable接口的方式。因为该方式是定义一个Runnable接口的子类对象,可以被多个线程所操作;实现了数据的共享。
2.实现了Runnable接口的好处,避免了单继承的局限性。也就说,一个类如果已经有了自己的父类是不可以继承Thread类的。但是该类中还有需要被多线程执行的代码。这时就可以通过在该类上功能扩展的形式。实现一个Runnable接口
代码体现:
class Testimplements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
while(true){
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"...if......locka");
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"...if......lockb");
}
}
}
}
else{
while(true){
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"...else..........lockb");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"...else..........locka");
}
}
}
}
}
}
class MyLock{
public static Object locka = newObject();
public static Object lockb = newObject();
}
class DeadLockTest{
public static void main(String[] args) {
Test t1 = new Test(true);
Test t2 = new Test(false);
Thread th1 = new Thread(t1,"张三");
Thread th2 = new Thread(t2,"李四");
th1.start();
th2.start();
}
}
三:这两种方式的区别:
A.实现Runnable接口是可以将资源共享。
B.实现Runnable接口避免了单继承的局限性 。
所以建议使用实现Runnable接口的方式。
四:线程的状态:
A.创建:
通过建立Thread类的对象或者Thread类的子类对象,来完成线程创建。
当调用了start方法后,线程就具备了执行资格,但是不一定立刻具备执行权。
所以这时处于的状态是临时阻塞状态,就是在等CPU过来对其进行执行。
B. 运行:
线程如果具备了执行资格的同时,还具备了执行权,那么该线程就是在当前正在运行的线程。
这时它就处于运行状态。
C. 冻结:
释放了线程执行权,而且释放了线程的执行资格。
当线程执行到 sleep方法,或wait方法时,线程就处于这种状态。
当sleep时间到,或者被wait的线程,被notify后,
线程又重新获取到了执行资格,但是不一定获取到执行权,所以这时会从冻结状态转到临时阻塞状态。
D.消亡:
当线程调用了stop方法(过时),或者线程执行的代码已经结束了,这时该线程结束,该执行路径在进程中消失。
E. 临时阻塞:
线程具备了执行资格,但是没有执行权。
D.线程具备一个随机性:是因为cpu做着快速的切换造成的。
五:线程中安全问题:
A.安全问题产生的原因:
当线程代码中有多条语句在操作线程共享的数据。
这多条语句被多个线程分开执行时,容易产生数据的错误。
B.安全问题涉及的要素:
a,在线程代码中有共享数据。
b,在线程代码中有多条操作共享数据的语句。
只要这两个要素存在,就容易产生安全问题,这也是判断是否有安全问题的依据。
C.安全问题的解决原理:
在某一个时刻或者时间段,对于多条操作共享数据的代码,只能有一个线程进行执行;在执行期间不可以有其他线程进行参与;简单说就是将部分代码加锁。
举例:在火车上上厕所,要上锁!
D.安全问题的解决代码:
java中对这种问题,提供了具体的解决代码:同步。
1,同步代码块:
synchronized(对象){
需要被同步的代码。
}
2,同步函数:
将同步关键字修饰在函数上即可;其实同步就是使用了一个锁机制。
六:同步:
A:前提:
a,必须是两个或者两个以上的线程。
b,必须保证多个线程使用的是同一个锁。
注意:如果你发现多线程存在安全问题,而且加上同步后,安全问题没有解决,
那么要查看一下这两个前提是否符合。
B: 好处:
解决了线程安全问题。
C: 弊端:
a,会降低性能,因为每次都要判断锁。
b,同步出现,有可能出现死锁。
D: 同步函数和同步代码块具体体现区别:
同步函数使用的锁是this。
同步代码块使用的锁可以是任意对象。
特殊:静态同步函数使用的锁是 该函数所属类对象字节码文件对象。 类名.class
E:单例设计模式的懒汉式(延迟加载方式)
class Single{
int num = 100;
private static Single s = null;
private Single (){}
public static Single getInstance(){
if(s==null){
synchronized(Single.class){
if(s==null){
s = newSingle();
}
}
}
return s;
}
}
七:sleep和wait有什么区别?
对时间的指定。
1,sleep方法必须指定时间。
2,wait方法有重载形式,可以指定时间,也可以不指定时间。
对于执行权和锁的操作.
1,sleep():释放执行权,不释放锁,因为肯定能醒,肯定可以恢复到临时阻塞状态。
2,wait():释放执行权,释放锁,因为wait不释放锁,如果没有时间指定,那么其他线程都进行不了同步中,无法将其唤醒。
记住:同步中可以有多个存活的线程,但是只能有一个执行同步的代码。因为只有一个线程会持有同步的锁。
只有当该线程释放了锁,其他线程才会有机会获取到锁,而且只能用一个线程获取到锁,继续执行。
publicsynchronized void show(){
if()
wait();
code....;
notify();
code....;
}
class {
public static void main(String[] args) {
System.out.println("HelloWorld!");
}
}
八:多线程间的通讯:
A:等待/唤醒机制
1,同步。
2,wait,notify,notifyAll方法。
wait,notify,notifyAll这样的方法都属于监视器方法。
监视器你可以理解成就是那个锁。
这些方法用于操作持有该锁的线程。
一个锁对应一组监视器方法。
这些方法必须定义在同步中,并明确所在同步的锁。
简单说,这些方法必须要被锁对象调用。
如果只有一个生产者,一个消费者的,
那么可以通过 if判断标记,同时使用wait notify方法来完成等待唤醒。
当有多个生产者和消费者时,
如果通过这种方式,会出现,数据错误。 比如:一个生产者生产了两次,而只被消费一次。
问题产生的原因:本方被本方唤醒后,没有判断标记,也不清楚本方是否有执行,就进行了一次执行;会导致之前的执行的有可能无效。
解决:必须让每次被唤醒的线程都判断一次标记。所以通过while循环来判断标记。
当循环判断标记后:发现,程序居然死锁了(程序没结束当无法继续执行。)
问题原因: 还是本方唤醒本方造成的,被唤醒的本方判断完标记后,有可能继续等待。导致了所有线程都等待。
解决:为了本方唤醒对象,而又没有直接方法完成,所以就使用过了notifyAll,将所有等待线程唤醒,
如果本方线程被唤醒,继续等,但是对方也被唤醒了,这就有了执行的机会。
所以多个生产者和消费者的解决方案就是 循环判断标记while,和notifyAll。
B:到了JDK1.5的时候,有了对锁和监视器的升级对象:
在java.util.concurrent.locks包中;提供了两个对象 Lock Condition.
1.Lock接口替换了synchronized
同步函数或者同步代码块,对锁的操作是隐式的,并不直观。
而Lock将锁封装成了一个单独的对象,该对象具备获取锁和释放锁的方法。
也就是对锁的操作是显示。
Lock
lock():获取锁。
unlock():释放锁。
注意:锁本身也是一种资源。释放锁动作必须要执行。所以一般定义在finally代码块中。
2.Condition:以前监视器方法封装到了Object对象中,任意锁对象都可以使用。
现在将监视器方法封装到了Condition对象中。而Condition是通过Lock对象来获取的;Lock对象可以绑定Condition。
监视器方法:
await();
signal():
signalAll():
这样就可以把原来的代码都用新对象来表示;而且新对象提供了一个对多生产者消费者的解决方案。
Lock对象上,可以绑定多组监视器对象;就可以实现本方只唤醒对象的操作。
publicsynchronized void set(){
while(b)
wait();
code....;
b = true;
notifyAll();
}
publicsynchronized void out(){
while(!b)
wait();
code....;
b = false;
notifyAll();
}
以上代码用jdk1.5版本的改写如下:
//明确锁对象,而且明确监视器对象。和以前不同的是,
//Lock对象可以有多组监视器。
Lock lock = newReentrantLock();
//生产者的监视器。
Condition con1 =lock.newCondition();
//消费者的监视器。
Condition con2 =lock.newCondition();
Thread-0 Thread-1
public void set(){
lock.lock();
try{
while(b)
con1.await();
code....;
b = true;
con2.signal();
}
finally{
lock.unlock();
}
}
Thread-2 Thread-3
public void out(){
lock.lock();
try {
while(!b)
con2.await();
code....;
b = false;
con1.signal();
}
finally{
lock.unlock();
}
}
sleep和wait的区别:
sleep:释放执行权,不释放锁。
wait:释放执行权,释放锁。
九:多线程中的其他线程:
A.停止线程:
1,定义标记。控制住run中的循环。
2,如果线程冻结,无法执行标记,强制将其恢复到运行状态,interrupt():
该动作会放生异常。
B.守护线程:
setDaemon(boolean):当前台线程运行都结束了,后台线程无论是否执行完代码都会自动结束。
C.加入线程:
join():A线程执行到B线程的join方法是,A线程会释放执行权,等到B线程执行结束后,A在执行;B线程执行时,A线程处于冻结状态。
------- android培训、java培训、期待与您交流! ---------- 详细请查看:http://edu.csdn.net/heima/