黑马程序员——java基础——多线程

来源:互联网 发布:php sleep 毫秒 编辑:程序博客网 时间:2024/06/14 21:06

                                                                              黑马程序员——java基础——多线程

一, 进程和线程的概念:
 进程: 进程是系统进行资源分配和调度的基本单位,系统为每个进程在内存中分配独立的一块区域,它是程序的一个动态执行过程。进程不同于程序,程序是静态的是保存在硬盘上的一些可执行的代码,而进程是动态的是运行中的程序。一个程序中可以有多个进程。
 线程:线程是控制程序执行最小的控制单元,有时也可以称为一个轻量级的进程,但是线程不占用系统资源,一个进程可以拥有多个线程,这多个线程可以共享进程所在内存区域的资源和数据,方便线程之间的通信。

 二, java中多线程的实现

在java中实现多线程有两种方式
A:第一种方式:直接或者间接继承Thread类 并且覆盖run()方法 ,将线程的任务代码封装到run()方法中


a:直接继承 代码演示

<span style="font-size:12px;"><strong>class Test{public static void main(String[] args){//创建线程对象MyThread th1=new MyThread();MyThread th2=new MyThread();//开启线程th1.start();th2.start();}}//自定定义线程class MyThread extends Thread{//覆盖run方法 封装线程任务public void run(){System.out.println(Thread.currentThread().getName()+"MyThread is running");}}</strong></span>


b:间接继承 代码演示

<span style="font-size:12px;"><strong>class Test{public static void main(String[] args){//创建线程对象MyThread2 th1=new MyThread2();MyThread2 th2=new MyThread2();//开启线程th1.start();th2.start();}}class MyThread1 extends Thread{public void run(){.......}}//自定定义线程class MyThread2 extends MyThread1{//覆盖run方法 封装线程任务public void run(){System.out.println(Thread.currentThread().getName()+"MyThread2 is running");}}</strong></span>

ps:就上面的代码而言,要是我们直接在Test类的main函数中调用th1 和 th2 的run方法的话,这样就只是main线程在执行run方法中的代码,而不是多线程在执行。

B:第二种方式:实现Runnable接口 覆盖run方法  将线程的任务代码封装到run()方法中

代码演示:

<span style="font-size:12px;"><strong>class Test{public static void main(String[] args){//创建线程任务对象MyTask task=new MyTask();//创建线程对象 并明确线程任务Thread th1=new Thread(task);Thread th2=new Thread(task);//开启线程th1.start();th2.saart();}}//定义线程任务class MyTask implements Runnable{//覆盖run方法public void run(){System.out.println(Thread.currentThread().getName()+"MyTask is running");}}</strong></span>


三 , 上面两种实现多线程的方式比较

1  实现Runnable接口直接将线程任务从线程对象中分离出来,按照java面向对象的思想进行了单独的封装。
2  实现Runnable避免了java单继承的局限性,假如一个类已经继承了另外一个类,就没有办法在继承Thread或者Thread的子类。
3  实现Runnable接口比较继承Thread类能更好实现资源共享。 下面用买票的例子演示资源共享。

用继承实现资源共享

<span style="font-size:12px;"><strong>class Test{public static void main(String[] args){//创建线程对象 Tickets th1=new Tickets();Tickets th2=new Tickets();//开启线程th1.start();th2.saart();}}//定义线程对象class Tickets extends Thread{//定义一个object对象作为同步锁Object obj=new Object();//定义一个变量记录票数 static int num=400;//覆盖run方法 将线程任务封装到方法里面 并用同步解决线程的安全问题 (同步下面会介绍)public void run(){while(true)synchronized(obj){if(num>0){System.out.println(Thread.currentThread().getName()+"*******"+num+"号票买了");--num;}}}}</strong></span>

用实现Runnable接口的方式实现资源共享

<span style="font-size:12px;"><strong>class Test{public static void main(String[] args){//创建Tickets实例Tickets tic=new Tickets();//创建线程对象 并明确线程任务Thread th1=new Thread(tic);Thread th2=new Thread(tic);//开启线程th1.start();th2.saart();}}//定义Tickets类实现Runnable接口class Tickets implements Runnable{//定义一个变量记录票数  int num=400;//覆盖run方法 将线程任务封装到方法里面 并用同步解决线程的安全问题 (同步下面会介绍)public void run(){while(true)synchronized(this){if(num>0){System.out.println(Thread.currentThread().getName()+"*******"+num+"号票买了");--num;}}}}</strong></span>

比较上面两种实现方式,继承Thread要想要想实现资源共享,资源必须是静态的(比如上面的num变量),要不是静态的话,每个线程对象拥有的资源是相对独立的,没有达到共享的目的,而实现Runnable接容易的方式则更容易实现资源共享,因为每个线程操作的资源都是实现了Runnable接口的类的对象的资源,这些资源供所有的线程共享。

四,多线程的安全问题

A:产生线程安全问题的原因:
当多个线程在操作共享数据,并且操作贡献数据的线程代码有多条的时候,由于cup会随机的切换到每个线程上,让线程具备CPU执行权,线程就会执行自己的线程任务,但是正是由于cup的切换时随机不可控的,就会出现某个线程正在操作共享数据的时候,cup随机切换到其他的线程,而其他的线程也在操作共享数据,这时就会出现线程安全问题。

B:解决线程安全问题
在java中是用同步的方式来线程的安全问题,同步分为同步代码块和同步函数。

a:同步代码块

class Test{public static void main(String[] args){//创建Tickets实例Tickets tic=new Tickets();//创建线程对象 并明确线程任务Thread th1=new Thread(tic);Thread th2=new Thread(tic);//开启线程th1.start();th2.saart();}}//定义Tickets类实现Runnable接口class Tickets implements Runnable{//定义一个变量记录票数  int num=400;//定义一个object对象作为同步锁Object obj=new Object();//覆盖run方法 将线程任务封装到方法里面 并用同步解决线程的安全问题 public void run(){while(true)//将需要同步的函数放在同步代码中,同步锁是objsynchronized(obj){if(num>0){System.out.println(Thread.currentThread().getName()+"*******"+num+"号票买了");--num;}}}}

b:同步函数

class Test{public static void main(String[] args){//创建Tickets实例Tickets tic=new Tickets();//创建线程对象 并明确线程任务Thread th1=new Thread(tic);Thread th2=new Thread(tic);//开启线程th1.start();th2.start();}}//定义Tickets类实现Runnable接口class Tickets implements Runnable{//定义一个变量记录票数  int num=400;//覆盖run方法 将线程任务封装到方法里面  并调用同步函数public void run(){while(true){this.sale();}}//定义一个同步函数public synchronized void sale(){if(num>0){System.out.println(Thread.currentThread().getName()+"*******"+num+"号票买了");--num;}}}

C:同步代码块和同步函数的区别:
a:同步代码块的同步锁是任意的,我们可以随意指定,但是在操作共享数据的时候,我们要保证多个线程使用是同一个锁。
b:同步函数的同步锁是this,这个是固定的。
c:当同步代码块持有的锁是this的话,我们可以将同步代码块中的代码封装到一个同步函数中。
d:静态同步函数的同步锁是该函数所在的类的字节码文件对象。

五:线程的几种状态

第一是创建状态。创建了线程对象之后,并没有调用该线程对象的start方法,这是线程处于创建状态。
第二是临时阻塞状态。当调用了线程对象的start方法之后,此时的线程具备了执行资格但是不具备执行权,就是说这个线程有资格去争夺cup资源,但是现在cup并没有处理这个线程。
第三是运行状态。线程正在被cup处理,此时线程就进入了运行状态,开始运行run函数当中的代码
第四是冻结状态。调用线程的sleep 或者 wait 方法的时候,线程就进去冻结状态,这时的线程既没有CPU执行资格,也不具有CPU执行权。
第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法开启线程。

这五种状态相互转换的示意图:


从上面的图片中我们可以知道不管是sleep 方法还是wait 方法都可以将线程变成冻结状态 现在说它之间的区别
sleep 和wait 的区别
1.wait 可以指定时间,也可以不用指定时间 而sleep方法必须要指定时间。
2,在同步中,sleep释放CPU执行权和执行资格 但是不会释放锁,而wait释放CPU执行权和执行资格的同时,还有释放锁。
3,wait 必须在同步中使用,否则会抛unchecked异常,sleep可以不用再同步中使用。

六:Thread 类中常用方法简述
public void start() 最常用的方法,顾名思义启动线程,即开始执行线程的run方法 
public void run() 如果线程重写了run方法,那么执行重写的方法,否则执行线程的Runnable接口中定义的run方法 
public final void setName(String s) 设置线程的名称 
public final void setPriority(int i) 设置线程的优先级(范围在1-10包含1,10)  Thread类中对几个特殊的优先级定义成了静态字段: MAX_PRIORITY  线程可以具有的最高优先级MIN_PRIORITY :线程可以具有的最低优先级 NORM_PRIORITY 分配给线程的默认优先级。所以我们在调用线程线程setPriority()方法的时候就可以利用Thread类定义好的静态字段作为方法的参数传递进去,
public final void setDeamon(boolean b) 设置线程是否是后台线程 该方法必须在启动线程前调用。 
public final void join() 在另外一个线程中调用当前线程的join方法,会导致当前线程阻塞,直到另一线程执行完毕,或者超过参数指定毫秒数,当前线程才会继续执行,不会释放锁.
public void interrupt() 如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法而让线程处于 冻结状态的话,然后调用处于冻结状态的线程的interrupt 方法 就可以将线程从冻结状态中恢复过来,变成运行状态或者临时阻塞状态,因为这个方法是将处于冻结状态的线程强制恢复过来,所以它将收到一个InterruptedException异常(注意不是该方法抛出一个InterruptedException 异常)
public static void yield() 这个方法可以是实现线程的礼让  暂停当前正在执行的线程对象,并执行其他线程。也就是交出CPU一段时间(其他同样的优先级或者更高优先级的线程可以获取到运行的机会) 不会释放锁



0 0