一、
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类。