线程的理解

 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{@Overridepublic 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 thenit 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 {@Overridepublic 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();}@Overridepublic 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表示最高级10
t.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_多线程相关推荐

  1. 黑马程序员—C#多线程

    ----------------------Windows Phone 7手机开发..Net培训.期待与您交流! ---------------------- 通过几天的学习,对多线程有了初步的了解, ...

  2. 黑马程序员_Java_交通灯管理

    交通灯管理项目模拟了对十字路口交通灯的控制,一般在我们生活中的十字路口是有人行道的,而此项目没有考虑人行道,到下面需求的第3条,右转车辆不受信号灯控制可以看出. 具体的需求如下: 1.异步随机生成按照 ...

  3. 黑马程序员——Java 多线程

    ------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- Java 多线程 1.多线程概述 多线程概述 进程: 正在运行的程序,是系统进行资源分配和调用的 ...

  4. 黑马程序员5 多线程

    ------- android培训.java培训.期待与您交流! ---------- 创建线程的第一种方式:继承Thread类. 步骤: 1,定义类继承Thread. 2,复写Thread类中的ru ...

  5. 黑马程序员_Java_反射

    反射 Reflection,听其名就像照镜子一样,既能看见自己也可以看见别人的每一部分.反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作.例如 ...

  6. 黑马程序员_Java_线程池

    线程池 线程池就是一个线程的容器[可以是链表,数组等]. 作用 作用就是限制系统中执行线程的数量,可以自动或手动设置线程数量,以达到最佳效果[少了浪费系统资源,多了消耗资源(每个线程需要大约1MB内存 ...

  7. 黑马程序员_Java_异常

    /* 异常:就是程序在运行时出现的不正常情况,其实就是java对不正常情况进行描述后的对象体现,通过java的类的形式进行描述,并封装成对象.两种划分:1.严重的问题:Java通过Error类进行描述 ...

  8. 黑马程序员_java_银行售票系统

    ------- android培训.java培训.期待与您交流! ---------- 银行业务调度系统 6个窗口 1-4  普通窗口 5 快速窗口(交费用的) 6 vip窗口 客户随机生成 1:6: ...

  9. [置顶] 黑马程序员 -- 多线程

    黑马程序员 -- 多线程 polk601001 星期五, 15/06/2012 - 17:14 发布 什么是多线程? 多线程就是使程序并发(同时)执行几个操作. .NET 框架类库在System.Th ...

最新文章

  1. python⾯向对象学员管理系统
  2. WebApi发送HTML表单数据:文件上传与多部分MIME
  3. 曳舞---1、曳舞各个动作要点
  4. python缩进编码教程_python基础语法教程:行与缩进
  5. Linux系统(五)负载均衡LVS集群之DR模式
  6. 测试驱动开发 测试前移_我如何以及为什么认为测试驱动开发值得我花时间
  7. PyCharm LicenseServer 破解
  8. jieba库统计出现词语次数
  9. Java网络编程学习汇总
  10. CocosBuilder 教程
  11. 软件设计师中级-UML建模
  12. 紫光输入法linux,紫光拼音输入法下载_紫光拼音输入法最新版下载-太平洋下载中心...
  13. 089【RTKLIB源码之postpos-5】pppos
  14. dcx矩阵 - 打表 - 找规律
  15. QEMU,Dynamips,IOL区别
  16. RNA编辑基本形式与相关技术的研究现状(阅读小结)
  17. 处理文档用计算机配置文件,计算机中丢失api-ms-win-crt-runtime-l1-1-0.dll的修复方案...
  18. win7开机启动项设置_Win7电脑如何提高开机速度?
  19. Crow和cinatra的C++web框架
  20. 13.分组曲线拟合与置信条带

热门文章

  1. 【3d建模】zbrush教程非常适合零基础入门,一学就会
  2. Google Earth Engine(GEE)下载全球土壤砂粒(Sand)含量数据
  3. MIZ7035上的AXI接口的MIG测试
  4. K2 BPM介绍(1)
  5. 新手坐高铁怎么找车厢_第一次做高铁怎么找车厢和做位,谢谢大家
  6. 据说很多程序员下班后都不关电脑,入职开机,离职关机,是真的吗?
  7. JAVA获取前一个月的第一天和最后一天
  8. [转] 能不吃最好别吃:一个食品专业本科生的自白
  9. JavaScript整合版——后端程序员学js,只读一文直接零基础入门,大学生期末考试不挂科宝典。
  10. Qt的跨平台的部分原理和机制