现在的位置: 首页 > 综合 > 正文

黑马程序员– Java多线程编程总结

2014年09月05日 ⁄ 综合 ⁄ 共 7759字 ⁄ 字号 评论关闭
文章目录

 -------
android培训
java培训、期待与您交流! ----------

Java线程:概念与原理

    

一、操作系统中线程和进程的概念

  进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

 
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

二、Java中的线程

在Java中,“线程”指两件不同的事情:
1、java.lang.Thread类的一个实例;
2、线程的执行。
 
使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
 
一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
 
Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
 
一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
 
一旦创建一个新的线程,就产生一个新的调用栈。
 
当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。
   1) 程序 挃令 + 数据的byte序列,如: qq.exe
   2) 进程 正在运行的程序, 是程序劢态的执行过程(运行于内存中)
   3 ) 线程 在迚程内部,并収运程的过程(Java中的方法可以看做线程)
  4) 并发 迚程是并収运行的,OS将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,微观上迚程走走停停,宏观上都在运行,这种都运行的现象叫并収,但是不是绝对意义上的“同时収生

 java线程:创建与启动

一,  定义线程

 
1、Thread类。
 
线程类(Thread)包含一个可以运行的过程(方法):run()方法
public void run();
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 
Thread 的子类应该重写该方法。
2、实现Runnable接口。
 void run()
使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run 方法。
方法 run 的常规协定是,它可能执行任何所需的操作。
3。使用内部类/匿名类创建线程
package bbs.itheima.com;

public class ThreadIntDemo {

	/**
	 * 线程的创建方法
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
       /**使用匿名内部类创建线程*/
		Thread t1 = new Thread(){//继承Thread类
			public void run(){
				System.out.println("匿名内部类");
			}
		};
		t1.start();
		
		//使用runnable 接口创建线程
		//实现Runnable接口
		Runnable runner = new Runnable(){

			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("实现Runnable接口");
			}
			
		};
		//在创建线程的时候,将Runnerble实例作为构造参数
		Thread t2 = new Thread(runner);
		t2.start();
		
		//使用Runnable接口创建匿名类,创建线程实例
		Thread t3 = new Thread(new Runnable(){
			public void run(){
				System.out.println("Hi t3");
			}
		});
		t3.start();
		
		//创建匿名内部类,直接启动线程
		new Thread(){
			public void run(){
				System.out.println("HI Thread");
			}
		};
		//创建匿名类实例,使用Runnable接口
		new Thread(new Runnable(){
			public void run(){
				System.out.println("HI, Runnable");
			}
		}).start();
	}

}

 

二、实例化线程

 
1、如果是扩展java.lang.Thread类的线程,则直接new即可。
 
2、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法:
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(ThreadGroup group, Runnable target)
Thread(ThreadGroup group, Runnable target, String name)
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

 三、启动线程

 

在线程的Thread对象上调用start()方法,而不是run()或者别的方法。
 
在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。
 
在调用start()方法之后:发生了一系列复杂的事情
启动新的执行线程(具有新的调用栈);
该线程从新状态转移到可运行状态;
当该线程获得机会执行时,其目标run()方法将运行。
 
注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。
案例演示:
  
package bbs.itheima.com;

public class ThreadDemo {
   /**基本基本线程演示*/
	public static void main(String[] args) {
		// TODO Auto-generated method stub
      Person p = new Person();//p线程实例
      Person2 p2 = new Person2();//p2线程实例
      p.start();
      p2.start();
      System.out.println("over");
	}

}
class Person extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("我是谁?");
		}
	}
}
class Person2 extends Thread{
	public void run(){
		for(int i = 0;i<5;i++){
			System.out.println("张三");
		}
	}
}

线程的状态及其管理

一,线程的状态

线程的5中状态
1) New 新建状态
当程序使用new关键字创建了一个线程后,该线程就处于新建状态,此时线程还未启动,当线程对象调用start()方法时,线程启劢,迚入Runnable状态
2) Runnable 可运行(就绪)状态
 当线程处于Runnable状态时,表示线程准备就绪,等待获取CPU
