一、

1-01

多线程

进程:是一个正在执行中的程序。

每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。

线程:就是进程中的一个独立的控制单元;线程在控制中进程的执行。

一个进程中至少有一个线程。

Java 的jvm启动的时候会有一个进程java.exe 。该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在与main方法中。该线程称之为主线程

扩展:其实更细节说明jvm,jvm启动时至少有两个线程可以分析的出来,一个是执行main函数的线程,该线程的代码都定义在main函数中,另一个是负责垃圾回收的线程。

垃圾回收机制Object:finalize();

运行垃圾回收器System:static gc():

1-02、03

如何在自定义代码中,自定义线程?

通过对API的查找,Java已经提供了对线程这类事物的描述,就是Thread类。

创建线程的第一种方式:继承Thread类

建立子类对象的同时线程也被创建。

步骤:1.定义类继承Thread;

2.复写父类中的方法;目的:将自定义代码存储在run方法中,让线程运行。

3.调用线程的start方法,该方法有两个作用:启动线程,调用run方法。

为什么要覆盖run方法:Thread类用于描述线程,该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。

也就是说Thread类中的run方法,用于存储线程要运行的代码

class Demo extends Thread
{
      public void run() //覆盖父类run方法
      {
           for( x=0 , x<60, x++)
                 System.out.println(“DemoRun--”+x);
      }
}

主函数:

Demo d = new Demo ; //建立一个对象就是创建一个线程。创建的时候就完成了名称的定义。
      d.start() ; //运行结果:Demo Run 和Hello World两个同时随机打印,且每次打                 印结果基本都不一样
      //调用start方法。开启线程并执行线程的run方法。主线程和自定义线程都执行
      d.run(); //运行结果:先打印Demo Run,再打印Hello World。
      //仅仅是对象调用方法,而线程创建了但并没有运行。属于单线程程序,有主线程执       行,执行完run再执行下面的语句。
      for( x=0 , x<60 ,x++) //主线程
           System.out.println(“HelloWorld--------”+x);

发现运行结果每一次都不同。

因为多个线程获取cpu的执行权,cpu执行到谁,谁就运行。

明确一点:在某一时刻,只能有一个程序在运行。(多核除外)

1-05

线程的几种常见状态:

临时状态(阻塞)(具备运行资格,但没有执行权)<-------|
                /|\                                    |------------/|\
                 |                                      |
       start()   \|/  sleep(time)(时间到就继续运行)    |
被创建---------->运行--------------------------------------------->冻结(放弃了执行资格)
                 |    wait()(通过motify()使其继续运行)  |
                 |   stop();                                |
                \|/ run方法结束                         \|/                
                消亡<----------------------------------------------

1-06

线程都有自己默认的名称:Thread-编号(编号从0开始)

static Thread currentThread() :获取当前线程对象。
getName() :获取线程名称
设置线程名称:setName或构造函数。

创建线程的另一个方法:声明实现Runnable接口的类,该类然后实现run方法。在创建Thread时作为一个参数来传递并启动。

步骤:

1.   定义类实现Runnable接口。

2.   覆盖Runnable接口中的run方法。(将线程要运行的代码存放在run方法中)

3.   通过Thread类建立线程对象。

4.   将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

      为什么要将Runnable接口的子类传递给Thread的构造函数?

      因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指       定对象的run方法,就必须明确该run方法所属的对象。

5.   调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

实现方式和继承方式有什么区别?(面试常考)

1.    实现方式相比继承方式的好处:

避免了单继承的局限性(单继承只能继承一个父类)。在定义线程是,建议使用实现方式。

2.存放代码的位置不一样:

继承Thread:线程代码存放Thread子类的run方法中

实现Runnable,线程代码存在接口的子类的run方法。

实现Runnable接口的好处:

1,将线程的任务从线程的子类中分离出来,进行了单独的封装。

      按照面向对象的思想将任务的封装成对象。

2,避免了java单继承的局限性。

1-09、10

多线程一定要小心安全问题。

买票程序中,多线程的运行出现了安全问题

问题的原因:

      当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没      有执行完,另一个线程参与进来执行,导致共享数据的错误

解决办法:

      对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可      以参与执行。

Java对于多线程的安全问题提供了专业的解决方式:就是同步代码块

synchronized(对象)
{
      需要被同步的代码;
}

哪些代码需要同步:看哪些语句在操作共享数据

对象如同锁,持有锁的线程可以在同步中执行,

没有持有锁的线程即使获取cpu的执行权也进不去,因为没有获取锁。

同步的前提:(加了同步还不安全则考虑是否满足这两个前提)

1.必须要有两个或两个以上的线程。

2.必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

同步的好处:解决了多线程的安全问题。

同步的弊端:多个线程需要判断,较为消耗资源。

锁又名监视器。

1-11  BackDemo

需求:银行有一个金库,有两个储户分别存300元,每次存100.存3次。

函数和同步代码块都具有封装代码功能,不一样的是同步代码块具有同步的特性。

如何使函数具有同步功能:

将synchronized关键字作为修饰符放在函数上。

public synchronized void add(int n)

1-12  TicketDemo1

同步的两种表现形式:同步代码块,同步函数。

同步函数用的是哪一个锁:函数需要被对象调用,那么该函数都有一个所属对象引用,就是this,所以同步函数使用的锁是this(对象)

同步代码块括号中是哪个对象,使用的就是哪个锁。

1-13  StaticTicketDemo

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现使用的不再是this,因为静态方法中也不可以定义this。

类进内存先封装class文件类型的对象(字节码对象)

静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象:类名.class

