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:可能有错误,或不完善的地方,有待进一步研究与学习。

原创粉丝点击