Java基础——多线程
来源:互联网 发布:sqlserver怎么下载 编辑:程序博客网 时间:2024/05/22 14:11
进程与线程
进程就是程序的一次执行,而线程可以理解为进程中的执行的一段程序片段。每个进程都有独立的代码和数据空间(进程上下文);而线程可以看成是轻量级的进程。一般来讲(不使用特殊技术),同一进程所产生的线程共享同一块内存空间。
同一进程中的两段代码是不可能同时执行的,除非引入线程(实际也非同时执行,但表面上有同时执行的效果)。
多线程情况下,多个线程都获取CPU的执行权,CPU执行谁,就执行谁,在某个时间点,只有一个程序在运行(多核除外)CPU在做着快速的切换,已达到看上去是同事运行的效果。
进程:是一个正在运行的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;
线程:就是进程中执行的一个控制单元,线程控制着进程的执行。
一个进程至少有一个线程,JVM 启动的时候会有一个进程java.exe。该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码在在main方法中,该线程为主线程。
多线程创建
创建多线程,一般有两种方式,通过java.lang.Thread类或者通过Runnable接口。
继承Thread类
1、继承Thread类,并重新Thread类中的run方法
2、创建并启动一个线程,调用start方法(启动线程、调用run方法)
public class MyThread extends Thread{ @Override public void run() { super.run(); for (int i = 0; i < 5; i++) { System.out.println("线程:"+currentThread().getName()+" 执行 "+ i); } } /** * @param args */ public static void main(String[] args) { MyThread mt = new MyThread(); mt.start(); //开启线程。并执行该线程的run方法 //mt.run(); //仅仅对象调用放到,而创建线程了,并没有运行 }}
为什么要覆盖run方法?
Thread类用于描述线程
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法
Thread类中run方法,用于存储线程要运行的代码。
开启线程的目的,运行自己希望执行的操作,由于上述,所以需要复写run方法
实现接口Runnable
1、覆盖Runnable接口中run的方法
2、通过Thread类建立线程对象
3、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
4、调用Thread类的start方法,开启线程,实现Runnable接口子类的run方法
public class MyRunnable implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程:"+Thread.currentThread().getName()+" 执行 "+ i); } } /** * @param args */ public static void main(String[] args) { MyRunnable mt = new MyRunnable(); //实例化需要运行的对象(还没有创建方法)// Thread t = new Thread(mt); //实例化Runnable对象// t.start(); //启动线程 new Thread(mt).start(); //Thread(Runnable runnable) 对Runnable对象的实现 }}
继承、实现对比
一个比较经典的例子,一个买票系统,有3个售票窗口,一定量的票,分别以上述方式实现,如下:
继承方式;
public class MyTicket extends Thread{ private int count = 5; //一定量的票数 private String name; public MyTicket(String name){ this.name = name; } @Override public void run() { super.run(); while(count>0){ System.out.println(name+" 卖票 "+count--); } } public static void main(String[] args) {// MyTicket mt = new MyTicket("窗口1");// mt.start(); new MyTicket("窗口1").start(); new MyTicket("窗口2").start(); new MyTicket("窗口3").start(); }}
运行结果:
窗口2 卖票 5窗口1 卖票 5窗口1 卖票 4窗口3 卖票 5窗口1 卖票 3窗口2 卖票 4窗口1 卖票 2窗口3 卖票 4窗口1 卖票 1窗口2 卖票 3窗口2 卖票 2窗口2 卖票 1窗口3 卖票 3窗口3 卖票 2窗口3 卖票 1
实现接口Runnable方式:
public class MyTicket1 implements Runnable { private int count = 5; //票数 @Override public void run() { while(count>0){ System.out.println(Thread.currentThread().getName()+ " 卖票 "+this.count--); } } public static void main(String[] args) { MyTicket1 my = new MyTicket1(); new Thread(my, "1号窗口").start(); new Thread(my, "2号窗口").start(); new Thread(my, "3号窗口").start(); }}
执行结果:
1号窗口 卖票 5
1号窗口 卖票 3
1号窗口 卖票 2
1号窗口 卖票 1
2号窗口 卖票 4
实现接口Runnable比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
采用静态变量,定义一个共享资源,修改继承方式中变量的定义为:
private static int count = 5; //一定量的票数
对应运行结果:
窗口2 卖票 4窗口1 卖票 5窗口3 卖票 3窗口1 卖票 1窗口2 卖票 2
用static定义票数时,static变量存在于JVM方法区共享数据,程序共享资源,能够实现资源的共享。
在实际操作中总票数存储于中心数据库,各售票点分布于各地,资源读取的时候就会出现多个售票点同时查询票数,买票的情况,简单的定义一个静态变量不能达到要求,因此,需要用到同步处理机制。
线程安全
同样是卖票问题,每次线程执行后有2S的sleep时间,运行结果可能会出现异常:
public class MyTicket1 implements Runnable { private int count = 5; //票数 @Override public void run() { while(count>0){ try{Thread.sleep(2000);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+ " 卖票 "+this.count--); } } public static void main(String[] args) { MyTicket1 my = new MyTicket1(); new Thread(my, "1号窗口").start(); new Thread(my, "2号窗口").start(); new Thread(my, "3号窗口").start(); }}
运行结果:
2号窗口 卖票 5
1号窗口 卖票 3
3号窗口 卖票 4
2号窗口 卖票 2
1号窗口 卖票 1
3号窗口 卖票 0
2号窗口 卖票 -1
出现0,-1的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行一部分,还没有执行完。另一个线程参与进来执行,导致共享数据的错误。
解决原因:对多条操作共享数据的语句,只能让一个线程执行,执行过程中,只能执行一个线程,实现同步操作。
java对于多线程安全问题提供了保证代码同步的方式:同步代码块
synchronized(对象) //设定一个标志位(加锁){ //需要被同步的代码}
具体实现:
public class MyTicket3 implements Runnable{ private static int tick = 10; Object obj = new Object(); public void run() { for(int i=0;i<tick;i++){ synchronized(obj) //同步化 { if(tick>0) { try{Thread.sleep(4000);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+ " 卖票 "+this.tick--); } } } } /** * @param args */ public static void main(String[] args) { MyTicket3 my = new MyTicket3(); new Thread(my, "1号窗口").start(); new Thread(my, "2号窗口").start(); new Thread(my, "3号窗口").start(); }}
运行结果:
1号窗口 卖票 10
1号窗口 卖票 9
1号窗口 卖票 8
1号窗口 卖票 7
3号窗口 卖票 6
3号窗口 卖票 5
2号窗口 卖票 4
3号窗口 卖票 3
1号窗口 卖票 2
2号窗口 卖票 1
如何使用同步:
1、必须要有两个或两个以上的线程
2、必须是多个线程使用同一个锁
3、要确定加锁的范围(明确哪些代码是多线程运行代码,哪些共享数据,哪些代码调用共享数据的进行操作的)
一些比较经典的多线程同步问题,如生产者消费者问题,银行家问题等,都是采用同步的方式处理的。
PS:可能有错误,或不完善的地方,有待进一步研究与学习。
- java基础——多线程
- Java基础——多线程
- Java基础——多线程
- Java基础——多线程
- JAVA基础——多线程
- java基础——多线程
- Java基础——多线程
- Java基础——多线程
- java基础——多线程
- java基础——多线程
- java基础——多线程
- 【Java基础】——多线程
- Java基础——多线程
- JAVA基础——多线程
- java基础——多线程
- Java基础——多线程
- Java基础——多线程
- Java基础—续多线程
- 几个有意思的算法题
- 【项目那些事儿】项目常总结2013.11.5
- Ruby bundle
- 让Entity Framework支持MySql数据库
- ASP.NET MVC3细嚼慢咽---(2)模板页
- Java基础——多线程
- Codeforces 52C - Circular RMQ - 线段树
- Entity Framework快速入门--实例篇 DatabaseFirst
- 算法思路重新实现-堆排序 中的 C++ & Java
- java.lang.Thread
- tomcat部署web项目的3中方法
- 多媒体技术实验--BMP图像的直方图均衡化
- 父类与子类之间的转换
- LeetCode题解:Minimum Depth of Binary Tree