您的位置:首页 > 职场人生

黑马程序员——学习笔记06.Java多线程

2014-01-04 11:15 162 查看
----------------------ASP.Net+Android+IOS开发.Net培训、期待与您交流!
---------------------

基本概念

1.进程:正在运行中的程序,也就是一个程序被启动后内存中的开辟的空间,负责分配开辟的内存空间。

2.线程:就是进程中一个负责程序执行的控制单元(执行路径)。一个进程中可以有多个线程,就叫做多线程。进程中的任务执行都是cpu在做着快速切换的动作,这个切换是随机的

3.进程和线程的关系:一个进程中至少有一个线程。开启多个线程是为了同时执行多部分代码。每一个线程都有自己执行的内容,这个内容可以称为线程要执行的任务。

4.多线程的好处是解决了多部分同时运行的问题,弊端是线程太多会使效率降低,并没有减少工作任务。

注意:

main线程结束了,虚拟机不一定结束,因为JVM会启动了就会开启了多个线程,main线程只是其中的一个线程

多线程的使用

1.创建线程的目的:是为了开启一条执行路径,去运行指定的代码和其他代码实现同时执行。而运行的指定代码就是这个执行路径的任务。

2.创建线程的实现途径:Thread类用于描述线程,线程是需要任务的。所以Thread类也对任务有描述,这个任务就是通过Thread类中的run方法来体现的。换句话说,run方法就是封装自定义线程运行任务的函数。run方法中定义的代码就是线程要运行的任务代码,然后由start()方法来调用。

3.创建线程:

方法一:

(1).自定义一个类继承Thread

(2).覆写run方法,将执行任务的代码写入到run方法体中

(3).创建Thread的子类对象创建线程

(4).调用子类start方法开启线程并调用线程才任务 run方法执行

代码演示:

class ThreadDemo extends Thread{
  public static void main(String [] args){
ThreadDemo  td= new ThreadDemo();
td.start();
}
void run(){
System.out.print("ThreadDemo is starting");
}

}

注意:每启动一个线程,就会在内存中创建一个单独的栈区域,来时执行任务代码

方法二:

(1).定义类实现Runnable接口

(2).覆写接口的run方法

(3).通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务

(4).调用Thread对象的start方法开启线程

两种方法的比较:

1.第一种方法比较局限,当定义的类要继承别的类时就会出现多继承,java是不允许的。

2.第一种方法是将定义的类实现称为一个线程,直接开启独立的任务,第二种是想任务封装成对象,由线程对象来开启,比较符合面向对象的编程思想

线程安全问题

通过卖票实例可以观察多线程出现的安全问题

代码演示:卖票的例子

class ThreadTest extends Runnable{
int ticket = 100;
@Override
public void run(){
while(true){
if(ticket>0)
System.out.print(Thread.currentThread().getName()+ticker--);
}
}
}
class Test{
public static void main(String[] args){
ThreadTest  tt= new ThreadTest();
Thread   t1 = new Thread(tt);
Thread   t2 = new Thread(tt);
Thread   t3 = new Thread(tt);
t1.start();
t2.start();
t3.start();
  }
}


上面代码演示最终会出现有0号,-1号的票售出,为什么?

线程安全问题产生的原因:

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

2.操作共享数据的线程任务代码有多条(数据变化后没有及时传递给其他线程)

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

解决思路:

就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候其他线程不可以参与进去。

必须要当前线程把这些代码执行完毕后,其他线程才可以参与运算

在java中,用同步代码块和同步函数能解决这个问题。

同步代码块的格式:

synchronized(对象)

{

需要被同步的代码

}

同步函数的格式:

返回类型 synchronized 方法名(){

需要同步的代码

}

同步的好处:能解决线程安全的问题

同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。

同步的前提:同步中的多线程必须使用的是用一个锁

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

单例的多线程安全问题:

单例设计模式一:
class Single{
private final static Single s  = new Single();
private Single(){}
public static Single getInstance(){
return s ;
}
}
单例设计模式二:
class Single{
private static Single s = null;
private Single(){}
public static Single getInstance(){
if(s == null){
s = new Single();
}
return s ;
}
}

对于之前学习单例设计模式二(延迟加载设计)如果应用在多线程中是存在线程安全问题的。应使用同步代码块或者同步函数,但使用同步函数效率较低,因为每次其他线程要进行锁的判断,消耗资源较大,效率较低。所有使用同步代码块加上判断条件。修改后的线程安全的并较合理的代码如下

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

同步的锁具有临时阻滞的功能造成死锁

同步的死锁现象体现之一:不同状态的线程都持有锁,都没有释放,造成出不来和进不去。

同步的死锁造成条件:有两个锁,一个锁嵌套在另一个锁之中或者拥有一种锁的线程全部处于休眠状态。一个线程只释放了其中的一个锁,并且释放锁有顺序,后得到的锁先释放,另外一个线程得到了前一个线程释放的锁之后立马锁定

等待唤醒机制.

线程间通讯:多个线程在处理同一资源,但是任务不同

涉及的方法:

1.wait():让线程处于休眠状态,这种状态的线程会被存储到线程池中

2.notify():唤醒线程池中一个线程(任意)

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

这些方法都必须定义在同步区域中,因为这些方法都是用于操作线程状态的方法必须要明确到底操作的是哪个锁上的线程。

为什么操作线程的方法wait,notify,notifyAll定义在Object类中呢?因为这些方法都监视器的方法,也就是我们常说的同步锁。锁可以是任意的对象,而任意的对象都是继承Object类的,所有这些方法都定义在Object中使用较为方便

