进程、线程(synchronized、wait、notify)

来源:互联网 发布:旅行商问题 贪心算法 编辑:程序博客网 时间:2024/05/19 04:52

为了帮助更好的理解进程和线程,举个例子:

电脑同时运行word和pdf,是指的2个进程,在word打印过程中,还可以进程编辑,是指的2个线程。


一、线程基本知识点

1、程序是静态的,进程是动态的。进程本质上是一个执行的程序。

2、一个进程可以包含多个线程,但至少包括一个线程;没有进行其他线程的定义时,进程中只有一个单线程(主线程),main方法就在这个主线程上运行。我们的程序都是通过线程来执行的。

3、CPU随机的抽取时间让程序一会做这件事,一会做那件事(同时有多少线程在运行取决于电脑的CPU,如果CPU是双核的,则最多同时有2个线程在运行)。线程一旦运行起来就无法控制了。由于多线程设计的目的是最大限度的使用CPU的资源,使得空闲时间保持最低;因此多线程对应的代码不是顺序执行的,当有线程需要进行IO读取时,CPU将把空闲时间分给其他线程利用。

4、java中内置了对多线程的支持,线程的实现有两种

(1)继承Thread类,实现run()方法

(2)实现Runnable接口,实现run()方法

在实现过程中,将代码写到run方法中,然后通过调用start方法来启动线程(start方法首先为线程准备好系统资源,然后再调用重写的run方法)。

PS:

如果不通过start方法启动线程,而调用run方法,则不会启动线程,而是运行了一个简单的类(虽然此类在形式上继承了Thread类或实现了Runnable接口)。换句话说,要想启动线程,只有通过start一种方式。

当一个类已经继承了一个父类的时候,这时候只能通过实现Runnable接口来实现多线程(java中是单继承的)。

线程的消亡不能通过stop()方法,而只能通过run()方法的自行结束(自己控制run方法),例如:


5、Runnable接口中只有一个run()方法,Thread类实现了Runnable接口,因此也实现了run()方法:

public class Thread extends Object implements Runnable

6、根据Thread构造方法的源代码分析可知:生成一个线程对象时,若没对其设定名字,则默认为“Thread-number”,该number(静态类型)是自动从0递增的,并被所有Thread对象所共享。若要自定义Thread的名字,则可在构造方法中重新定义。


线程生命周期转换图



线程生命周期:

创建状态:创建一个线程后,其实是创建了一个空的线程对象,系统并没有为其分配资源

可运行状态:调用了start()方法,为线程分配了资源,并安排其运行

不可运行状态:sleep(),IO操作,wait()

消亡状态

(1)新创建一个线程后,调用start方法,系统会为此线程分配CPU资源,处于Runnable(可运行状态);线程抢占到CPU,此时线程处于Running(运行状态);

(2)Runnable状态和Running状态可相互切换,因为有可能线程运行一段时间后其他高优先级的线程抢占了CPU,这样此线程就从Running状态变成Runnable状态;

(3)Bloched是阻塞的意思,例如如果遇到了一个IO操作,此时CPU处于空闲状态,可能会转而把内存分配给其他线程,Blocked状态结束后,进入Runnable状态,等待系统重新分配资源;

(4)run()方法运行结束后进入消亡阶段,整个线程执行完毕。


8、线程优先级为1~10的整数,10的优先级最高。通过setPriority()进行修改,若一个线程很久没执行了,系统会自动提升优先级级别,使其逐渐有机会被执行。在程序中,应该通过代码设置一个线程的执行过程,不应该依赖于线程的优先级,因为这样的化,有可能一个线程永远没机会执行。


二、线程同步

例如,两个线程(统一个线程对象对应的不同的线程):

Runnable r = new MyThread();//一个Runnable对象

Thread t1 = new Thread(r);//同一个线程对象对应的线程1

Thread t2 = new Thread(r);//同一个线程对象对应的线程2

t1.start();

t2.start();

此时,线程t1和t2对成员变量的操作是相互影响的,对于局部变量的操作是独立的。

1、关于成员变量和局部变量:如果一个变量是成员变量,则多个线程对同一个对象的成员变量进行操作时,对该成员变量是彼此影响的,即一个线程对变量的改变会影响另一个线程。

2、如果一个变量是局部变量,每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到另一个线程对该局部变量的改变。


若两个线程定义如下(不同的线程对象对应的不同的线程)

Runnable r1 = new MyThread();//一个Runnable对象

Runnable r2 = new MyThread();//另一个Runnable对象

Thread t1 = new Thread(r1);//不同线程对象对应的线程

Thread t2 = new Thread(r2);//不同线程对象对应的线程

t1.start();

t2.start();

此时,线程t1和t2对成员变量和局部变量的操作是互不影响的,因为是两个线程,对应的两个run()方法。

以上两个例子引出了线程同步问题,同一个线程对象所对应的多个线程在对同一个成员变量进行操作时,会相互影响,因此需要加入同步机制,避免两线程之间对彼此的影响。


同步方法:

用Synchronized关键字修饰方法,该方法叫同步方法;当同一个线程对象的不同线程对此方法访问时,就依照同步机制执行。

同步机制:

