黑马程序员——javase基础--多线程
2015-04-14 00:32
295 查看
——- android培训、java培训、期待与您交流! ———-
一、多线程概述
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。
而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式。
1、 继承方式
通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
覆盖run方法的原因:
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
2、 实现方式
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。
创建步骤:
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
e,调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
三、两种方式的区别和线程的几种状态
1、两种创建方式的区别
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口子类run方法中。
2、几种状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
四、线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单的说就两点:
a、多个线程访问出现延迟。
b、线程随机性 。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
2、解决办法——同步
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
同步代码块
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
3、同步的前提
a,必须要有两个或者两个以上的线程。
b,必须是多个线程使用同一个锁。
4、同步的利弊
好处:解决了多线程的安全问题。
5、如何寻找多线程中的安全问题
a,明确哪些代码是多线程运行代码。
b,明确共享数据。
c,明确多线程运行代码中哪些语句是操作共享数据的。
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
示例:
六、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
示例:
七、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
示例:
运行结果:
八、什么时候写多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。
示例:
一、多线程概述
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。
一个进程中至少有一个线程。
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。
而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式。
1、 继承方式
通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
覆盖run方法的原因:
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
2、 实现方式
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复习其中run方法的方式。
创建步骤:
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法,就必须明确该run方法所属对象。
e,调用Thread类中start方法启动线程。start方法会自动调用Runnable接口子类的run方法。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
/* 需求:简单的卖票程序。 多个窗口卖票。 */ class Ticket implements Runnable//extends Thread { private int tick = 100; public void run() { while(true) { if(tick>0) { //显示线程名及余票数 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } } } class TicketDemo { public static void main(String[] args) { //创建Runnable接口子类的实例对象 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(); } }
三、两种方式的区别和线程的几种状态
1、两种创建方式的区别
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口子类run方法中。
2、几种状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
四、线程安全问题
1、导致安全问题的出现的原因:
当多条语句在操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没用执行完,另一个线程参与进来执行。导致共享数据的错误。
简单的说就两点:
a、多个线程访问出现延迟。
b、线程随机性 。
注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。
2、解决办法——同步
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
同步代码块
用法:
synchronized(对象)
{需要被同步的代码}
同步可以解决安全问题的根本原因就在那个对象上。其中对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
3、同步的前提
a,必须要有两个或者两个以上的线程。
b,必须是多个线程使用同一个锁。
4、同步的利弊
好处:解决了多线程的安全问题。
5、如何寻找多线程中的安全问题
a,明确哪些代码是多线程运行代码。
b,明确共享数据。
c,明确多线程运行代码中哪些语句是操作共享数据的。
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
示例:
/* 加同步的单例设计模式————懒汉式 */ class Single { private static Single s = null; private Single(){} public static void getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) s = new Single(); } } return s; } }
六、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
示例:
/* 写一个死锁程序 */ //定义一个类来实现Runnable,并复写run方法 class LockTest implements Runnable { private boolean flag; LockTest(boolean flag) { this.flag=flag; } public void run() { if(flag) { while(true) { synchronized(LockClass.locka)//a锁 { System.out.println(Thread.currentThread().getName()+"------if_locka"); synchronized(LockClass.lockb)//b锁 { System.out.println(Thread.currentThread().getName()+"------if_lockb"); } } } } else { while(true) { synchronized(LockClass.lockb)//b锁 { System.out.println(Thread.currentThread().getName()+"------else_lockb"); synchronized(LockClass.locka)//a锁 { System.out.println(Thread.currentThread().getName()+"------else_locka"); } } } } } } //定义两个锁 class LockClass { static Object locka = new Object(); static Object lockb = new Object(); } class DeadLock { public static void main(String[] args) { //创建2个进程,并启动 new Thread(new LockTest(true)).start(); new Thread(new LockTest(false)).start(); } }
七、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。
示例:
/* 生产者生产商品,供消费者使用 有两个或者多个生产者,生产一次就等待消费一次 有两个或者多个消费者,等待生产者生产一次就消费掉 */ import java.util.concurrent.locks.*; class Resource { private String name; private int count=1; private boolean flag = false; //多态 private Lock lock=new ReentrantLock(); //创建两Condition对象,分别来控制等待或唤醒本方和对方线程 Condition condition_pro=lock.newCondition(); Condition condition_con=lock.newCondition(); //p1、p2共享此方法 public void setProducer(String name)throws InterruptedException { lock.lock();//锁 try { while(flag)//重复判断标识,确认是否生产 condition_pro.await();//本方等待 this.name=name+"......"+count++;//生产 System.out.println(Thread.currentThread().getName()+"...生产..."+this.name);//打印生产 flag=true;//控制生产\消费标识 condition_con.signal();//唤醒对方 } finally { lock.unlock();//解锁,这个动作一定执行 } } //c1、c2共享此方法 public void getConsumer()throws InterruptedException { lock.lock(); try { while(!flag)//重复判断标识,确认是否可以消费 condition_con.await(); System.out.println(Thread.currentThread().getName()+".消费."+this.name);//打印消费 flag=false;//控制生产\消费标识 condition_pro.signal(); } finally { lock.unlock(); } } } //生产者线程 class Producer implements Runnable { private Resource res; Producer(Resource res) { this.res=res; } //复写run方法 public void run() { while(true) { try { res.setProducer("商品"); } catch (InterruptedException e) { } } } } //消费者线程 class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res=res; } //复写run public void run() { while(true) { try { res.getConsumer(); } catch (InterruptedException e) { } } } } class ProducerConsumer { public static void main(String[] args) { Resource res=new Resource(); new Thread(new Producer(res)).start();//第一个生产线程 p1 new Thread(new Consumer(res)).start();//第一个消费线程 c1 new Thread(new Producer(res)).start();//第二个生产线程 p2 new Thread(new Consumer(res)).start();//第二个消费线程 c2 } }
运行结果:
八、什么时候写多线程?
当某些代码需要同时被执行时,就用单独的线程进行封装。
示例:
class ThreadTest { public static void main(String[] args) { //一条线程 new Thread() { public void run() { for (int x=0;x<100 ;x++ ) { System.out.println(Thread.currentThread().toString()+"....."+x); } } }.start(); //又是一条线程 Runnable r= new Runnable() { public void run() { for (int x=0;x<100 ;x++ ) { System.out.println(Thread.currentThread().getName()+"....."+x); } } }; new Thread(r).start(); //可以看作主线程 for (int x=0;x<1000 ;x++ ) { System.out.println("Hello World!"); } } }
相关文章推荐
- 黑马程序员--javaSE--多线程基础总结
- 黑马程序员----【javaSE基础】多线程
- 黑马程序员_JavaSE基础12 之 导入包 多线程的创建
- 黑马程序员--Java基础之多线程
- 黑马程序员——Java基础——多线程的同步、死锁和等待唤醒机制
- 黑马程序员_JavaSe基础知识总结六:super、final关键字
- 黑马程序员_JavaSE基础知识总结二十:课堂上关于集合的几个超经典案例(结课前集合知识温故)
- 黑马程序员_Java基础_多线程1
- 黑马程序员---java基础---07多线程
- 黑马程序员-JAVA基础之多线程
- 黑马程序员-Java基础-多线程之单例模式总结
- 黑马程序员——Java基础(九)之多线程
- 黑马程序员:Java基础——多线程的停止与守护以及Join,优先级和yield方法
- 黑马程序员-Java语言基础– 多线程 第12天
- 黑马程序员——Java基础:多线程及其应用
- 黑马程序员---java基础---多线程
- 黑马程序员_java基础加强7_多线程加强
- 黑马程序员————java基础---------多线程与反射
- 黑马程序员_JavaSE基础08 之 单例设计模式 继承 覆盖
- 黑马程序员——java基础---多线程