黑马程序员_多线程技术

来源:互联网 发布:开淘宝店服装去哪进货 编辑:程序博客网 时间:2024/05/18 13:30

---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

名词解析:


进程:一个程序在内存中的所处空间。进程只分配应用程序的内存空间,并不执行应用程序。

线程:就是进程中一个负责程序执行中的控制单元(执行路径)

垃圾回收器:垃圾回收线程

当垃圾回收时会执行objectfinalize方法,我们可以用system.gc()来通知垃圾回收线程要开始回收垃圾了。

并发:就是”同时”的意思

CPU执行资格:当某线程start()开启之后就有了CPU执行资格,当线程sleep()wait()时也就是处于冻结状态时就不具备CPU执行资格了。

CPU执行权:当CPU正在处理某线程时,某线程就具备了CPU执行权。

随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。

返回当前线程的名称:Thread.currentThread().getName()

线程的名称是由:Thread-编号定义的。编号从0开始。


线程生命周期:


线程状态:

被创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()

 

 

创建线程:

法一:创建一个类继承Thread

1,定义类继承Thread类;

2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;

3,通过创建Thread类的子类对象,创建线程对象;

4,调用线程的start方法,开启线程,并执行run方法。

 

法二:实现Runnable接口,然后new一个实现该接口的类,在把该实例作为new Thread的构造参数

1,定义类实现Runnable接口。

2,覆盖接口中的run方法(用于封装线程要运行的代码)。

3,通过Thread类创建线程对象;

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

为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。

5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的run方法。

Ticket t = new Ticket();

/*

直接创建Ticket对象,并不是创建线程对象。

因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。

所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。

*/

Thread t1 = new Thread(t); //创建线程。

/*

只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联

为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。

*/

t1.start();

 

实现Runnable接口的好处:

1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。

可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?

只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。

所以,通常创建线程都用第二种方式。

因为实现Runnable接口可以避免单继承的局限性。

 

2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。

 

实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。

线程方法:

开启多个线程后区分现在正在运行的线程是那个?

通过Thread.getName获取线程的名称

通过Thread.currentThread()获取当前运行的线程名

使线程冻结的方法

sleep(time)就是把该线程冻结time时间,time时间过后自动唤醒,释放执行权,释放锁。

wait()释放执行权,不释放锁。

notify()唤醒线程。

 

线程安全问题:

多线程安全问题的原因

通过图解:发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。

 

涉及到两个因素:

1,多个线程在操作共享数据。

2,有多条语句对共享数据进行运算。

原因:这多条语句,在某一个时刻被一个线程执行时,还没有执行完,就被其他线程执行了。

 

解决安全问题的原理

只要将操作共享数据的语句在某一时段让一个线程执行完,在执行过程中,其他线程不能进来执行就可以解决这个问题。

 

如何进行多句操作共享数据代码的封装呢?

java中提供了一个解决方式:就是同步代码块。

格式:

synchronized(对象) {  // 任意对象都可以。这个对象就是锁。

需要被同步的代码;

}

同步:

synchronized(对象){

要同步的代码块。

}

好处:解决了线程安全问题。

弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

 

定义同步是有前提的

1,必须要有两个或者两个以上的线程,才需要同步。

2,多个线程必须保证使用的是同一个锁。

 

同步的第二种表现形式:

同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。

 

同步函数是用的哪个锁呢?

通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁。

 

当同步函数被static修饰时,这时的同步用的是哪个锁呢?

静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。

所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是 类名.class

 

同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

 

在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。


请写一个延迟加载的单例模式?写懒汉式;当出现多线程访问时怎么解决?加同步,解决安全问题;效率高吗?不高;怎样解决?通过双重判断的形式解决。

//懒汉式:延迟加载方式。

当多线程访问懒汉式时,因为懒汉式的方法内对共性数据进行多条语句的操作。所以容易出现线程安全问题。为了解决,加入同步机制,解决安全问题。但是却带来了效率降低。

为了效率问题,通过双重判断的形式解决。

 

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; }}


静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class获取。

一般函数可以用synchronized关键字修饰,函数中持有的参数是this,可以如下证明:

 

public synchronized void show(){}

类似于

public void show(){

synchronized(this){}

}

同步代码块的意思就是将多条操作共享数据的线程代码块封装起来,当有线程在执行这些代码块的时并且没有执行完,其他线程始终进不来。

synchronized解决问题的原理:

举个生活中的例子:synchronized关键字后面的参数相当于是一把钥匙,代码块里面的内容想当于房间内的东西。现在有4个对象,当Thread-0执行到synchronized这个代码块时,看到门上面插着钥匙,所以就那着钥匙进去了,进去后CPU切换到Thread-1Thread-1执行到synchronized时看到门上面没有钥匙,所以进不去,不能动房子里面的东西。

死锁:常见情景之一:同步的嵌套

线程之间的通信:

多个线程在处理同一资源,但任务却不同  

等待唤醒机制涉及到的方法:1waitnotifynotifyAll

1、wait():让线程处于冻结状态,被wait的线程会被存储到线程池

2、notify():唤醒线程池中的一个线程(随机)

3、notifyAll():唤醒线程池中的所有线程

这些方法都必须定义在同步中,因为这些方法是操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程。一个锁对应着一个线程池。

为什么这些操作线程的方法定义在了Object类中?因为这是监视器的方法,监视器就是锁,而锁可以为任意对象。

 

线程经典例子:生产者与消费者

一个生产者和一个消费者:

比较简单,对同步和wait()notify()方法的运用

多个生产者和多个消费者:

会出现产生多个产品,但没有消费与之对应的产品个数,比如生产编号为12号的产品,但只消费了编号为1的产品。还有一种情况是只生产了编号为1号的产品,但只消费了编号为12号的2个产品,这样就不对应。

1、解决方法:在判断是什么时候生产产品的时候判断标记加入循环。

解决效果:解决了上面出现的问题,但出现了一个新的问题,死锁,因为所有的线程都被处于wait状态。

2、解决方法:把notify该为notifyAll()

Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。

解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。

 

到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。

在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。

 

所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。

 

在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

 

而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。

java.util.concurrent.locks > Condition接口await()、signal()、signalAll();

--------------------------------------------------------

class BoundedBuffer {   final Lock lock = new ReentrantLock();   final Condition notFull  = lock.newCondition();    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();     }   }  }



停止线程

1.stop方法(不占成使用)

2.run方法结束(通过控制循环,定义标记结束线程。当线程处于冻结状态就无法读取标记就不能结束)

3.Interrupt方法可以将动态线程强制恢复到运行状态中来,让线程具备CPU执行资格,但是强制中断会发生异常。

守护线程

setDaemon方法是守护线程,该线程被守护后就会在虚拟机结束后继续执行。

Thread.yield()释放执行权。

---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------

0 0
原创粉丝点击