1、java中的每个对象都有一把锁,也叫监视器(monitor),当访问对象的Synchronized方法时,表示对此对象加锁,此时其他线程则不能再访问此方法,除非当前线程执行完毕(或抛出异常),就会将该对象的锁解锁,则其他线程才有可能继续访问此方法,当然也需要进行加锁处理。

如果一个对象有多个Synchronized方法,某一时刻某个线程已经进入到某个Synchronized方法,难么在该线程执行完毕前,其他线程也无法访问其他的Synchronized方法,因2、为要访问此方法,必须对对象进行上锁,但此时对象已经被当前线程上锁,除非等当前线程执行完毕后,其他线程才能取得访问Synchronized方法的机会。

以上两点考虑相对于同一个线程对象对应的不同线程,如果不同线程对象对应的不同线程执行时,由于上锁的对象不同,所以对不同对象的Synchronized方法的访问不受影响,因此输出结果不受控制。


总结:

对于同一个线程对象生成的多个线程:

(1)如果run()方法所要执行的方法有Synchronized声明,则说明线程在running时要对对象上锁,此时其他线程无法访问对线程象;

(2)如果run()方法所要执行的方法没有Synchronized声明,则看多个线程所要操作的变量是成员变量还是局部变量:

  • 如果是成员变量,多个线程公用一份,则说明多个线程对变量的操作相互影响,输出结果为1份
  • 如果是局部变量,每个线程都有一份,则说明多个线程之间的操作彼此不受影响,输出结果为多份,且顺序不可控

对于不同线程对象生成的多个线程:

1、有Synchronized声明:

(1)如果run()方法所要执行的方法有static声明:此时所在的类对应的Class对象只有一个,也就是说这种情况下,无论线程对象有多少个,都等同于一个线程对象,那么各个线程的执行要在前一个线程解锁后才能进行。

(2)如果run()方法所要执行的方法没有static声明:多个线程之间的操作彼此不受影响,输出结果为多份,且顺序不可控

2、没有Synchronized声明:多个线程之间的操作彼此不受影响,输出结果为多份,且顺序不可控


综上,关键是看要上锁的对象是不是同一个线程对象,如果是,则会按照顺序,一个线程执行完毕解锁后,另一个线程再执行;如果上锁的对象不是同一个线程对象,则两线程之间的执行互相不影响,执行结果是穿插的,不规律的;如果某个synchronized方法是static的,那么当线程访问此方法时,它的锁不是synchronized方法所在的对象,而是synchronized方法对应的对象的Class对象,因为java中无论一个类有多少个对象,这些对象唯一的对应一个Class对象,因此线程分别访问同一个类中两个对象的两个static、synchronized方法时,会产生这样的结果:一个线程执行完解锁后,另一个线程再执行。

但是,如果一个synchronized方法是static的,另一个不是static的,则线程分别访问同一个类中两个对象的synchronized方法时,会产生这样的结果:两个线程各执行各的,结果是穿插的,不规律的(因为两个线程上锁的对象不同:一个是synchronized方法所在的对象,另一个是synchronized方法对应的对象的Class对象)。


除了在方法声明处加入synchronized关键字实现线程同步外,也可以在方法中加入synchronized代码快来对特定的某几行代码实现同步;synchronized方法是一种粗粒度的并发控制,只能有一个线程执行该方法,synchronized代码块是一种细粒度的并发控制,只会将代码块中的代码同步,位于方法内、synchronized代码块外的代码是可以被多个线程同时访问到的。

synchronized代码块的结构如下:

Object object = new Object();
    public void printNumber1()
    {        
        synchronized(object)
        {
            ......
        }
    }       

 public void printNumber1()
    {        
        synchronized(this)
        {
            ......
        }
    } 


同步的线程状态图


上半部分已经分析过了,下半部分分析如下:

线程处于Running状态后,需要调用synchronized方法,若此时此方法正在被其他线程调用,则此线程就会被放入锁池,等到其他线程解锁后,进入Runnable状态,若此时分配到CPU资源,则会Running。


三、死锁deadlock(哲学家就餐问题、生产消费者问题)——求职问题

1、wait()和notify()方法都是定义在Object类中的,而且是final类型的,因此会被所有的java类继承,无法重写。这两个方法在使用时要求线程已经得到了对象的锁,所以必须写在synchronized方法中。当一个线程调用了wait()方法时,会释放掉锁;等待另一个线程调用notify()方法才有可能把这个线程唤醒(notify()方法唤醒的线程是不确定的);线程被唤醒后,继续执行wait()方法下面的语句。

2、另一个导致线程暂停的方法是Thread类中的sleep方法,它定义了线程睡眠的毫秒数。

wailt()方法和sleep()方法的区别:

(1)线程调用sleep()方法会进入睡眠,但是不会释放掉锁,因此等醒来后,线程会继续执行。

(2)wait()方法自己不会醒来,只有另一线程调用了notify()方法后,才有可能被唤醒;sleep()方法会自动醒来。


线程完整的状态图


当线程调用wait方法后,会进入等待池,被其他线程的notify方法唤醒后,进入锁池,等待对象的锁。




0 0
原创粉丝点击