------- <a href="http://www.itheima.com" target="blank">android培训</a>、<a href="http://www.itheima.com" target="blank">java培训</a>、期待与您交流! ----------
一:概念讲解
现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。
1、进程
正在进行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
一个进程中至少有一个线程。
2、线程
线程是进程中的内容,就是进程中的一个独立的控制单元。线程在控制着进程的执行。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
3、多线程
在Java中,并发机制非常重要,但并不是所有的程序语言都支持线程。在以往的的程序中,多以一个任务完成后再进行下一个项目的模式进行开发,这样下一个任务的开始必须等待前一个任务的结束。Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。
扩展:其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。
4、多线程存在的意义: 程序运行中至少有两个线程在运行,一个是主函数的主线程,另一个是垃圾回收的线程。
二:创建线程的方式
1、继承thread类
通过对API的查找,java已经提供了对线程这类事物的描述即为thread类。所以创建线程的第一种方式为继承thread类
打印结果为:
运行的结果每一次都不同,因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行,明确一点,在某个时刻,只能有一个程序在运行。(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果,我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
cpu每次只执行一个程序,只是在快速的不同线程间切换,表现了多线程的随机性
创建的步骤:
(1)定义类继承thread
(2)复写thread类中的run方法(目的:定义线程要运行的自定义代码<将自定义代码存储在run方法,让线程运行>)
(3)调用该线程的start方法。该方法有两个作用:
作用:1.启动线程 2.运行run方法。目的是将自定义的代码存储在run方法中,让线程运行
class demo extends Thread{ public void run(){ }}run方法用于存储线程要运行的代码。demo demo=new demo();创建对象就创建了一个线程。run方法和 start方法run方法 仅仅是对象调用方法,并没有运行线程start方法 是开启线程并且执行线程中的run方法
2、创建线程的第二种方式:实现Runnable接口。
创建线程 Thread t=new Thread(new 对象名());步骤:1,定义类实现Runnable接口。2,覆盖接口中的run方法(用于封装线程要运行的代码)。3,通过Thread类创建线程对象;4,将实现了Runnable接口的子类对象作为实际参数传递给Thread类中的构造函数。 为什么要传递呢?因为要让线程对象明确要运行的run方法所属的对象。?5,调用Thread对象的start方法。开启线程,并运行Runnable接口子类中的
run方法。
实现Ticket?t?=?new?Ticket();?/*?直接创建Ticket对象,并不是创建线程对象。?因为创建对象只能通过new?Thread类,或者new?Thread类的子类才可以。?所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。?*/?Thread?t1?=?new?Thread(t);?//创建线程。?/*?只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联?为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。?*/?实现方式和继承方式有什么区别?继承Thread类:线程代码块存放在Thread子类的run方法中实现Runnable,线程代码存放在接口的子类的run方法中,可以被多实现。继承方式有局限性。要被实现多线程的一个类 如果继承了父类 就不能再继承Thread类。实现方式就变面了单继承的局限性。
三:线程的运行状态
新建:start()运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()唤醒; 线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop() run方法结束
当从冻结状态唤醒的先返回临时状态(阻塞状态),先获得执行资格;或者直接返回运行状态。
四:获取线程名称和对象
1、线程都有自己默认的名称:2、获取线程名称的方法。
Thread.currentThread().getName()currentThread() 获取当前线程对象getName() 获取线程名称
setName 或 构造函数 设置线程名称
五:多线程的安全问题
1、线程安全问题产生的原因
(1)当多条语句在操作同一个共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
2、线程安全问题的解决方案
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
方式:同步代码块:synchronized(对象)
{ 需要被同步的代码。(共享数据)}
同步的前提:1.必须要有两个或者两个以上的线程2.必须多个线程必须使用同一个锁。 必须保证同步中只能有一个线程在运行。
对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程,即使获取cpu的执行权,也进不去,因为没有获取锁。
解决线程安全的利与弊:
好处:解决了线程的安全问题弊端:消耗了资源,多个线程需要判断锁。
实例讲解:
打印结果如下:
3、同步函数:public synchronized void show(){ }如何找问题?1.明确哪些代码是多线程运行代码2.明确共享数据3.明确多线程运行代码中哪些语句是操作共享数据的?同步函数的锁是 this。函数需要被对象调用,那么函数都有一个所属对象引用。想让线程停一下 Thread.sleep(10);如果同步函数被静态修饰后,使用的锁是 classsynchronized (对象名.class)
静态的同步函数使用的锁是该函数所属字节码文件对象, 可以用getClass方法获取, 也可以用当前类名.class表示。
4、单里设计模式:
饿汉式:
class Single{
private static final Single s = new Single( ) ;
private Single( ) { }
public static Single getInstance( ) {
return s ;
}
}
P.S.
饿汉式不存在安全问题, 因为不存在多个线程共同操作数据的情况。
懒汉式:懒汉式的特点在于延迟加载,懒汉式也存在问题,解决方式采用加同步, 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 ;
}
}
P.S.
懒汉式存在安全问题, 可以使用同步函数解决。
但若直接使用同步函数, 则效率较低, 因为每次都需要判断。
但若采取如下方式, 即可提升效率。
原因在于任何一个线程在执行到第一个if判断语句时, 如果Single对象已经创建, 则直接获取即可, 而不用判
断是否能够获取锁, 相对于上面使用同步函数的方法就提升了效率。 如果当前线程发现Single对象尚未创建, 则
再判断是否能够获取锁。
1. 如果能够获取锁, 那么就通过第二个if判断语句判断是否需要创建Single对象。 因为可能当此线程获取到锁
之前, 已经有一个线程创建完Single对象, 并且放弃了锁。 此时它便没有必要再去创建, 可以直接跳出同步代码
块, 放弃锁, 获取Single对象即可。 如果有必要, 则再创建。
5、死锁
同步中嵌套同步,可能会发生,该怎么解决是由于
两个线程相互等待 对方已被锁定的资源循环等待条件:第一个线程等待其它线程,后者又在等待第一个线程。示例:
class Ticket implements Runnable{private static int num = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag ){
while(true ) {
synchronized(obj ){
show() ;
}
}
} else
while(true )
show();
}
public synchronized void show() {
synchronized(obj ) {
if(num > 0){
try{
Thread. sleep(10);
} catch(InterruptedException e){
e. printStackTrace();
}
System. out. println(Thread. currentThread(). getName() +
" . . . function. . . " + num--) ;
}
}
}
}
class DeadLockDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1. start();
try{
Thread. sleep(10);
} catch(InterruptedException e){
e. printStackTrace();
}
t. flag = false ;
t2. start();
}
}
六:线程间通信问题
思考1:wait();notify();notifyAll();用来操作线程为什么定义在了Object类中。
1、这些方法存于同步中。
2、使用这些方法是必须要标识所属的同步的锁。
3、锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
思考2:wait();sleep()有什么区别?
wait();释放资源,释放锁;
sleep();释放资源,不释放锁。
线程间通信:多个线程在操作同一个资源,但是操作的动作不同。
1.是不是两个或两个以上的线程。解决办法 两个线程都要被同步。2.是不是同一个锁。解决办法 找同一个对象作为锁。
打印结果如下:
七:等待唤醒机制。
wait后,线程就会存在线程池中,notify后就会将线程池中的线程唤醒。notifyAll();唤醒线程池中所有的线程。实现方法 :给资源加个标记 flag synchronized(r){ while(r.flag)//多个生产者和消费者 if(r.flag)//一个生产者和消费者 r.wait(); 代码 r.flag=true; r.notify(); r.notifyAll();}
上面三种方法都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。为什么这些操作线程的方法要定义在object类中呢?因为这些方法在操作同步中线程的是偶,都必须要表示它们所操作线程只有的锁。只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁,而锁可以是特意对象,可以被任意对象调用的方法定义在Object类中。
八:停止线程:run方法结束,就会停止线程,开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让线程结束。方法:改变标记。特殊情况,改变标记也不会停止的情况。将处于冻结状态的线程恢复到运行状态。interrupt(); 中断线程。
九:守护线程:SetDaemon将线程标记为守护线程或用户线程。在启动线程前调用 。当线程都为守护线程后,JVM退出。十:JOin方法:t.join();抢过cpu执行权。当A线程执行到了B线程的join方法时,A就会等待,等B线程执行完,A才会执行。Join可以用来临时加入线程执行。优先级:SetPriority(1-10)设置优先级。Thread.MAX_PRIORITY 10Thread.MIN_PRIORITY 1Thread.NORM_PRIORITY 5yield方法:暂停当前正在执行的线程对象,并执行其他线程。开发中应用::保证以下三个代码同时运行。案例
new Thread(){ for(int x=0;x<100;x++)
{ sop(Thread.currentThread().getName())
}}.start(); for(int x=0;x<100;x++){ sop(Thread.currentThread().getName())}
Runnable r=new Runnable(){ public voud run()
{ for(int x=0;x<100;x++)
{ sop(Thread.currentThread().getName()) }
}};new Thread(r).start();