3) Running 运行(正在运行)状态
  假如该线程获叏了CPU,则迚入Running状态,开始执行线程体,即run()方法中的内容
  注意:
如果系统只有1个CPU,那么在任意时间点则只有1条线程处于Running状态; 如果是双核系统,那么同一时间点会有2条线程处于Running状态

但是,当线程数大于处理器数时,依然会是多条线程在同一个CPU上轮换执行
 当一条线程开始运行时,如果它不是一瞬间完成,那么它丌可能一直处于Running状态,
线程在执行过程中会被中断,目的是让其它线程获得执行的机会,像这样线程调度的策 略叏决于底层平台。对于抢占式策略的平台而言,系统系统会给每个可执行的线程一小 段时间来处理任务,当该时间段(时间片)用完,系统会剥夺该线程所占资源(CPU), 让其他线程获得运行机会。
 调用yield()方法,可以使线程由Running状态迚入Runnable状态
4) Block 阻塞(挂起)状状态
 当如下情况下,线程会迚入阻塞状态:
线程调用了sleep()方法主劢放弃所占CPU资源
 线程调用了一个阻塞式IO方法(比如控制台输入方法),在该方法返回前,该线程被阻塞
 ......
当正在执行的线程被阻塞时,其它线程就获得执行机会了。需要注意的是,当阻塞结束时,该线程将迚入Runnable状态,而非直接迚入Running状态
5) Dead 死亡状态
 当线程的run()方法执行结束,线程迚入Dead状态 需要注意的是,不要试图对一个已经死亡的线程调用start()方法,线程死亡后将能再次作为线程执行,系统会抛出IllegalThreadStateException异常

 

注:
1) new运算创建线程后,线程迚入New状态(初始状态)
2) 调用 start()方法后,线程从New状态迚入Runnable状态(就绪状态)
 start()方法是在main()方法(Running状态)中调用的
3) 线程结束后,迚入Dead状态(死亡状态),被对象垃圾回收
4) main()方法结束后,其它线程,比如上例中p1和p2开始抢着迚入Running状态
由谁抢到是底层操作系统决定(操作系统分配时间片)
 单核处理器:在一个时间点上只有一个线程在Running状态;双核处理器:2个
如果p1迚入Running状态,当操作系统分配给它的时间片到期时,p1迚入Runnable状态,p2迚入Running状态
 在期间有可能其它的迚程的线程获得时间片,那么p1和p2同时迚入Runnable状态,等待操作系统分配时间片
5) 线程迚入Dead状态后,只能被垃圾回收,丌能再开始
6) 如果线程在运行过程中,自己调用了yield()方法,则主劢由Running状态迚入Runnable状态

二,线程状态管理

1) 让出CPU Thread.yield()
当前线程让出处理器(离开Running状态),使当前线程迚入Runnable状态等待
2) 休眠 Thread.sleep(times)
  使当前线程从 Running 放弃处理器迚入Block状态, 休眠times毫秒, 再返回到Runnable 如果其他线程打断当前线程的Block(sleep),   就   会収生InterruptedException。
   Thread.yield()方法演示
package bbs.itheima.com;

public class ThreadDemo {
   /**基本基本线程演示*/
	public static void main(String[] args) {
		// TODO Auto-generated method stub
      Person p = new Person();//p线程实例
      Person2 p2 = new Person2();//p2线程实例
      p.start();
      p2.start();
      System.out.println("over");
	}

}
class Person extends Thread{
	public void run(){
		for(int i = 0;i<155;i++){
			System.out.println("我是谁?");
			Thread.yield();
		}
	}
}
class Person2 extends Thread{
	public void run(){
		for(int i = 0;i<155;i++){
			System.out.println("张三");
			Thread.yield();
		}
	}
}

线程的常用属性及方法

