黑马程序员-----JAVA多线程

来源:互联网 发布:九维外呼软件 编辑:程序博客网 时间:2024/06/05 11:20

                ---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------

线程简述:

进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。

线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。

 一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。

多线程的好处:解决多部分同时运行的问题。

多线程的弊端:线程太多回到效率低

jvm在启动的时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。

当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,会去运行垃圾回收器代码,效率较低,所以由单独一个线程来负责垃圾回收。

 随机性的原理:因为cpu的快速切换造成,哪个线程获取到了cpu的执行权,哪个线程就执行。

返回当前线程的名称:Thread.currentThread().getName()

线程的名称是由:Thread-编号定义的。编号从0开始。

线程要运行的代码都统一存放在了run方法中。

线程要运行必须要通过类中指定的方法开启。start方法。(启动后,就多了一条执行路径)

start方法:1)、启动了线程;2)、让jvm调用了run方法。

创建线程的第一种方式:继承Thread ,由子类复写run方法。

步骤:

1,定义类继承Thread类;

2,目的是复写run方法,将要让线程运行的代码都存储到run方法中;

3,通过创建Thread类的子类对象,创建线程对象;

4,调用线程的start方法,开启线程,并执行run方法。

线程状态:

被创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify()唤醒;线程释放了执行权,同时释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()


创建线程的第二种方式:实现一个接口Runnable。


创建线程的第一种方式:继承 Thread Thread Thread 类。
创建线程的第二种方式:实现 Runnable接口。

步骤: 
1. 定义类实现 定义类实现 定义类实现 Runnable  接口;
2.覆盖接口中的 run 方法,将线程的任务代码封装到 run方法中;
3.通过 Thread类创建线程类创建线程对象,并将 Runnable接口的子类对象作为  Thread 类的构造函数参进行传递;
为什么?因线程的任务都封装在 Runnable接口子类对象的run方法中。
所以要在线程对象创建时就必须明确运行的任务。
4,调用线程对象的 start 方法开启线程。
实现 Runnable接口的好处:
1.将线程的任务从子类中分离出来,进行了单独封装。 
   按照面向对象的思想将任务封装成。 
2.避免了 java单继承的局限性。
   所以,创建线程的第二种方式较为常用。

Ticket t = new Ticket();/* 直接创建Ticket对象,并不是创建线程对象。 因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。 所以最终想要创建线程。既然没有了Thread类的子类,就只能用Thread类。*/ Thread t1 = new Thread(t); //创建线程。/* 只要将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联 为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。*/ t1.start();

