黑马程序员——多线程
2015-06-16 11:11
543 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一,多线程概述
1,进程:是一个正在执行的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2,线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束,
(一个进程中至少有一个线程)
3,多线程:
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
像种在一个进程中有多个线程执行的方式,就叫做多线程。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。
二,创建线程的两种方式
(1 )继承Thread类
步骤:
1,定义类继承Thread
2,复写Thread类中的run方法(目的:将自定义代码存储在run方法中,让线程运行)
3,创建这个类的实例对象,相当于创建一个线程
4,调用线程的start方法(该方法有两个作用:启动该线程,调用run方法)
注意:覆盖run方法的原因:run方法是用于存储线程运行的代码。
start( )和run( )方法的区别:
start( )是用于开启线程并执行线程的run方法。
run()仅仅是对象调用方法,而线程创建了并没有运行。
下面是一段线程代码:
通过结果发现运行结果每一次都不同,为什么呢?
因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行,明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去同时运行的效果。
我们可以形象的把多线程的运行行为是在互相抢夺cpu的执行权,这就是多线程的一个特性。随机性,谁抢到谁执行,至于执行多长,cpu说了算。
同时还可以看出原来线程都有自己默认的名称,Thread_编号,该编号从0开始。
线程的运行状态:
被创建,运行,冻结,消亡
sleep方法需要指定睡眠时间,单位是毫秒。比如:wait(); notify();
一个特殊的状态:就绪(临时状态/阻塞)。具备了执行资格,但是还没有获取资源
冻结:放弃了执行资格。sleep(); wait(); notify();唤醒后先到临时状态等待执行
获取线程对象及名称:Thread.currentThread().getName()
设置线程名称:setName或者构造函数(super)
(2)实现Runnable接口
步骤:
1,定义类,实现Runnable接口。
2,,覆盖Runnable接口中的run方法。(将线程要用的代码存放在run方法中)
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去执行指定对象的run方法。就必须明确该run方法所属的对象。
实现方式和继承方式有什么区别呢?
实现方式的好处:避免单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口的子类的run方法中。
接口的代码示例:
三,多线程的安全问题
问题的原因:
当多条语句操作同一线程共享数据时,一个线程对多条语句执行了一部分,还没执行完,
另一个线程参与进来运行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
第一种方式:同步代码块
ynchronize(对象)
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1,必须要有两个或两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断,较为消耗资源。
下面是对卖票的代码进行修改(加了同步代码块):
运行结果:
多线程中,判断程序是否有安全问题,如果有,如何解决?如何找问题?
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的
第二种方式:同步函数
格式:
在函数上加上synchronized修饰符即可
同步函数用的锁是this
同步函数被静态修饰后,使用的锁是该方法所在类的字节码文件对象
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所有同步函数使用的锁都是this
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证。发现不是this。因为静态方法中不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class。 该对象的类型是Class。
同步函数和同步代码块的区别:
1,同步函数的锁是固定的this。
2,同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
示例:
四,死锁
死锁:同步中嵌套同步,而锁却不同。
示例:
运行结果:
五,线程间通信
其实就是线程间在操作同一资源,但是操作的动作不同。
下面是多个线程操作统一资源的示例:
运行结果:
注意:
等待唤醒机制:wait(),notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的那个锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同锁中的线程进行唤醒。
wait和sleep区别?
1,wait可以指定时间也可以不指定。sleep必须指定时间。
2,在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
生产者与消费者问题
对于多个生产者和消费者
1,为什么要定义while循环判断标记?
让被唤醒的线程再一次判断标记。
2,为什么定义notifyAll?
因为需要唤醒对方线程。只用notify容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
JDK1.5升级以后:
将同步synchronized替换成显示Lock操作。
将Object中的wait(),notify(),notifyAll(),替换成Condition对象。
该对象可以Lock锁,进行获取。
运行结果:
该示例中实现了本方只唤醒对方的操作。
停止线程
stop方法已经过时
1,定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
2,使用interrupt(中断)方法
该方法是结束线程的冻结状态,使线程回到运行状态中来。
如何停止线程。
只有一种,run方法结束,开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束。也就是线程结束。
特殊情况:
当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
线程的其他方法
setDaemon(boolean b)标记为守护线程(后台线程),当所有前台线程结束后自动结束。
1,当正在运行的线程都是守护线程时,Java虚拟机退出
2,必须在线程启动前调用
join( )抢夺cpu执行权。join可用来临时加入线程执行。
当A线程执行到了B线程的join()方法时。A就会等待。等B线程都执行完,A才会执行。join可以用来临时加入线程执行。
自定义线程名称:
toString()方法包含线程名称,优先级和线程组。
设置优先级:
setPriority( )所有线程都默认为5,最大是10
凡是数据是固定的,定为常量。数据共享的定为静态static
yield()是指临时停止当前正在运行的线程。
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一,多线程概述
1,进程:是一个正在执行的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
2,线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束,
(一个进程中至少有一个线程)
3,多线程:
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。
而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
像种在一个进程中有多个线程执行的方式,就叫做多线程。
多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。
其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。
二,创建线程的两种方式
(1 )继承Thread类
步骤:
1,定义类继承Thread
2,复写Thread类中的run方法(目的:将自定义代码存储在run方法中,让线程运行)
3,创建这个类的实例对象,相当于创建一个线程
4,调用线程的start方法(该方法有两个作用:启动该线程,调用run方法)
注意:覆盖run方法的原因:run方法是用于存储线程运行的代码。
start( )和run( )方法的区别:
start( )是用于开启线程并执行线程的run方法。
run()仅仅是对象调用方法,而线程创建了并没有运行。
下面是一段线程代码:
class Demo extends Thread { private String name ; Demo(String name) { this.name = name; } public void run() { for(int x = 0; x < 10; x++) { System.out.println(name + "...x=" + x + "...ThreadName=" + Thread.currentThread ().getName()); } } } class ThreadDemo1 { public static void main(String[] args) { Demo d1 = new Demo("zhangsan"); Demo d2 = new Demo("李四"); d1.start(); //开启线程,调用run方法。 d2.start(); for(int x = 0; x < 20; x++) { System.out.println("x = " + x + "...over..." + Thread.currentThread().getName()); } } }运行结果:
通过结果发现运行结果每一次都不同,为什么呢?
因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行,明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
cpu在做着快速的切换,以达到看上去同时运行的效果。
我们可以形象的把多线程的运行行为是在互相抢夺cpu的执行权,这就是多线程的一个特性。随机性,谁抢到谁执行,至于执行多长,cpu说了算。
同时还可以看出原来线程都有自己默认的名称,Thread_编号,该编号从0开始。
线程的运行状态:
被创建,运行,冻结,消亡
sleep方法需要指定睡眠时间,单位是毫秒。比如:wait(); notify();
一个特殊的状态:就绪(临时状态/阻塞)。具备了执行资格,但是还没有获取资源
冻结:放弃了执行资格。sleep(); wait(); notify();唤醒后先到临时状态等待执行
获取线程对象及名称:Thread.currentThread().getName()
设置线程名称:setName或者构造函数(super)
(2)实现Runnable接口
步骤:
1,定义类,实现Runnable接口。
2,,覆盖Runnable接口中的run方法。(将线程要用的代码存放在run方法中)
3,通过Thread类建立线程对象。
4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
为什么要将Runnable接口的子类对象传递给Thread的构造函数?
因为,自定义的run方法所属的对象是Runnable接口的子类对象。
所以要让线程去执行指定对象的run方法。就必须明确该run方法所属的对象。
实现方式和继承方式有什么区别呢?
实现方式的好处:避免单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread:线程代码存放在Thread子类run方法中。
实现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) { 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(); //Ticket t1 = new Ticket(); //Ticket t2 = new Ticket(); //Ticket t3 = new Ticket(); //Ticket t4 = new Ticket(); /*t1.start(); t2.start(); t3.start(); t4.start();*/ } }
三,多线程的安全问题
问题的原因:
当多条语句操作同一线程共享数据时,一个线程对多条语句执行了一部分,还没执行完,
另一个线程参与进来运行,导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
在java中对于多线程的安全问题提供了专业的解决方式——synchronized(同步)
这里也有两种解决方式,一种是同步代码块,还有就是同步函数。都是利用关键字synchronized来实现。
第一种方式:同步代码块
ynchronize(对象)
{
需要被同步的代码
}
对象如同锁,持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
同步的前提:
1,必须要有两个或两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断,较为消耗资源。
下面是对卖票的代码进行修改(加了同步代码块):
class Ticket implements Runnable{ private int num = 100; Object obj = new Object(); public void run(){ while(true ){ synchronized(obj ){ if(num > 0){ System.out.println(Thread.currentThread().getName() + "...sale..." + num--); } } } } } class TicketDemo1{ 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(); } }
运行结果:
多线程中,判断程序是否有安全问题,如果有,如何解决?如何找问题?
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的
第二种方式:同步函数
格式:
在函数上加上synchronized修饰符即可
同步函数用的锁是this
同步函数被静态修饰后,使用的锁是该方法所在类的字节码文件对象
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所有同步函数使用的锁都是this
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证。发现不是this。因为静态方法中不可以定义this。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。
类名.class。 该对象的类型是Class。
同步函数和同步代码块的区别:
1,同步函数的锁是固定的this。
2,同步代码块的锁是任意的对象。
建议使用同步代码块。
由于同步函数的锁是固定的this,同步代码块的锁是任意的对象,那么如果同步函数和同步代码块都使用this作为锁,就可以实现同步。
示例:
class Ticket implements Runnable{ private static int num = 100; Object obj = new Object(); boolean flag = true; public void run(){ if(flag ){ while(true ){ synchronized(Ticket.class){//this.getClass() if(num > 0){ try{ Thread. sleep(10); } catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...obj..." + num--); } } } } else while(true ) show(); } public static synchronized void show(){ if(num > 0){ try{ Thread. sleep(10); } catch(InterruptedException e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...function..." + num--); } } } class SynFunctionLockDemo1{ 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(); } }
四,死锁
死锁:同步中嵌套同步,而锁却不同。
示例:
class Test implements Runnable{ private boolean flag ; Test( boolean flag){ this.flag = flag; } public void run(){ if(flag ){ while(true ) synchronized(MyLock.locka){ System.out.println(Thread.currentThread().getName() + "...if locka..."); synchronized(MyLock.lockb){ System.out.println(Thread.currentThread().getName() + "...if lockb..."); } } } else{ while(true ) synchronized(MyLock.lockb){ System.out.println(Thread.currentThread().getName() + "...else lockb..."); synchronized(MyLock.locka){ System.out.println(Thread.currentThread().getName() + "...else locka..."); } } } } } class MyLock{ public static final Object locka = new Object(); public static final Object lockb = new Object(); } class DeadLockDemo{ public static void main(String[] args){ Test a = new Test(true ); Test b = new Test(false ); Thread t1 = new Thread(a); Thread t2 = new Thread(b); t1.start(); t2.start(); } }
运行结果:
五,线程间通信
其实就是线程间在操作同一资源,但是操作的动作不同。
下面是多个线程操作统一资源的示例:
/* 线程间通讯 其实就是多个线程在操作同一个资源 但是操作动作不同 */ class Res { String name; String sex; boolean flag; } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x=0; while(true) { synchronized(r) { if(r.flag) try{r.wait();}catch(Exception e){} if(x==0) { r.name="jake"; r.sex = "man"; } else { r.name="李丽"; r.sex= "女女女"; } x = (x+1)%2; r.flag = true; r.notify(); } } } } class Output implements Runnable { private Res r; Output(Res r) { this.r = r; } public void run() { while(true) { synchronized(r) { if(!r.flag) try{r.wait();}catch(Exception e){} System.out.println(r.name+"....."+r.sex); r.flag = false; r.notify(); } } } } class InputOutDemo { public static void main(String[] args) { Res r = new Res(); Input i = new Input(r); Output o = new Output(r); Thread t1 = new Thread(i); Thread t2 = new Thread(o); t1.start(); t2.start(); } }
运行结果:
注意:
等待唤醒机制:wait(),notify(),notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的那个锁。
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。
不可以对不同锁中的线程进行唤醒。
wait和sleep区别?
1,wait可以指定时间也可以不指定。sleep必须指定时间。
2,在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
生产者与消费者问题
对于多个生产者和消费者
1,为什么要定义while循环判断标记?
让被唤醒的线程再一次判断标记。
2,为什么定义notifyAll?
因为需要唤醒对方线程。只用notify容易出现只唤醒本方线程的情况,导致程序中的所有线程都等待。
JDK1.5升级以后:
将同步synchronized替换成显示Lock操作。
将Object中的wait(),notify(),notifyAll(),替换成Condition对象。
该对象可以Lock锁,进行获取。
import java.util.concurrent.locks.*; class ProducerConsumerDemo2 { public static void main(String[] args) { Resource res = new Resource(); Producer pro = new Producer(res); Consumer con = new Consumer(res); Thread t1 = new Thread(pro); Thread t2 = new Thread(pro); Thread t3 = new Thread(con); Thread t4 = new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } } class Resource { private String name; private int count=1; private boolean flag = false; private Lock lock = new ReentrantLock(); private Condition condition_pro = lock.newCondition(); private Condition condition_con = lock.newCondition(); public void set(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(); } } public void out()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; } public void run() { while(true) { try { res.set("+商品+"); } catch (InterruptedException e) { } } } } class Consumer implements Runnable { private Resource res; Consumer(Resource res) { this.res = res; } public void run() { while(true) { try { res.out(); } catch (InterruptedException e) { } } } }
运行结果:
该示例中实现了本方只唤醒对方的操作。
停止线程
stop方法已经过时
1,定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
2,使用interrupt(中断)方法
该方法是结束线程的冻结状态,使线程回到运行状态中来。
如何停止线程。
只有一种,run方法结束,开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束。也就是线程结束。
特殊情况:
当线程处于冻结状态。就不会读取到标记。那么线程就不会结束。
线程的其他方法
setDaemon(boolean b)标记为守护线程(后台线程),当所有前台线程结束后自动结束。
1,当正在运行的线程都是守护线程时,Java虚拟机退出
2,必须在线程启动前调用
join( )抢夺cpu执行权。join可用来临时加入线程执行。
当A线程执行到了B线程的join()方法时。A就会等待。等B线程都执行完,A才会执行。join可以用来临时加入线程执行。
自定义线程名称:
toString()方法包含线程名称,优先级和线程组。
设置优先级:
setPriority( )所有线程都默认为5,最大是10
凡是数据是固定的,定为常量。数据共享的定为静态static
yield()是指临时停止当前正在运行的线程。
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
相关文章推荐
- 作为程序员,其实你并没真正努力(二)
- 30岁,我的程序员梦想
- 黑马程序员__java基础__IO流(File流、其它流)
- 黑马程序员-------异常处理和常用类
- 一个程序员的日常
- 让程序员跳槽的非钱原因
- 黑马程序员----java基础之面向对象
- 怎么面试架构师【注重招式还是心法】
- 【剑指offer】面试题七:用两个栈实现队列
- 黑马程序员-----------面向对象
- 习理论与公务员面试-贺丽红-专题视频课程
- 职场中的80,90后到底看重什么
- 2014年最新前端开发面试题(题目列表+答案 完整版)
- 黑马程序员--Java基础Day10
- 专访谢宇:拒绝做程序员“砖工”
- 【好程序员笔记分享】—— 函数结合指针的妙用
- 黑马程序员————System类的讲解
- 中文分词算法-百度面试题
- 黑马程序员——Java基础---基础加强
- 黑马程序员————————IO流 FileWriter类 和 FileReader类的一些基本用法