wait 和sleep的区别:

1.wait可以指定时间也可以不指定

sleep必须指定时间。

2.在同步中时,对cpu的执行权和锁的处理不同。

wait:释放执行权也释放锁。

sleep:释放执行权,不释放锁。

将同步和锁封装成对象

dk1.5以后将同步和锁封装成了对象。并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

java.util.concurrent.locks中的接口Lock替代了synchronized的功能

锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁。对共享资源的所有访问首先都需要获得锁。不过,某些锁可能允许对共享资源并发访问。synchronized方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获得和释放均要出现在一个块结构中,当获得多个锁时,它们必须以相反的顺序释放,且必须与所有锁被获得时相同的词法范围内释放所有锁。

Lock接口

它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成显示锁操作。更为灵活,可以一个锁上加上多组监视器对象。

基本方法:

lock():获取锁。

unlock():释放锁。通常需要定义finally代码块中。(因为同步代码中如果出现了异常,也必须要释放锁,释放资源)

newCondition():创建锁上的监视器对象。

Condition接口

它的出现替代了Object中的wait notify notifyAll方法。将这些监视器方法单独进行了封装,变成了Condition监视器对象。可以和任意锁进行组合。

await():监视该组线程等待

asignal();

asignalAll();

在资源中创建锁对象代码演示

1.创建一个锁对象

Lock lock = new ReentrantLock();

2.通过已有的锁获取该锁上的监视器对象,可创建两组监视器,一组监视生产者,一组监视消费者

Condition con1 = lock.newCondition();

监视消费者

Condition con2 = lock.newCondition();

Thread类的基本方法

停止线程

1.stop 方法

2.run方法结束。

任务中都会有循环结构,只要控制循环就可以结束任务。

控制循环通常就定义标记来完成

但是如果线程处于wait状态,就无法读取标记,如何结束呢?

可以使用interrupt方法将线程从wait状态强制恢复到运行状态中去,让线程具备cpu的执行资格,但调用了interrupt方法后要处理异常

后台线程

setDaemon(boolean)在线程启动前设为守护线程(后台线程)

后台线程在启动后跟前台线程一样都随机获取cpu执行权,当前台线程都结束了,后台线程会自动结束不需要手动结束,JVM也退出.

加入线程

join(),在哪个线程中加入,被加入的线程就获取这个线程的执行权和执行资格,一直到任务完成后,这个线程才开始运行。

yeild()让线程暂停一会

ThreadLocal的使用:它保证了多线程在线程内要共享,线程外要独立。实现线程范围内的共享变量。

对于多线程共享资源的问题使用代码表示的不同方式

方法一:

1. 将资源封装到一个类中,向外提供操作资源的方法,为了保证多线程的资源同步,将方法定义成同步函数。

2. 定义类实现Runnable,将资源对象作为线程的构造方法参数传递给线程得到这个资源。覆写run方法,方法体就是操作资源。

3. 在主类中创建资源和不同的线程对象,然后启动线程。

方法二:

1. 将资源封装到一个类中,向外提供操作资源的方法,为了保证多线程的资源同步,将方法定义成同步函数。

2. 在主类中定义匿名内部类,并创建资源对象(在main方法内部创建,就必须定义成final常量,在主类中定义成成员变量,就必须定义成static)内部线程类对象可以直接通过资源对象来调用操作资源的方法。

方法三:

1. 将资源封装到主类中,操作资源的方法也定义在主类中作为成员方法(保证同步)。

2. 在主类中定义线程类作为成员内部类,然后在内部创建线程并启动。

Java.util.concurrent

线程并发库包

Executors:是一个线程池工具类,封装了很多静态函数

Executor

|-----ExecutorService(线程池)

|-----ScheduledExecutorServices(相当于定时器的功能)

线程池的概念:节省资源,提高效率,优化系统

//创建一个固定容量的为3的线程池
ExecutorService threadpool = Executors.newFixedThreadPool(3);
//创建单一线程容量的线程池
ExecutorService  threadPool = Executors.newSingleThreadPool()
//創建了10任務线程
for( int i =0 ;i<10;i++){final修饰的不能改变
final int task = i;//内部类访问外部类的成员要加final修饰
threadPool.execute(new Runnable(){//执行任务
public void run(){
for(int j =0;j<5;j++)
System.out.println(Thread.currentThread().getName()+"is looping of"+j+"for task"+task);
}
} );
}
theadPool.shutdown();//任务执行结束关闭线程池,结束线程
threadPool.shutdownNow();//强制结束线程。

这个线程池安排了10个任务,但线程池中只有3个线程去完成,先完成前3个任务

再接着完成下3个任务,直到任务结束。

ScheduledThreadPoolExecutor tPool =Executors.newScheduledThreadPool(3)
//创建一个带有定时功能的线程池
tPool.schedule(Runnable task,long time,TimeUnit unit);
//执行线程池的任务在指定的time时间后执行,unit是时间的单位。TimeUnit是时间单位枚举
threadPool.scheduleAtFixedRate(Runnable task,long delayTime,long rateTime,TimeUnit unit);
//执行线程池的任务在固定的频率连续执行,delayTime是第一次执行任务的时间,rateTime是以后每次间隔的时间就执行一次


使用缺点:传入的时间参数都是相对时间,不是具体某一个绝对时间,但可以通过Date类获取某个时刻的相对时间作为参数传入.

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

详细请查看:http://edu.csdn.net
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: