黑马程序员--线程

来源:互联网 发布:mysql my.cnf 路径 编辑:程序博客网 时间:2024/06/08 11:31

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------


一、线程基本知识

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

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

 

多线程:一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。开启多个线程是为了同时运行多部分代码。每一个线程都有自己运行的内容。这个内容可以称为线程要执行的任务。

 

多线程好处:解决了多部分同时运行的问题。

多线程的弊端:线程太多会效率的降低。

其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。

JVM启动时就启动了多个线程,至少有两个线程可以分析的出来。

1,执行main函数的线程,

该线程的任务代码都定义在main函数中。

2,负责垃圾回收的线程。

 

 

二、Thread创建线程

 

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

步骤:

1,创建一个新的线程类,继承Thread类并覆盖Thread类的run方法;

 

class ThreadType extends Thread{

    publicvoid run(){

    …

    }

}

 

2,创建一个线程类对象,创建方法与一般对象的创建相同,使用关键字new完成;

ThreadType tt = new ThreadType();

 

3,启动新线程对象,调用start()方法;

tt.start();

4,线程自己调用run()方法。

package ThreadDemo;class ThreadDemo extends Thread{private String name;ThreadDemo(String name) {this.name = name;}public void run() {for (int i = 0; i < 10; i++) {System.out.println(name+"...i="+i+"...ThreaName="+Thread.currentThread().getName());}}}public class CreateThreadDemo {public static void main(String[] args) {ThreadDemo d1 = new ThreadDemo("张三");  //创建线程ThreadDemo d2 = new ThreadDemo("李四");  //创建线程d1.start(); //开启线程,调用run方法d2.start();for (int i = 0; i < 20; i++) {System.out.println("i="+i+"...over..."+Thread.currentThread().getName());}}}


创建线程的第二种方式:实现一个接口Runnable。

 

利用实现Runnable接口来创建线程的方法可以解决Java语言不支持的多重继承问题。

 

步骤:

1,创建一个实现Runnable接口的类,并且在这个类中重写run方法。

class ThreadType implement  Runnable{

public void run(){

......

}

}

2,使用关键字new新建一个实现了Runnable接口的子类对象。

  Runnable rb = new ThreadType();

3,通过Runnable的实例创建一个线程对象,在创建线程对象时,调用的构造函数是new Thread(ThreadType),它用ThreadType中实现的run()方法作为新线程对象的run()方法。

Thread td = new Thread(rb);

4,通过调用ThreadType对象的start()方法启动线程运行。

td.start();

package ThreadDemo;class ThreadType implements Runnable {public void run() {show();}public void show() {for (int i = 0; i < 20; i++) {System.out.println(Thread.currentThread().getName()+"..."+i);}}}public class CreateThreadTest {public static void main(String[] args) {ThreadType d = new ThreadType();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t2.start();}}

三、线程的周期

线程的创建仅仅是线程生命周期中的一个内容,线程的整个周期由线程创建、运行、冻结和消亡等部分组成,这些状态之间的转化是通过线程提供的一些方法完成的。

线程状态:

被创建:start()

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

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

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

消亡:stop()




四、线程同步

  Java应用程序中的多线程可以共享资源,例如文件、数据库、内存等。当线程以并发模式访问共享数据时,共享数据可能会发生冲突。Java引入线程同步的概念,以实现共享数据的一致性。线程同步机制让多个线程有序的访问共享资源,而不是同时操作共享资源。

1、同步概念

  在线程异步模式的情况下,同一时刻有一个线程在修改共享数据,另一个线程在读取共享数据,当修改共享数据的线程没有处理完毕,读取数据的线程肯定会得到错误的结果。如果采用多线程的同步控制机制,当处理共享数据的线程完成处理数据之后,读取线程读取数据。

 

2、同步的格式

定义同步是有前提的:

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

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

 

 

同步代码块

格式:

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

    需要被同步的代码;

}

 

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

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

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

 

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

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

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

这个对象就是类名.class

 

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

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

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

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

 

4、线程应用

package ThreadDemo;/* * 需求:模拟4个线程同时卖100张票 */class Ticket implements Runnable {private int num = 100;public void run() {while (true) {if(num>0){try {Thread.sleep(10);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName()+"...sale..."+num--);}}}}public class TicketDemo {public static void main(String[] args) {Ticket t = new Ticket();//创建4个线程Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();}}

出现上图安全问题的原因在于Thread-0通过了if判断后,在执行到“num--”语句之前,num此时仍等于1。CPU切换到Thread-1、Thread-2、Thread-3之后,这些线程依然可以通过if判断,从而执行“num--”的操作,因而出现了0、-1的情况。

采用同步方式修改后

class Ticket implements Runnable {private int num = 100;Object obj = new Object();public void run() {while (true) {synchronized (obj) {if (num>0) {System.out.println(Thread.currentThread().getName()+"...sale..."+num--);}}}}}

以单例设计模式中的懒汉式(演示加载方式)为例:

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

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

package ThreadDemo;public 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;}}

五、线程间通信

多线程之间可以通过消息通信,已达到相互协作的目的。Java中线程之间的通信是通过Object类中的wait()、notify()、notifyAll()等几种方法实现的。Java中每一个对象内部不仅有一个对象锁之外,还有一个线程等待队列,这个队列用于存放所有等待对象锁的线程。

等待唤醒机制

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

 注意:

1:这些方法都需要定义在同步中。

2:因为这些方法必须要标示所属的锁。

    你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

    因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

 

wait和sleep区别:分析这两个方法:从执行权和锁上来分析:

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

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但不是不释放锁。


六、同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

 

七、线程Thread 中常用方法

interrupt():中断线程。

setPriority(int newPriority):更改线程的优先级。

getPriority():返回线程的优先级。

toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

join:临时加入一个线程的时候可以使用join方法。

当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

 

八、Lock接口

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

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

lock(): 获取锁。
unlock(): 释放锁, 为了防止异常出现, 导致锁无法被关闭, 所以锁的 关 闭动作要放在finally中。

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

Condition接口中的await方法对应于Object中的wait方法。
Condition接口中的signal方法对应于 Object中的notify方法。
Condition接口中的signalAll方法对应于Object中的notifyAll方法





0 0