1) 线程的优先级 (资源紧张时候, 尽可能优先)

 t3.setPriority(Thread.MAX_PRIORITY); 设置为最高优先级

 默认有10 优先级, 优先级高的线程获得执行(迚入Running状态)的机会多. 机会的多少丌能通过代码干预

 默认的优先级是 5

2) 后台线程(守护线程,精灵线程)

 t1.setDaemon(true);

 Java迚程的结束:当前所有前台线程都结束时, Java迚程结束

 当前台线程结束时, 丌管后台线程是否结束, 都要被停掉!

3) 获得线程名字

getName()

4) 获得当前线程

Thread main = Thread.currentThread();

Sleep状态的打断唤醒

 1) Thread.sleep(times) 使当前线程从 Running状态放弃处理器,迚入Block状态, 休眠times(单位为毫秒), 休眠结束后,再返回到Runnable状态 2) interrupt() 方法打断/中断 使用该方法可以让一个线程提前唤醒另外一个sleep Block的线程 3) 被唤醒线程会出现中断异常

  

package bbs.itheima.com;

public class SleepDemo {

	/**
	 * Sleep(休眠)演示
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Thread t = new Thread(){
			public void run(){//覆盖run()方法
				long start = System.currentTimeMillis();
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				long end = System.currentTimeMillis();
				System.out.println("线程t休眠了"+(end-start));
				System.out.println("线程t结束了");
			}
		};
      t.start();
      System.out.println("main结束");
	}

}


main结束
线程t休眠了1000
线程t结束了


线程的同步与锁

一、同步问题提出

线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。
public class Foo {
    private int x = 50;

    public int getX() {
        return x;
    }

    public int fix(int y) {
        x = x - y;
        return x;
    }
}

public class MyRunnable implements Runnable {
    private Foo foo = new Foo();

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread ta = new Thread(r, "Thread-A");
        Thread tb = new Thread(r, "Thread-B");
        ta.start();
        tb.start();
    }

    public void run() {
        for (int i = 0; i < 3; i++) {
            this.fix(30);
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " : 当前foo对象的x值= " + foo.getX());
        }
    }

    public int fix(int y) {
        return foo.fix(y);
    }
}
 
Thread-B : 当前foo对象的x值= -10
Thread-B : 当前foo对象的x值= -40
Thread-A : 当前foo对象的x值= -70
Thread-B : 当前foo对象的x值= -70
Thread-A : 当前foo对象的x值= -100
Thread-A : 当前foo对象的x值= -130

从结果发现,这样的输出值明显是不合理的。原因是两个线程不加控制的访问Foo对象并修改其数据所致。
 如果要保持结果的合理性,只需要达到一个目的,就是将对Foo的访问加以限制,每次只能有一个线程在访问。这样就能保证Foo对象中数据的合理性了。
 在具体的Java代码中需要完成一下两个操作:
把竞争访问的资源类Foo变量x标识为private;

同步哪些修改变量的代码,使用synchronized关键字同步方法或代码

二、同步和锁定

        1、锁的原理

 Java中每个对象都有一个内置锁
 当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this实例)有关的锁。获得一个对象的锁也称为获取锁、锁定对象、在对象上锁定或在对象上同步。
 当程序运行到synchronized同步方法或代码块时才该对象锁才起作用。
 一个对象只有一个锁。所以,如果一个线程获得该锁,就没有其他线程可以获得锁,直到第一个线程释放(或返回)锁。这也意味着任何其他线程都不能进入该对象上的synchronized方法或代码块,直到该锁被释放。释放锁是指持锁线程退出了synchronized同步方法或代码块。
  于锁和同步,有一下几个要点:

1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。

6)、线程睡眠时,它所持的任何锁都不会释放。

7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁。例如:
    public int fix(int y) {
        synchronized (this) {
            x = x - y;
        }
        return x;
    }
 
当然,同步方法也可以改写为非同步方法,但功能完全一样的,例如:
    public synchronized int getX() {
        return x++;
    }
    public int getX() {
        synchronized (this) {
            return x;
        }
    }
效果是完全一样的。

 

 

 

抱歉!评论已关闭.