该对象的类型是Class。

静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class

1-14

单例设计模式:饿汉式,懒汉式

饿汉式:懒汉式的特点在于实例的延时加载,如果多线程访问时可能出现安全问题,解决方式是加同步来解决,加同步的方式用同步代码块和同步函数都行,但是有点低效,用双重判断的形式,可以解决效率问题。

加同步的时候使用的锁是哪一个?

该类所属的字节码文件:类名.class .

要求会写延时加载的单例设计模式示例。(懒汉式)

1-15  DeadLockDemo

同步会产生的弊端:死锁

死锁:同步中嵌套同步,而锁却不同。

写一个死锁程序:DeadLockTest

二、

2-01、02  InputOutputDemo.java

线程间通信:

其实就是多个线程在操作同一个资源,但是操作的动作不一样。

2-03、04(改良代码)InputOutputDemo2

等待唤醒机制:

线程运行的时候,内存中会建立一个线程池,等待线程存在线程池中,notify唤醒线程池中的线程,一般唤醒第一个等待的线程。

wait() ,notify(), notifyAll() 都使用在同步里的,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才有锁。

它们在使用的时候需要标记:锁.wait() , 锁.notify() ,锁.notifyAll()。例:r.wait();

为什么这些操作线程的方法要定义在Object类中?

因为这些方法在操作同步线程时,都必须要标识它们锁操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,也就是说,等待和唤醒必须是同一个锁。

而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

2-05、06  ProducerComsumerDemo

生产者消费者示例:

当出现多个生产者消费者时,要写while循环,用notifyAll( ) 。

全部等待也是死锁。

对于多个生产者和消费者:

为什么要定义while判断标记:为了让被唤醒的线程在一次判断标记。

为什么要定义notifyAll :因为需要唤醒对方线程,只用notify,容易出现只唤醒本放线程的情况,导致程序中所有的线程都等待。

JDK1.5中提供了多线程升级解决方案,将同步synchronized替换成实现Lock操作,将Object中的wait,notify,notifyAll,替换成了Condition对象,该对象可以通过Lock锁进行获取。

Lock a = newReentrantLock() ; //多态,建立一个Lock对象。
ReentrantLock()是Lock实现的类。
Condition c =lock.newCondition() ; //建立了一个Condition对象。
lock.newCondition() ;-----> lock的一个方法,可以获取到Condition的对象。
c.await() ; //调用Condition的等待方法。抛出异常
c.signal() ; //调用Condition的唤醒方法。
c.signalAll() :唤醒全部等待线程。

升级方案可以实现只唤醒对方的操作。

同步中,一个锁只能对应一个wait和notify。

生产者消费者有什么替代方案?

1.5版本以后,提供了显示的锁机制,显示的锁对象上的等待唤醒操作机制,同时将等待唤醒机制进行封装,一个锁可以对应多个Condition

wait和sleep的区别:

 1.wait 可以指定时间也可以不指定。sleep必须指定时间。

2.在同步中,对CPU的执行权和锁的处理不同:

wait:释放执行权,释放锁

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

2-07   StopThreadDemo.java

停止线程:原理,run方法结束

1.    定义循环结束标记。因为线程运行代码一般都是循环,只要控制了循环即可。

2.    使用intrrupt(中断)方法。该方法是结束线程的冻结状态,是线程回到运行状态中来。

注:stop方法已经过时不再使用

特殊情况:当线程处于冻结状态,就不会结束读取到标记,那么线程就不会结束。

当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类中提供了该方法interrupt() ;

2-08

setDeamon();

守护线程:setDaemon(ture) ;(后台线程,当前台线程执行是后台线程也在执行,但是但前台线程全部执行完关闭时,后台线程也会跟着自动关闭,jvm退出。)

例:t1.setDeamon(ture);

2-09

join()等待该线程终止:一般用于临时加入线程。

抛出: InterruptedException - 如果另一个线程中断了当前线程。当抛出该异常时,当前线程的中断状态被清除

例:t1.join() ; :请求获取执行权。主线程处于冻结状态,直到t1运行结束。

join:当A线程执行到了B线程的.join()方法时,A就会等待,等B线程都执行完,A才会执行

2-10

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

例:s.o.p( Thread.currentThread().toString( ) )

Thread-1 ,  5  ,  main]

线程组:谁开启的线程就属于哪个线程组。

优先级:一般优先级都是5。优先级有10级。1-10,越大优先级越高。

yield()方法:释放执行权,让其他线程运行。临时释放。例Thread.yield();

一般写程序怎么写线程:

方法1:

new Thread() //Thread的子类。匿名内部类
{
      public void run()
      {
           //需要线程运行的程序
      }
}.start() ; //Thread可以直接调用start方法。

方法2 :

Runnable r = newRunnable() //不能直接调用start。匿名内部类。
{
      public void run()
      {
           //需要线程运行的程序
      }
};
new Thread(r).start();

面试题:

1.这个类有没有错误,如果错了,错误出在哪一行?

calss Test implementsRunnable
{  public void run(Thread t ) { }   }

答:有错误,错误出在第一行。一个类实现的了Runnable,值定义了与run方法重载的方法,没有覆盖run方法,则这个类应是抽象类。应该被abstract修饰。

3.    会运行哪一个?

new Thread(newRunnable()
{
      public voidrun()
      {  sop(“runnable run”) ;  }
})
{
      public voidrun()
      {  sop(“subthread run”) ;  }
}.start();

答:会运行Thr ead子类:subthread run

      如果没有子类,会运行任务:runnablerun

      如果也没有任务,则会运行Thread类。