Java线程相关知识

来源:互联网 发布:网络视频营销 编辑:程序博客网 时间:2024/05/21 09:53
1. 线程和进程的概念: 
   进程:是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
   线程:是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程;
2、进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
(4)处理机分给线程,即真正在处理机上运行的是线程。
(5)线程是指进程内的一个执行单元,也是进程内的可调度实体。
3、线程与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
(4)系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。
(5)线程的划分尺度小于进程,使得多线程程序的并发性高。
(6)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大的提高了程序运行效率。
4、 创建线程的第一种方法: 继承Thread类
    步骤:1. 定义类继承Thread;
2. 重写Thread类中的run方法。目的:将自定义的代码存储在run方法中,让线程运行。
3. 调用线程的start方法。该方法两个作用:启动线程,调用run方法。
代码如下:
public class Test{      public static void main(String[] args) {        MyThread t = new MyThread();        t.start();//开启线程,并且执行该线程的run方法        // t.run();//仅仅是对象调用方法,而线程创建了,并没有运行。        for(int i=0;i<123;i++){            System.out.println("main"+i);        } 
    }}class MyThread extends Thread{    public void run(){        for(int i=0;i<30;i++){            System.out.println("Mythread>>"+i);        }       }}
(1)当运行时:会发现运行结果每次都不同。  
因为多个线程都获取cpu 的执行权。 cpu执行到谁,谁就运行;明确一点,在某个时刻,只能有一个程序在运行(多核除外);cup在做着快速的切换,以达到看上去是同时运行的效果。
(2)为什么要覆盖run方法。
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储方法就是run方法,也就是说Thread类中的run方法用于存储线程要运行的代码。
5、创建线程的第二种方式: 实现Runnable接口
 步骤: 1. 定义实现Runnable接口。
2. 重写Runnable接口中的run方法。将线程要运行的代码存放在该run方法中。
3. 通过Thread类建立线程对象。
4. 将Runnable接口的子类 对象作为实际参数传递给Thread类的构造函数。
5. 调用Thread类的start方法开启线程并且调用Runnable接口子类的run方法。
代码如下:
public class Demo3_runable {    public static void main(String[] args) {        MyRunnable r = new MyRunnable();        Thread t = new Thread(r);        t.start();        for (int i = 0; i < 30; i++) {            System.out.println("Main>>>"+i);        }    }}class MyRunnable implements Runnable {    @Override    public void run() {        for (int i = 0; i < 50; i++) {            System.out.println("runable>>>>>>>>>" +i);        }    }}
(1)为什么要将Runnable接口的子类对象传递给Thread的构造函数?
  因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
(2)实现方式和继承方式的区别:
实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用;线程代码存放在接口的子类的run方法中。线程代码存放在Thread子类run方法中
6、线程常用方法
1.start():线程调用该方法将启动线程,使之从新建状态进入就绪队列排队,一旦轮到它来享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期了。
2.run(): Thread类的run()方法与Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度之后所执行的操作,都是系统自动调用而用户程序不得引用的方法。
3.sleep(int millsecond): 优先级高的线程可以在它的run()方法中调用sleep方法来使自己放弃CPU资源,休眠一段时间。
4.isAlive(): 线程处于“新建”状态时,线程调用isAlive()方法返回false。在线程的run()方法结束之前,即没有进入死亡状态之前,线程调用isAlive()方法返回true.
5.currentThread():该方法是Thread类中的类方法,可以用类名调用,该方法返回当前正在使用CPU资源的线程
6.interrupt() :一个占有CPU资源的线程可以让休眠的线程调用interrupt()方法“吵醒”自己,即导致休眠的线程发生InterruptedException异常,从而结束休眠,重新排队等待CPU资源。
7 .join():使得一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后,才可以继续运行。
8 . getName():在Thread类中可以通过getName()方法取得线程名称,通过setName()设置线程名称。(线程的名称一般在启动线程前设置,但也允许为运行的线程设置名称,允许两个Thread对象有相同名称,但是应该避免。如果程序没有为线程指定名称,系统会自动为线程设置名称。
9 . yield():yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
实际上,当某个线程调用了yield()方法之后,只有优先级与当前线程相同或者比当前线程更高的处于就绪状态的线程才会获得执行机会。
10 . wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
11 . notify():唤醒一个处于等待状态的线程,注意:在调用此方法的时,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
12 . notityAll():唤醒所有处入等待状态的线程,注意:不是给所有唤醒线程一个对象的锁,而是让它们竞争
注意:wait()和notify(),notifyAll()是Object类的方法,sleep()和yield()是Thread类的方法。
7、锁
(1)一般java中的锁指的是内置锁,每个java对象都可以作为一个实现同步的锁,虽然说在java中一切皆对象,但是锁必须是引用类型的,基本数据类型则不可以 。每一个引用类型的对象都可以隐式的扮演一个用于同步的锁的角色,执行线程进入synchronized块之前会自动获得锁,无论是通过正常语句退出还是执行过程中抛出了异常,线程都会在放弃对synchronized块的控制时自动释放锁。获得锁的唯一途径就是进入这个内部锁保护的同步块或方法 。
(2)对共享资源的访问必须是顺序的,也就是说当多个线程对共享资源访问的时候,只能有一个线程可以获得该共享资源的锁,当线程A尝试获取线程B的锁时,线程A必须等待或者阻塞,直到线程B释放该锁为止,否则线程A将一直等待下去,因此java内置锁也称作互斥锁,也即是说锁实际上是一种互斥机制。
(3)根据使用方式的不同一般我们会将锁分为对象锁类锁,两个锁是有很大差别的,对象锁是作用在实例方法或者一个对象实例上面的,而类锁是作用在静态方法或者Class对象上面的。一个类可以有多个实例对象,因此一个类的对象锁可能会有多个,但是每个类只有一个Class对象,所以类锁只有一个。类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的 。
8、在java中实现锁机制不仅仅限于使用synchronized关键字,还有JDK1.5之后提供的Lock。
(1)synchronized关键字的作用是对一个对象或者是方法(其实还是持有这个方法的对象)加上锁,加锁实质上是取得对象的访问权限,如果对象已经被其他者所加锁,那么请求者就只好进入等待队列(休眠),知道被其他线程使用nodify方法唤醒。在被加锁的代码块当中,可能会出现一些情况,使得代码需要一些条件才能继续运行,此时为了让这个代码块能够被其他的线程执行到,必须释放锁,nodify方法除了唤醒其他的线程,并把锁交给它之外还有释放锁+让自己休眠的作用。
获取锁的线程释放锁会有三种情况:(1).获取锁的线程执行完该代码块,然后线程释放对锁的占有;
(2).线程执行发生异常,此时JVM会让线程自动释放锁;
(3).调用wait方法,在等待的时候立即释放锁,方便其他的线程使用锁.
(2)Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。
注意:用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。