黑马程序员——Java基础之多线程
2015-07-02 09:16
726 查看
------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、线程简介
要先了解线程就得知道进程。进程就是正在执行的程序,也就是程序执行的路径。线程就是进程中的独立控制单元,线程控制着进程的执行,一进程至少有一个线程,当有多个线程时,每个线程完成一个功能,并与其他线程并发执行,这种机制就叫多线程。当JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
多线程的意义:让程序同时运行,提高程序执行效率。
windows操作系统CPU的工作原理:系统可以分配给每个进程一段有限的CPU时间片,CPU在这段时间中执行某个进程,然后下一个时间片又跳到另一个进程中去执行。由于CPU这样的跳转很快,所以使得每个进程好像是同时执行一样。
二、实现线程的两种方式:
1、继承Thread类
通过继承Thread类,覆盖类中的run()方法,通过Thread类中的start()方法来执行线程。
创建步骤:1).定义一个类继承Thread类。2.)覆盖Thread类中的run方法。3).直接创建Thread的子类对象创建线程。4).调用start方法开启线程并调用线程的任务run方法执行。
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
示例:
运行结果:
10 Thread-0
9 Thread-0
10 Thread-1
8 Thread-0
9 Thread-1
7 Thread-0
8 Thread-1
6 Thread-0
7 Thread-1
5 Thread-0
4 Thread-0
3 Thread-0
2 Thread-0
1 Thread-0
0 Thread-0
6 Thread-1
5 Thread-1
4 Thread-1
3 Thread-1
2 Thread-1
1 Thread-1
0 Thread-1
可见一个线程并非一次性执行完的。
2、实现Rannable接口
当我们已经继承了别的类但是又要实现多线程那怎么办,这时就可以用通过实现Runnable接口来实现。
创建步骤:
1).定义类实现Runnable接口。
2).覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3).通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传
递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4).调用线程对象的start方法开启线程。
实现Rannable接口的好处就是打破单继承的局限性。所以当创建多线程的时候应该用这种实现Rannable接口方式。
示例:
运行结果是:
如果start方法调用一个已经启动的线程,系统会抛出IllegalThreadStateException异常。
三、线程安全问题
先看一个例子:
运行结果 :
通过结果可以看出 :买票居然卖出了负数,这是不可能的,这时多线程出现的安全问题,并且此时的CPU 利用率很高,那么为什么会出现在这种情况呢?
当多条语句在操作线程共享数据时,一个线程的语句只执行了一部分,这是另一个进程进来的,导致共享数据的错误。
解决办法是对操作共享数据的语句只让一个线程都执行完,而其他线程不能执行。这就需要
1)同步代码块,用关键字synchronized。
synchronized(对象)
{
需要被同步的代码
}
2)同步函数,格式:在函数上加上synchronized修饰符即可。它实用的锁是this。静态函数使用的锁是该类的字节码文件,即类.Class
上示例修改后的代码:
运行结果:
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,CPU利用率高无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
如何找出多线程中的安全问题:
1、明确那些代码是多线程代码
2、明确共享数据
3、明确多线程运行代码中哪些语句是操作共享数据的。
多线程之懒汉式单例模式:实例延迟加载,多线程访问时会有安全问题,加同步能解决,锁是该类的字节码文件。
示例:
四、线程死锁问题
什么叫死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作
用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进锁。
示例:
运行结果:
五、线程间通信
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1). wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2). notify():唤醒线程池中的一个线程(任何一个都有可能)。
3). notifyAll():唤醒线程池中的所有线程。
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法, 监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定在object类中。
2、JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
下面是典型的生产者与消费者的问题
代码示例如下:
运行结果为:
一、线程简介
要先了解线程就得知道进程。进程就是正在执行的程序,也就是程序执行的路径。线程就是进程中的独立控制单元,线程控制着进程的执行,一进程至少有一个线程,当有多个线程时,每个线程完成一个功能,并与其他线程并发执行,这种机制就叫多线程。当JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。
多线程的意义:让程序同时运行,提高程序执行效率。
windows操作系统CPU的工作原理:系统可以分配给每个进程一段有限的CPU时间片,CPU在这段时间中执行某个进程,然后下一个时间片又跳到另一个进程中去执行。由于CPU这样的跳转很快,所以使得每个进程好像是同时执行一样。
二、实现线程的两种方式:
1、继承Thread类
通过继承Thread类,覆盖类中的run()方法,通过Thread类中的start()方法来执行线程。
创建步骤:1).定义一个类继承Thread类。2.)覆盖Thread类中的run方法。3).直接创建Thread的子类对象创建线程。4).调用start方法开启线程并调用线程的任务run方法执行。
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
示例:
package com.heima.thread; public class ThreadDemo extends Thread{ /** * @param args */ private int x=10; public void run() { while(x>=0) { System.out.println(x--+" "+Thread.currentThread().getName()); } } public static void main(String[] args) { // TODO Auto-generated method stub ThreadDemo a1=new ThreadDemo(); ThreadDemo a2=new ThreadDemo(); a1.start(); a2.start(); } }
运行结果:
10 Thread-0
9 Thread-0
10 Thread-1
8 Thread-0
9 Thread-1
7 Thread-0
8 Thread-1
6 Thread-0
7 Thread-1
5 Thread-0
4 Thread-0
3 Thread-0
2 Thread-0
1 Thread-0
0 Thread-0
6 Thread-1
5 Thread-1
4 Thread-1
3 Thread-1
2 Thread-1
1 Thread-1
0 Thread-1
可见一个线程并非一次性执行完的。
2、实现Rannable接口
当我们已经继承了别的类但是又要实现多线程那怎么办,这时就可以用通过实现Runnable接口来实现。
创建步骤:
1).定义类实现Runnable接口。
2).覆盖接口中的run方法,将线程的任务代码封装到run方法中。
3).通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传
递。为什么?因为线程的任务都封装在Runnable接口子类对象的run方法中。所以要在线程对象创建时就必须明确要运行的任务。
4).调用线程对象的start方法开启线程。
实现Rannable接口的好处就是打破单继承的局限性。所以当创建多线程的时候应该用这种实现Rannable接口方式。
示例:
package com.heima.thread; public class ThreadDemo1 implements Runnable{ public void run() { show(); } private void show() { // TODO Auto-generated method stub int x=0; while(x<=10) { System.out.println(x+++" "+Thread.currentThread().getName()); } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ThreadDemo1 a=new ThreadDemo1(); Thread t1=new Thread(a); Thread t2=new Thread(a); t1.start(); t2.start(); } }
运行结果是:
如果start方法调用一个已经启动的线程,系统会抛出IllegalThreadStateException异常。
三、线程安全问题
先看一个例子:
package com.heima.thread; /** * 需求:简单的卖票程序,模拟4个线程同时卖100张票。多窗口同时卖票 * @author Administrator * */ class Ticket implements Runnable{ private int ticket=100; public void run() { while(true) { if(ticket>0) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"…………"+ticket--); } } } } public class ThreadTicket { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub 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(); } }
运行结果 :
通过结果可以看出 :买票居然卖出了负数,这是不可能的,这时多线程出现的安全问题,并且此时的CPU 利用率很高,那么为什么会出现在这种情况呢?
当多条语句在操作线程共享数据时,一个线程的语句只执行了一部分,这是另一个进程进来的,导致共享数据的错误。
解决办法是对操作共享数据的语句只让一个线程都执行完,而其他线程不能执行。这就需要
1)同步代码块,用关键字synchronized。
synchronized(对象)
{
需要被同步的代码
}
2)同步函数,格式:在函数上加上synchronized修饰符即可。它实用的锁是this。静态函数使用的锁是该类的字节码文件,即类.Class
上示例修改后的代码:
package com.heima.thread; /** * 需求:简单的卖票程序,模拟4个线程同时卖100张票。多窗口同时卖票 * @author Administrator * */ class Ticket implements Runnable{ private int ticket=100; Object obj=new Object(); public void run() { while(true) { synchronized (obj) { if(ticket>0) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"…………"+ticket--); } } } } } public class ThreadTicket { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub 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(); } }
运行结果:
同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,CPU利用率高无形中会降低程序的运行效率。
同步的前提:必须有多个线程并使用同一个锁。
如何找出多线程中的安全问题:
1、明确那些代码是多线程代码
2、明确共享数据
3、明确多线程运行代码中哪些语句是操作共享数据的。
多线程之懒汉式单例模式:实例延迟加载,多线程访问时会有安全问题,加同步能解决,锁是该类的字节码文件。
示例:
<span style="white-space:pre"> </span>private static Single getInstance() { if(s==null) { synchronized (Single.class) { if (s==null) { s=new Single(); } } } return s; }
四、线程死锁问题
什么叫死锁:是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作
用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进锁。
示例:
package com.heima.thread; /** * 写一个死锁程序 * @author Administrator * */ class Test implements Runnable{ private boolean flag; Test(boolean flag) { this.flag=flag; } public void run() { if(flag) { synchronized (MyLock.aObject) { System.out.println("我是if aObject"); synchronized (MyLock.bObject) { System.out.println("我是if bObject"); } } } else { synchronized (MyLock.bObject) { System.out.println("我是else bObject"); synchronized (MyLock.aObject) { System.out.println("我是else aObject"); } } } } } class MyLock{ static Object aObject=new Object(); static Object bObject=new Object(); } public class DeadLock { public static void main(String[] args) { // TODO Auto-generated method stub Thread aThread=new Thread(new Test(true)); Thread bThread=new Thread(new Test(false)); aThread.start(); bThread.start(); } }
运行结果:
五、线程间通信
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1). wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2). notify():唤醒线程池中的一个线程(任何一个都有可能)。
3). notifyAll():唤醒线程池中的所有线程。
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法, 监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方式一定在object类中。
2、JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
下面是典型的生产者与消费者的问题
代码示例如下:
package com.heima.thread; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.swing.LookAndFeel; /** * 生产者与消费者问题 * @author Administrator * */ class Resource2{ private String name; private int count=1; private boolean flag=false; private Lock look=new ReentrantLock(); private Condition condition_p=look.newCondition(); private Condition condition_c=look.newCondition(); public void set(String name) throws Exception { look.lock(); try { while(flag) { condition_p.await(); } this.name=name+"------"+count++; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); flag=true; condition_c.signal(); } finally{ look.unlock(); } } public void out() throws Exception { look.lock(); try{ while(!flag) { condition_c.await(); } System.out.println(Thread.currentThread().getName()+"...消费者............"+this.name); flag=false; condition_p.signal(); } finally{ look.unlock(); } } } class Productor2 implements Runnable{ private Resource2 re; public Productor2(Resource2 re) { super(); this.re = re; } public void run() { while(true) { try { re.set("++烤鸭+++"); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer2 implements Runnable{ private Resource2 re; public Consumer2(Resource2 re) { super(); this.re = re; } public void run() { while(true) { try { re.out(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public class ProConDemo2 { public static void main(String[] args) { Resource2 r=new Resource2(); Productor2 pro=new Productor2(r); Consumer2 con=new Consumer2(r); Thread t1=new Thread(pro); Thread t3=new Thread(pro); Thread t2=new Thread(con); Thread t4=new Thread(con); t1.start(); t2.start(); t3.start(); t4.start(); } }
运行结果为:
相关文章推荐
- 黑马程序员——Java基础——单例设计模式及扩展
- 【剑指Offer学习】【面试题28 :字符串的排列】
- 【剑指Offer学习】【面试题27:二叉搜索树与双向链表】
- 【剑指Offer学习】【面试题26:复杂链表的复制】
- 摘录-IT企业必读的200个.NET面试题-05 常用类和接口
- 黑马程序员-java基础-三种实现线程的方式
- 黑马程序员------Java的多态性
- 黑马程序员-java基础-String类及String类的相关操作方法
- 黑马程序员------Java_IO关于缓冲区提高代码复用性
- 黑马程序员------IO流文件复制4种代码实现
- 黑马程序员------集合的遍历Iterator/ListIterator
- 程序员获取新编程技能的5个技巧?
- 程序员学会八大开发技巧 涨薪不是问题
- 黑马程序员------数组排序总结(Day12)
- 黑马程序员------判断语句概述
- 随笔:做一个平庸程序员,are you scared?
- 黑马程序员------数据类型与运算概述
- 程序员都应该熟练掌握最基本、最简单的“极小化程序调试法”
- 程序员的“纪律性”
- 黑马程序员------String概述