黑马程序员——java基础之多线程
2016-01-02 23:55
337 查看
——- android培训、java培训、期待与您交流! ———-
进程:正在进行中的程序(直译)。进程是程序在某个数据集上的运行,它有自己的生命周期,因调度而运行。因等待资源或事件而被处于等待状态,因完成任务而被撤销。
线程:进程中一个负责程序执行的控制单元(执行路径)。是一个程序中的不同执行路径。
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。
开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程解决了多部分代码同时运行的问题。
知识扩展:其实更细节说明虚拟机,jvm不只一个线程 还有负责垃圾回收机制的线程
—————————-割——————————-割——————————–
2. 创建线程的两种方法
方法1: 继承Thread类
通过对api的查找 java已经提供了堆线程这类事物的描述 就是Thread类
定义类 继承Thread
复写Thread类中的run方法
目的是将自定义的代码存储在run方法中,让线程运
调用线程的start方法
两个作用:启动线程 调用run方法
如下面的示例代码:
从上面的实例代码运行结果可以得到几点:
运行结果每一次都不同
这是因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行
明确一点,在某个时刻,只能有一个程序在运行
cpu在作者快速的切换,以达到看上去是同时运行的效果
这就是java多线程的一个特性: 随机性,谁抢到是执行, 至于执行时间多长 cpu说了算.
小知识点:
为什么要覆盖run方法呢?
Thread 类用于描述线程,该类定义了一个功能用于存储线程要运行的代码,该存储功能就是run方法
也就是说Thread类中的run方法,用于存储要运行的代码
不能直接调用run()方法,因为这样不能开启你线程.仅仅是普通的调用.
—————————-割——————————-割——————————–
方法2: 实现runnable接口
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复写其中run方法的方式。
定义类实现Runnable接口.
覆盖接口中的run方法,将线程的任务代码封装到run方法中
通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
调用线程对象的start方法开启线程。
为什么要将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递?
因为线程的任务都封装在Runnable接口子类对象的run方法中。 所以要在线程对象创建时就必须明确要运行的任务
示例代码:
运行结果如下:
—————————-割——————————-割——————————–
实现Runnable接口的好处:
将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
避免了Java单继承的局限性。 所以,创建线程的第二种方式较为常用。
两种方式的区别
继承方式: 线程代码存放在Thread子类run方法中
实现方式: 线程代码存放在接口的子类的run方法中
—————————-割——————————-割——————————–
3. 线程的生命周期
线程的几种状态:
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行格变为临时状态。
消忙状态:stop()方法,或者run方法结束。
如下图:
—————————-割——————————-割——————————–
4. 线程的安全
线程安全问题产生的原因:
1. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
3. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
线程安全问题的解决方案—-同步
线程的同步: 就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。 必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
总结:
- 同步的好处:解决了线程的安全问题。
- 同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
- 同步的前提:必须有多个线程并使用同一个锁。
示例代码—-同步后的卖票程序
运行结果:
结果分析:
上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。
总结:
要明确哪些代码是多线程运行代码。
要明确共享数据
要明确多线程运行代码中哪些语句是操作共享数据的。
—————————-割——————————-割——————————–
5. 线程的安全的单例设计模式–懒汉式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式存在安全问题,可以使用同步函数解决,也可以使用代码块的方式来解决
示例代码
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
—————————-割——————————-割——————————–
6. 线程的安全—-死锁
死锁常见情景之一:同步的嵌套。
由上图可以看到程序已经被锁死,无法向下执行。
由下面代码可以看到,run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。而执行show方法则必须获取this对象锁,然后才能执行其中的同步代码块。
当线程t1获取到obj对象锁执行同步代码块,线程t2获取到this对象锁执行show方法。 同步代码块中的show方法因无法获取到this对象锁无法执行,show方法中的同步代码块因无法获取到obj对象锁无法执行,就会产生死锁。
另一个示例代码: 便于我们理解
运行结果:
程序卡住了,每次的运行结果都不一样.
总结: 我们学习死锁是为了在写程序的时候避免这样的情况
——- android培训、java培训、期待与您交流! ———-
java基础之多线程
一. 多线程的概念
1. 进程、线程和多线程:进程:正在进行中的程序(直译)。进程是程序在某个数据集上的运行,它有自己的生命周期,因调度而运行。因等待资源或事件而被处于等待状态,因完成任务而被撤销。
线程:进程中一个负责程序执行的控制单元(执行路径)。是一个程序中的不同执行路径。
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。
开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。
多线程解决了多部分代码同时运行的问题。
知识扩展:其实更细节说明虚拟机,jvm不只一个线程 还有负责垃圾回收机制的线程
—————————-割——————————-割——————————–
2. 创建线程的两种方法
方法1: 继承Thread类
通过对api的查找 java已经提供了堆线程这类事物的描述 就是Thread类
定义类 继承Thread
复写Thread类中的run方法
目的是将自定义的代码存储在run方法中,让线程运
调用线程的start方法
两个作用:启动线程 调用run方法
如下面的示例代码:
[code]//定义类 继承Thread class Demo extends Thread { //复写run方法 public void run() { for(int i = 0;i<60;i++) System.out.println("Demo run--"+i); } } class ThreadDemo1 { public static void main(String[] args) { Demo d = new Demo();//创建好一个线程 d.start();//启动线程并执行该线程的run方法 for(int x = 0;x<60;x++) System.out.println("Hello run--"+x); } }
从上面的实例代码运行结果可以得到几点:
运行结果每一次都不同
这是因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行
明确一点,在某个时刻,只能有一个程序在运行
cpu在作者快速的切换,以达到看上去是同时运行的效果
这就是java多线程的一个特性: 随机性,谁抢到是执行, 至于执行时间多长 cpu说了算.
小知识点:
为什么要覆盖run方法呢?
Thread 类用于描述线程,该类定义了一个功能用于存储线程要运行的代码,该存储功能就是run方法
也就是说Thread类中的run方法,用于存储要运行的代码
不能直接调用run()方法,因为这样不能开启你线程.仅仅是普通的调用.
—————————-割——————————-割——————————–
方法2: 实现runnable接口
使用继承方式有一个弊端,那就是如果该类本来就继承了其他父类,那么就无法通过Thread类来创建线程了。这样就有了第二种创建线程的方式:实现Runnable接口,并复写其中run方法的方式。
定义类实现Runnable接口.
覆盖接口中的run方法,将线程的任务代码封装到run方法中
通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
调用线程对象的start方法开启线程。
为什么要将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递?
因为线程的任务都封装在Runnable接口子类对象的run方法中。 所以要在线程对象创建时就必须明确要运行的任务
示例代码:
[code] //定义类,实现Runnable接口 class Demo implements Runnable { public void run()//覆盖run方法 { show(); } public void show() { for(int x = 0; x < 20; x++) { System.out.println(Thread.currentThread().getName() + "..." + x); } } } class ThreadDemo2 { public static void main(String[] args) { Demo d = new Demo();//创建子类对象 //将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递 Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start();//启动线程并调用其中的run方法 t2.start(); } }
运行结果如下:
—————————-割——————————-割——————————–
实现Runnable接口的好处:
将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
避免了Java单继承的局限性。 所以,创建线程的第二种方式较为常用。
两种方式的区别
继承方式: 线程代码存放在Thread子类run方法中
实现方式: 线程代码存放在接口的子类的run方法中
—————————-割——————————-割——————————–
3. 线程的生命周期
线程的几种状态:
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行格变为临时状态。
消忙状态:stop()方法,或者run方法结束。
如下图:
—————————-割——————————-割——————————–
4. 线程的安全
[code]/* 需求:卖票程序 多个卖票窗口 */ class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { if(tick>0) { //显示余票 System.out.println(Thread.currentThread().getName()+"---"+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. 多个线程在操作共享的数据。
2. 操作共享数据的线程代码有多条。
3. 当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。
线程安全问题的解决方案—-同步
线程的同步: 就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。 必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
在java中,用同步代码块就可以解决这个问题。
同步代码块的格式:
synchronized(对象)
{
需要被同步的代码;
}
总结:
- 同步的好处:解决了线程的安全问题。
- 同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
- 同步的前提:必须有多个线程并使用同一个锁。
示例代码—-同步后的卖票程序
[code]/* 需求:卖票程序 多个卖票窗口 */ class Ticket implements Runnable { private int tick = 100; Object obj = new Object(); public void run() { while(true) { synchronized (obj) { if(tick>0) { //显示余票 System.out.println(Thread.currentThread().getName()+"---"+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(); } }
运行结果:
结果分析:
上图显示安全问题已被解决,原因在于Object对象相当于是一把锁,只有抢到锁的线程,才能进入同步代码块向下执行。
总结:
要明确哪些代码是多线程运行代码。
要明确共享数据
要明确多线程运行代码中哪些语句是操作共享数据的。
—————————-割——————————-割——————————–
5. 线程的安全的单例设计模式–懒汉式
饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式存在安全问题,可以使用同步函数解决,也可以使用代码块的方式来解决
示例代码
[code]/* 单例设计模式--懒汉式 实例的延迟加载 */ 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 ; } }
同步函数和同步代码块的区别:
同步函数的锁是固定的this。
同步代码块的锁是任意的对象。
建议使用同步代码块。
—————————-割——————————-割——————————–
6. 线程的安全—-死锁
死锁常见情景之一:同步的嵌套。
[code] class Ticket implements Runnable { private static int num = 100; Object obj = new Object(); boolean flag = true; public void run() { if (flag) { while (true) { synchronized (obj) { show(); } } } else while (true) show(); } public synchronized void show() { synchronized (obj) { if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...function..." + 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) { e.printStackTrace(); } t.flag = false; t2.start(); } }
由上图可以看到程序已经被锁死,无法向下执行。
由下面代码可以看到,run方法中的同步代码块需要获取obj对象锁,才能执行代码块中的show方法。而执行show方法则必须获取this对象锁,然后才能执行其中的同步代码块。
当线程t1获取到obj对象锁执行同步代码块,线程t2获取到this对象锁执行show方法。 同步代码块中的show方法因无法获取到this对象锁无法执行,show方法中的同步代码块因无法获取到obj对象锁无法执行,就会产生死锁。
另一个示例代码: 便于我们理解
[code]//让一个类实现Runnable接口 class Test implements Runnable { private boolean flag; Test(boolean flag) { this.flag = flag; } //复写run方法 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_1 { public static void main(String[] args) { Test a = new Test(true);//创建Runnable子类的实例对象 Test b = new Test(false); Thread t1 = new Thread(a);//创建线程 Thread t2 = new Thread(b); t1.start();//启动线程 t2.start(); } }
运行结果:
程序卡住了,每次的运行结果都不一样.
总结: 我们学习死锁是为了在写程序的时候避免这样的情况
——- android培训、java培训、期待与您交流! ———-
相关文章推荐
- 从头认识java-15.7 Map(4)-介绍HashMap的工作原理-hash碰撞(经常作为面试题)
- 面试题——查找单链表的中间节点
- 黑马程序员——java基础之多态
- 初入职场的思考
- Android面试宝典(一位优秀应届生面试经验,很多Android高阶问题)
- Android面试题及其参考答案
- 心路历程(四)-我的2015
- 黑马程序员——数据加密问题的两种实现方式
- 程序员面试指南
- 如何编写职业发展计划
- 面试题10 找到链表中倒数第 K 个元素
- 指针和数组的面试题
- 黑马程序员——折纸问题、九九乘法表和菱形图案的打印
- 程序员新年愿景
- 黑马程序员——Java---&&和||逻辑表达式
- 黑马程序员——阶乘的两种实现方法及水仙花数的打印
- 程序员精选图书
- 往者可谏,来者可追
- 程序员练级之路
- 实习一个月,记录下对学习能力这个词的感觉。