黑马程序员_Java_多线程
来源:互联网 发布:知乎 宋仲基宋慧乔 编辑:程序博客网 时间:2024/05/18 02:43
线程的理解
1、同一个应用中,多个任务同时进行。就像editplus编辑工具,打开一个文件窗口就是一个线程。
2、线程可以有多个,但cpu每时每刻只做一件事(多核除外)。由于cpu处理速度很快,我们就感觉是同时进行的。所以宏观上,线程是并发进行的;从微观角度看,线程是异步执行的。
3、使用线程的目的是最大限度的利用cpu资源。想想当你在editplus中按下"ctrl + shift +S"保存全部文件的时候,如果要保存的文件比较多,没有多线程的话,前面的文件没有完成操作的话,后面的操作是执行不了的~!
创建线程
实现多线程,有两种手段:一:继承Thread类(启动线程方法:new MyThread().start();)
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
目的:将自定义代码存储在run方法。让线程运行。
3,调用线程的start方法,
该方法两个作用:启动线程,调用run方法。
线程中,start和run的区别
start():是开启线程并调用run方法
run():存放的是线程执行的代码,如果单纯的调用run方法,只是普通的创建对象调用方法,而并没有开启线程
class 类名 extends Thread{ @Override public void run() { //code }}
class Show extends Thread {public void run(){for (int i =0;i<5 ;i++ ){System.out.println(name +"_" + i);}}public Show(){}public Show(String name){this.name = name;}private String name;public static void main(String[] args) {new Show("csdn").run();new Show("黑马").run();}}
【运行结果】:
发现这些都是顺序执行的,说明调用run()方法不对,应该调用的是start()方法。
把上面的主函数修改为如下:
public static void main(String[] args) {new Show("csdn").start();new Show("黑马").start();}
在命令行执行:javac Show.java java Show,输出的可能的结果如下:
因为需要用到CPU的资源,多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。所以每次的运行结果基本是都不一样的;
这也是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说了算。
那么:为什么我们不能直接调用run()方法呢?
我的理解是:线程的运行需要本地操作系统的支持。
如果你查看start的源代码的时候,会发现:public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();在start0()方法中jvm调用run()方法,这个这个方法用了native关键字,native表示调用本地操作系统的函数,多线程的实现需要本地操作系统的支持。
二.实现Runable接口,(启动线程方法:new Thread(new MyRunnable()).start();)
class 类名 implements Runnable { @Override public void run() { //code }}
class Show implements Runnable {public Show(){}public Show(String name){this.name = name;}@Overridepublic void run(){for (int i =0;i<5 ;i++ ){System.out.println(name +"_" + i);}}private String name;public static void main(String[] args) {new Thread(new Show("csdn")).start();new Thread(new Show("黑马")).start();}}【可能的运行结果】:
关于选择继承Thread还是实现Runnable接口?
其实Thread也是实现Runnable接口的:
public class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } @Override public void run() { if (target != null) { target.run(); } }}其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。
Thread和Runnable的区别:
如果一个类继承Thread,则不能资源共享(有可能是操作的实体不是唯一的);但是如果实现了Runable接口的话,则可以实现资源共享。
class Show extends Thread{@Overridepublic void run(){for (int i = 0; i < 5 ; i++ ){if (count > 0){System.out.println(Thread.currentThread().getName()+": count=" + count--);}}}private int count = 5;public static void main(String[] args) {new Show().start();//new 出来多个实体new Show().start();}}【运行结果】:
public static void main(String[] args) {Show s = new Show();s.start();s.start();}
我们再以实现Runnable接口的方式修改上面的程序:
class Show implements Runnable{private int count = 10;//假设有10张票@Overridepublic void run(){for (int i = 0; i < 5 ; i++ ){if (this.count > 0){System.out.println(Thread.currentThread().getName()+"正在卖票" + this.count--);}}}public static void main(String[] args) {Show s = new Show(); //注意必须保证只对1个实体s操作new Thread(s,"窗口1").start();new Thread(s,"窗口2").start();new Thread(s,"窗口3").start();}}
【运行结果】:
总结:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
所以,还是以实现接口的方式来创建好些。在java中所有的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源;每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实际上就是在操作系统中启动了一个进程。
设置线程优先级(主线程的优先级是5,不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源)
<span style="font-size:14px;">Thread t = new Thread(myRunnable);t.setPriority(Thread.MAX_PRIORITY);//一共10个等级,Thread.MAX_PRIORITY表示最高级10t.start();</span>判断线程是否启动
class Show implements Runnable{public void run(){for (int i = 0; i < 5 ; i++ ){System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {Show s = new Show();Thread t = new Thread(s);System.out.println("线程启动前:" + t.isAlive());t.start();System.out.println("线程启动后:" + t.isAlive());}}
【运行结果】:
主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。
join,sleep,yield的用法与区别
join方法:假如你在A线程中调用了B线程的join方法B.join();,这时B线程继续运行,A线程停止(进入阻塞状态)。等B运行完毕A再继续运行。
sleep方法:线程中调用sleep方法后,本线程停止(进入阻塞状态),运行权交给其他线程。
yield方法:线程中调用yield方法后本线程并不停止,运行权由本线程和优先级不低于本线程的线程来抢。(不一定优先级高的能先抢到,只是优先级高的抢到的时间长)
后台线程
<span style="font-size:14px;">Thread t = new Thread(new Show());t.setDaemon(true);t.start();</span>在java程序中,只要前台有一个线程在运行,整个java程序进程不会消失,所以此时可以设置一个后台线程,这样即使java进程消失了,此后台线程依然能够继续运行。
结束线程(修改标示符flag为false来终止线程的运行)
<span style="font-size:14px;">class Show implements Runnable{private boolean flag = true;public void run(){while(flag){System.out.println(Thread.currentThread().getName()+" is living");}}public void shutDown(){this.flag = false;}public static void main(String[] args) {Show s = new Show();Thread t = new Thread(s);t.start();try{Thread.sleep(2);}catch (Exception e){e.printStackTrace();}s.shutDown();}}</span><span style="color: rgb(128, 128, 128); font-size: 15px;"></span>
线程同步synchronized
synchronized可以修饰方法,或者方法内部的代码块。被synchronized修饰的代码块表示:一个线程在操作该资源时,不允许其他线程操作该资源。
【问题引出】:比如说对于卖票系统,有下面的代码:
class Show implements Runnable{private int count =5;//5张票要卖public void run(){for (int i = 0; i < 10 ; i++ ){if (count > 0){try{Thread.sleep(200);}catch (Exception e){e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+","+count--);}}public static void main(String[] args) {Show s = new Show();new Thread(s,"A").start();new Thread(s,"B").start();new Thread(s,"C").start();}}【运行结果】:
这里出现了负数,显然这个是错的。,应该票数不能为负值。
如果想解决这种问题,就需要使用同步。
所谓同步就是在统一时间段中只有有一个线程运行,其他的线程必须等到这个线程结束之后才能继续执行。
【使用线程同步解决问题】
采用同步的话,可以使用同步代码块和同步方法两种来完成。
同步代码块:
语法格式:
synchronized(同步对象){
//需要同步的代码
}
但是一般都把当前对象this作为同步对象。
class Show implements Runnable{private int count =5;public void run(){for (int i = 0; i < 20 ; i++ ){synchronized(this){if (count > 0){try{//Thread.currentThread().yield();Thread.sleep(300);}catch (Exception e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+","+count--);}}}}public static void main(String[] args) {Show s = new Show();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t3.start();t1.start();t2.start();}}
【运行结果】:
也可以采用同步方法:
语法格式为synchronized 方法返回类型方法名(参数列表){
// 其他代码
}
class Show implements Runnable{private int count =5;public void run(){for (int i = 0; i < 20 ; i++ ){sale();}}public synchronized void sale(){if (count > 0){try{//Thread.currentThread().yield();Thread.sleep(300);}catch (Exception e){e.printStackTrace();}System.out.println(Thread.currentThread().getName()+","+count--);}}public static void main(String[] args) {Show s = new Show();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t3.start();t1.start();t2.start();}}同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程都需要判断锁,较为消耗资源,
wait、notify、notifyAll的用法
wait方法:当前线程转入阻塞状态,让出cpu的控制权,解除锁定。
notify方法:唤醒因为wait()进入阻塞状态的其中一个线程。
notifyAll方法: 唤醒因为wait()进入阻塞状态的所有线程。
这三个方法都必须用synchronized块来包装,而且必须是同一把锁,不然会抛出java.lang.IllegalMonitorStateException异常。
当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁,同步中嵌套同步容易引发死锁。
此处列举经典的【生产者和消费者问题】:
class Info { public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } private String name = "lfz"; private int age = 20;}/** * 生产者 */class Producer implements Runnable{ private Info info=null; Producer(Info info){ this.info=info; } public void run(){ boolean flag=false; for(int i=0;i<10;++i){ while (true) { if(flag){ this.info.setName("adanac"); this.info.setAge(20); flag=false; }else{ this.info.setName("jean"); this.info.setAge(30); flag=true; } } } }}/** * 消费者类 * */class Consumer implements Runnable{ private Info info=null; public Consumer(Info info){ this.info=info; } public void run(){ for(int i=0;i<10;++i){ System.out.println(this.info.getName()+"<---->"+this.info.getAge()); } }}class Show { public static void main(String[] args) { Info info=new Info(); Producer pro=new Producer(info); Consumer con=new Consumer(info); new Thread(pro).start(); new Thread(con).start(); }}
从结果中看到,名字和年龄并没有对应。
那么如何解决呢?
1)加入同步
2)加入等待和唤醒
先来看看加入同步会是如何:
class Info { public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public synchronized void set(String name, int age){ this.name=name; this.age=age; } public synchronized void get(){ System.out.println(this.getName()+"<===>"+this.getAge());} private String name = "lfz"; private int age = 20;}/** * 生产者 */class Producer implements Runnable{ private Info info=null; Producer(Info info){ this.info=info; } public void run(){ boolean flag=false; for(int i=0;i<10;++i){while (true){if(flag){this.info.set("adanac",10);flag=false;}else{this.info.set("lfz",20);flag=true;}} } }}/** * 消费者类 * */class Consumer implements Runnable{ private Info info=null; public Consumer(Info info){ this.info=info; } public void run(){ for(int i=0;i<10;++i){ try{ Thread.sleep(100); }catch (Exception e) { e.printStackTrace(); } this.info.get(); } }}class Show {public static void main(String[] args) {Info info=new Info(); Producer pro=new Producer(info); Consumer con=new Consumer(info); new Thread(pro).start(); new Thread(con).start();}}
运行结果来看,错乱的问题解决了,现在是adanac对应20,jean对于30。
/*
对于多个生产者和消费者。
为什么要定义while判断标记。
原因:让被唤醒的线程再一次判断标记。
为什么定义notifyAll,
因为需要唤醒对方线程。
因为只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。
*/
但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。
如果想解决这个问题,就需要使用Object类帮忙了,我们可以使用其中的等待和唤醒操作。
要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:class Info { public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public synchronized void set(String name, int age){ if (!flag) {try{super.wait();}catch (Exception e){e.printStackTrace();} }this.name=name;this.age=age;flag = false;super.notify(); } public synchronized void get(){ if (flag) { try { super.wait(); } catch (Exception e) { e.printStackTrace(); } }System.out.println(this.getName()+"<===>"+this.getAge());flag = true;super.notify();} private String name = "xiaoli"; private int age = 22;private boolean flag = false;}/** * 生产者 */class Producer implements Runnable{ private Info info=null; Producer(Info info){ this.info=info; } public void run(){ boolean flag=false; for(int i=0;i<10;++i){while (true){if(flag){this.info.set("adanac",10);flag=false;}else{this.info.set("lfz",20);flag=true;}} } }}/** * 消费者类 * */class Consumer implements Runnable{ private Info info=null; public Consumer(Info info){ this.info=info; } public void run(){ for(int i=0;i<10;++i){ this.info.get(); } }}class Show {public static void main(String[] args) {Info info=new Info(); Producer pro=new Producer(info); Consumer con=new Consumer(info); new Thread(pro).start(); new Thread(con).start();}}
【运行结果】:
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
Lock:替代了Synchronized
lock
unlock
newCondition()
Condition:替代了Object wait notify notifyAll
await();
signal();
signalAll();
*/
- 黑马程序员_JAVA_多线程
- 黑马程序员_Java_多线程
- 黑马程序员_java_多线程
- 黑马程序员_java_多线程
- 黑马程序员_Java_多线程
- 黑马程序员_JAVA_多线程
- 黑马程序员_Java_多线程
- 黑马程序员_Java_多线程
- 黑马程序员_Java_多线程
- **黑马程序员_Java_多线程**
- 黑马程序员_Java_多线程总结
- 黑马程序员_java_多线程1
- 黑马程序员_java_多线程2
- 黑马程序员_Java_多线程的创建
- 黑马程序员_java_多线程总结(上)
- 黑马程序员_java_多线程总结(下)
- 黑马程序员_Java_数据类型
- 黑马程序员_Java_反射
- 国外程序员推荐:每个程序员都应读的书
- Fragment的onViewStateRestored onViewCreated 函数
- nginx+lua+redis构建高并发应用
- union 和 struct 的区别与联系
- MSER_sample
- 黑马程序员_Java_多线程
- 微信向移动开放平台又迈进了一大步:微信开放平台更新
- 使用自定义的BaseAdapter实现LIstView的展示
- Seft-backup-001-iscsi
- I/O流之--FileWriter类 和 FileReader类
- Jquery简介处了解以及常用方法
- utf-8字符转换为String字符串(java)
- Android 新机制 ART
- 商城二次开发学习笔记---20140701