class Demo implements Runnable//extends Fu //准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行。//通过接口的形式完成。{public void run(){show();}public void show(){for(int x=0; x<20; x++){System.out.println(Thread.currentThread().getName()+"....."+x);}}}class  ThreadDemo{public static void main(String[] args) {Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t2.start();//Demo d1 = new Demo();//Demo d2 = new Demo();//d1.start();//d2.start();}}
new Thread(new Runnable(){  //匿名public void run(){System.out.println("runnable run");}}{public void run(){System.out.println("subthread run");}}.start();  //结果:subthread run
Try {      Thread.sleep(10);}catch(InterruptedException e){}// 当刻意让线程稍微停一下,模拟cpu切换情况。
多线程安全问题的原因:

1,多个线程在操作共享的数据;
2,操作共享数据的线程代码有多条。
当一个线程在执行操作共享数据的多条代码过中,其他参与了运算。就会导致线程安全问题的产生。 

解决思路:
就是将多条操作共享数据的线程代码封装起来,当有在执行这些时候其他线程时不可以参与运算的。
必须要当前线程把这些代码都执行完毕后,其他才可以参与运算。 
在java 中,用同步代码块就可以解决这个问题。

同步代码块的格式: 同步代码块的格式: 同步代码块的格式: 同步代码块的格式:
 synchronized( 对象 )
{
 需要被同步的代码 ;
}
同步的好处:解决了线程安全问题。
同步的弊端:相对降低了效率,因为外线程都会判断锁。 
同步的前提:中必须有多个线程并使用一锁。

class Ticket implements Runnable//extends Thread{private  int num = 100;Object obj = new Object();public void run(){while(true){synchronized(obj){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){}System.out.println(Thread.currentThread().getName()+".....sale...."+num--);}}}}}class  TicketDemo{public static void main(String[] args) {Ticket t = new Ticket();//创建一个线程任务对象。Thread t1 = new Thread(t);Thread t2 = new Thread(t);Thread t3 = new Thread(t);Thread t4 = new Thread(t);t1.start();t2.start();t3.start();t4.start();}}

同步函数:其实就是将同步关键字定义在函数上,让函数具备了同步性。

 同步函数是用的哪个锁呢?

通过验证,函数都有自己所属的对象this,所以同步函数所使用的锁就是this锁

 当同步函数被static修饰时,这时的同步用的是哪个锁呢?

静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象

所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。

这个对象就是 类名.class

 同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

 在一个类中只有一个同步,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

/*多线程下的单例*///饿汉式class Single{private static final Single s = new Single();private Single(){}public static Single getInstance(){return 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;}}class  SingleDemo{public static void main(String[] args) {System.out.println("Hello World!");}}

同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

 

/*死锁:常见情景之一:同步的嵌套。*/class Ticket implements Runnable{private  int num = 100;Object obj = new Object();boolean flag = true;public void run(){if(flag)while(true){synchronized(obj){show();}}elsewhile(true)this.show();}public synchronized void show(){synchronized(obj){if(num>0){try{Thread.sleep(10);}catch (InterruptedException e){}System.out.println(Thread.currentThread().getName()+".....sale...."+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){}t.flag = false;t2.start();}}

线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。

1:将资源封装成对象。

2:将线程执行的任务(任务其实就是run方法。)也封装成对象。

/*线程间通讯:多个线程在处理同一资源,但是任务却不同。*///资源class Resource{String name;String sex;}//输入class Input implements Runnable{Resource r ;//Object obj = new Object();Input(Resource r){this.r = r;}public void run(){int x = 0;while(true){synchronized(r){if(x==0){r.name = "mike";r.sex = "nan";}else{r.name = "丽丽";r.sex = "女女女女女女";}}x = (x+1)%2;}}}//输出class Output implements Runnable{Resource r;//Object obj = new Object();Output(Resource r){this.r = r;}public void run(){while(true){synchronized(r){System.out.println(r.name+"....."+r.sex);}}}}class  ResourceDemo{public static void main(String[] args) {//创建资源。Resource r = new Resource();//创建任务。Input in = new Input(r);Output out = new Output(r);//创建线程,执行路径。Thread t1 = new Thread(in);Thread t2 = new Thread(out);//开启线程t1.start();t2.start();}}

等待唤醒机制:涉及的方法:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

注意:

1:这些方法都需要定义在同步中。

2:因为这些方法必须要标示所属的锁。

    你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

    因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

/*等待/唤醒机制。 涉及的方法:1,wait(): 让线程处于冻结状态,被wait的线程会被存储到线程池中。2,notify():唤醒线程池中一个线程(任意).3,notifyAll():唤醒线程池中的所有线程。这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上的线程。为什么操作线程的方法wait notify notifyAll定义在了Object类中? 因为这些方法是监视器的方法。监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中。*///资源class Resource{String name;String sex;boolean flag = false;}//输入class Input implements Runnable{Resource r ;//Object obj = new Object();Input(Resource r){this.r = r;}public void run(){int x = 0;while(true){synchronized(r){if(r.flag)try{r.wait();}catch(InterruptedException e){}if(x==0){r.name = "jack";r.sex = "nan";}else{r.name = "lala";r.sex = "nv";}r.flag = true;r.notify();}x = (x+1)%2;}}}//输出class Output implements Runnable{Resource r;//Object obj = new Object();Output(Resource r){this.r = r;}public void run(){while(true){synchronized(r){if(!r.flag)try{r.wait();}catch(InterruptedException e){}System.out.println(r.name+"....."+r.sex);r.flag = false;r.notify();}}}}class  ResourceDemo2{public static void main(String[] args) {//创建资源。Resource r = new Resource();//创建任务。Input in = new Input(r);Output out = new Output(r);//创建线程,执行路径。Thread t1 = new Thread(in);Thread t2 = new Thread(out);//开启线程t1.start();t2.start();}}

 wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。

sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。

wait:线程会释放执行权,而且线程会释放锁。

Sleep:线程会释放执行权,但不是不释放锁。

 

class Demo{void show(){synchronized(this)// { wait();//t0 t1 t2}}void method(){synchronized(this)//t4{//wait();notifyAll();}//t4}}class  {public static void main(String[] args) {System.out.println("Hello World!");}}

线程的停止:通过stop方法就可以停止线程。但是这个方式过时了。

停止线程:原理就是:让线程运行的代码结束,也就是结束run方法。

怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。

第一种方式:定义循环的结束标记。

第二种方式:如果线程处于了冻结状态,是不可能读到标记的,这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。

 ---------< java.lang.Thread>----------

interrupt():中断线程。

setPriority(int newPriority):更改线程的优先级。

getPriority():返回线程的优先级。

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

Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。

setDaemon(true):将该线程标记为守护线程或用户线程。将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。

/*停止线程:1,stop方法。2,run方法结束。怎么控制线程的任务结束呢?任务中都会有循环结构,只要控制住循环就可以结束任务。控制循环通常就用定义标记来完成。但是如果线程处于了冻结状态,无法读取标记。如何结束呢?可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。 当时强制动作会发生了InterruptedException,记得要处理*/class StopThread implements Runnable{private boolean flag = true;public synchronized void run(){while(flag){try{wait();//t0 t1}catch (InterruptedException e){System.out.println(Thread.currentThread().getName()+"....."+e);flag = false;}System.out.println(Thread.currentThread().getName()+"......++++");}}public void setFlag(){flag = false;}}class StopThreadDemo {public static void main(String[] args) {StopThread st = new StopThread();Thread t1 = new Thread(st);Thread t2 = new Thread(st);t1.start();t2.setDaemon(true);t2.start();int num = 1;for(;;){if(++num==50){//st.setFlag();t1.interrupt();//t2.interrupt();break;}System.out.println("main...."+num);}System.out.println("over");}}

join:临时加入一个线程的时候可以使用join方法。

当A线程执行到了B线程的join方式。A线程处于冻结状态,释放了执行权,B开始执行。A什么时候执行呢?只有当B线程运行结束后,A才从冻结状态恢复运行状态执行。

class Demo implements Runnable{public void run(){for(int x=0; x<50; x++){System.out.println(Thread.currentThread().toString()+"....."+x);Thread.yield();}}}class  JoinDemo{public static void main(String[] args) throws Exception{Demo d = new Demo();Thread t1 = new Thread(d);Thread t2 = new Thread(d);t1.start();t2.start();//t2.setPriority(Thread.MAX_PRIORITY);//t1.join();//t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。for(int x=0; x<50; x++){//System.out.println(Thread.currentThread()+"....."+x);}}}

Lock接口:多线程在JDK1.5版本升级时,推出一个接口Lock接口。

解决线程安全问题使用同步的形式,(同步代码块,要么同步函数)其实最终使用的都是锁机制。

 

到了后期版本,直接将锁封装成了对象。线程进入同步就是具备了锁,执行完,离开同步,就是释放了锁。

在后期对锁的分析过程中,发现,获取锁,或者释放锁的动作应该是锁这个事物更清楚。所以将这些动作定义在了锁当中,并把锁定义成对象。

 所以同步是隐示的锁操作,而Lock对象是显示的锁操作,它的出现就替代了同步。

 

在之前的版本中使用Object类中wait、notify、notifyAll的方式来完成的。那是因为同步中的锁是任意对象,所以操作锁的等待唤醒的方法都定义在Object类中。

 而现在锁是指定对象Lock。所以查找等待唤醒机制方式需要通过Lock接口来完成。而Lock接口中并没有直接操作等待唤醒的方法,而是将这些方式又单独封装到了一个对象中。这个对象就是Condition,将Object中的三个方法进行单独的封装。并提供了功能一致的方法 await()、signal()、signalAll()体现新版本对象的好处。

< java.util.concurrent.locks >Condition接口:await()、signal()、signalAll();

/*生产者,消费者。多生产者,多消费者的问题。if判断标记,只有一次,会导致不该运行的线程运行了。出现了数据错误的情况。while判断标记,解决了线程获取执行权后,是否要运行!notify:只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。*/class Resource{private String name;private int count = 1;private boolean flag = false;public synchronized void set(String name)//  {while(flag)try{this.wait();}catch(InterruptedException e){}//   t1    t0this.name = name + count;//烤鸭1  烤鸭2  烤鸭3count++;//2 3 4System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);//生产烤鸭1 生产烤鸭2 生产烤鸭3flag = true;notifyAll();}public synchronized void out()//  t3{while(!flag)try{this.wait();}catch(InterruptedException e){}//t2  t3System.out.println(Thread.currentThread().getName()+"...消费者........"+this.name);//消费烤鸭1flag = false;notifyAll();}}class Producer implements Runnable{private Resource r;Producer(Resource r){this.r = r;}public void run(){while(true){r.set("烤鸭");}}}class Consumer implements Runnable{private Resource r;Consumer(Resource r){this.r = r;}public void run(){while(true){r.out();}}}class  ProducerConsumerDemo{public static void main(String[] args) {Resource r = new Resource();Producer pro = new Producer(r);Consumer con = new Consumer(r);Thread t0 = new Thread(pro);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(con);t0.start();t1.start();t2.start();t3.start();}}
                ---------------------- ASP.Net+Unity开发.Net培训、期待与您交流! ----------------------
0 0