java(29) - 线程详解

一.线程概述:

      1).线程,是程序执行流的最小单元,线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那么就是程序本身。

         2).线程的状态:

              新建状态:一个新产生的线程从新状态开始了它的生命周期。它保持这个状态知道程序start这个线程。

              就绪状态:当一个新状态的线程被start以后,线程就变成可运行状态,一个线程在此状态下被认为是开始执行其任务。

              运行状态:当一个线程等待另外一个线程执行一个任务的时候,该线程就进入就绪状态。当另一个线程给就绪状态的线程发送信号时,该线程才重新切换到运行状态。

              阻塞状态:由于一个线程的时间片用完了,该线程从运行状态进入休眠状态。当时间间隔到期或者等待的时间发生了,该状态的线程切换到运行状态。

              死亡状态:一个运行状态的线程完成任务或者其他终止条件发生,该线程就切换到终止状态。

         3).在单个程序中同时运行多个线程完成不同的工作,称为多线程。多线程中带给我们很多的好处,但是又给我们带来了麻烦,就是同步问题和死锁。

         4).对于现在的单核CPU来说,某一个时刻只能有一个线程在执行(微观串行),从宏观角度来看多个线程在同时执行(宏观并行)。但是对于双核或双核以上的CPU来说,可以真正的做到微观并行。

                          

二.线程创建:

    1). 一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

    2).每一个java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序,java优先级MIN_PRIORITY(0)和MAX_PRIORITY(10)之间的范围,之间的范围内。默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5),不能依靠线程的优先级来决定线程的执行顺序。

    3).在java中实现多线程有两种方式,第一种直接继承Thread,第二种实现Runnable接口。

Thread类的重要方法:


1 public void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name)
改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority)
 更改线程的优先级。
5 public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
6 public final void join(long millisec)
等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt()
中断线程。
8 public final boolean isAlive()
测试线程是否处于活动状态。


(一).先使用第一种:

public class MyThread extends Thread {

    private String name;	

	public  MyThread(String name){
		
		this.name = name;
		
	}
	
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i<5;i++){
			
			System.out.println("MyThread "+this.name);
			
		}
	}

}

继承Thread类,实现其中的run()方法,run()方法中写入要执行的代码。

运行:

MyThread  mt = new MyThread("A");
MyThread  mt1 = new MyThread("B");
		
mt.start();
mt1.start();
注意我们要启动线程而不是直接使用run(),而是通过start()方法实现。
运行结果:

技术分享

从这里的结果我们可以看出来,执行的顺序,跟代码的顺序是不一样的,这就说明了线程的调用是不跟代码的中的顺序执行的。


    (二).使用第二种Runnable:


public class MyRunnable implements Runnable{

	private String name ;
	
	public MyRunnable(String name){
		
		this.name = name ;
	}
	
	public void run() {
		
		for(int i = 0 ; i<5;i++){
			
			System.out.println("MyRunnable "+this.name);
			
		}
		
	}

}

运行:

MyRunnable  mr = new MyRunnable("A");
MyRunnable  mr1 = new MyRunnable("B");
MyRunnable  mr2 = new MyRunnable("C");
		
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr1);
Thread t3 = new Thread(mr2);
		
t1.start();
t2.start();
t3.start();


运行结果:

技术分享


这两种实现的方式区别:当我们使用第一种方式来生成线程对象的时,我们需要重写run方法,因为Tread类中的run方法此时什么事情也没有做。而第二种方式来生成线程时,我们需要实现Runnable接口的run方法,然后使用new Tread(new MyRunnable())(假如MyTread已经实现了Runnable接口)来生成线程对象,这时的线程对象的run方法就会调用MyTread类的run方法,这样我们自己编写的run方法就执行了。


三.线程基本使用:

       1).线程的消亡不能通过调用一个stop()命令,而是要让run()方法自然结束。

       可以主动结束线程的例子:

public class MyThread implements Runnable{
    //设置标志
	private boolean  flag = true;
	@Override
	public void run() {
		while(flag){
			//。。。
		}
	}
	public void stopRunning(){
		this.flag = false;
	}
}
public class  ControlThread{
	private MyThread runnable = new MyThread();
	private Thread thread = new Thread(runnable);
   
	public void startThread(){
		thread.start();
	}
	public void stopThread(){
		runnable.stopRunning();
	}
}


2).局部变量和成员变量:

成员变量:

public class ThreadTest {
	public static void main(String[] args) {
		Runnable r = new NumberThread();
		
		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);
		
		t1.start();
		t2.start();
	}
}

class NumberThread implements Runnable{
    //成员变量
	int number ;
	
	@Override
	public void run() {
		//局部变量
        //int number = 0;
		while(true){
			
			System.out.println("number:"+(number++));
			
			try {
				//休眠1s
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(number > 10){
				 break;
			}
		}
		
	}
}

打印:

number:0
number:0
number:1
number:1
number:2
number:3
number:4
number:5
number:7
number:6
number:8
number:9
number:10

每次运行结果都会不同,但肯定少于20个数。因为多个线程对一个对象的成员变量进行操作的时,他们对该成员变量是互相影响的(也就是说一个线程对成员变量的改变会影响到另一个线程)。

局部变量:

public class ThreadTest {
	public static void main(String[] args) {
		Runnable r = new NumberThread();
		
		Thread t1 = new Thread(r);
		Thread t2 = new Thread(r);
		
		t1.start();
		t2.start();
	}
}

class NumberThread implements Runnable{
    //成员变量
    //int number ;
	
	@Override
	public void run() {
		//局部变量
        int number = 0;
		while(true){
			
			System.out.println("number:"+(number++));
			
			try {
				//休眠1s
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if(number > 10){
				 break;
			}
		}
		
	}
}

打印:

number:0
number:0


number:10
number:10

每个线程都会有一个该局部变量的拷贝,一个线程对该局部变量的改变不会影响到其他的线程。


四.多线程的同步:

    1).在多线程中,可能会有两个甚至多个的线程试图同时访问一个有限的资源,必须对这种潜在资源冲突进行预防。

通过例子来看一下(银行取钱):

public class FetchMoney {
	public static void main(String[] args) {
		
		Bank bank = new Bank();
		//柜台
		MoneyThread mt1 = new MoneyThread(bank);
		//ATM
		MoneyThread mt2 = new MoneyThread(bank);
		
		mt1.start();
		mt2.start();
	}
}
class Bank{
	//存款1000块
	private int money = 1000;
	
	public int getMoney(int number){
	
	    if(number < 0){
	    	return -1;
	    }else if(number > money){
	    	return -2;
	    }else{
          
	    	try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	money -= number;
	    	return number;
	    }
	
	}
}
class MoneyThread extends Thread{
	
	private Bank bank;
	
	public MoneyThread(Bank bank){
		this.bank = bank;
	}
	@Override
	public void run() {
		//取钱
		System.out.println(bank.getMoney(800));
	}
}

打印:800 800

为什么会这个结果呢,因为在第一个线程进入取款方法时进入睡眠状态未执行取钱操作,而第二个线程也进入了取钱方法,也进入了睡眠,那么第一个方法醒后就继续执行,就会取出800来,第二个醒后也继续执行,也取出800来,这就是线程同步的问题了。

如何解决这个问题: 在线程使用一个资源时为其加锁即可,访问资源的第一个线程为其加上锁后,其他线程则不能再使用那个资源,除非解锁。

实现同步:用synchronized关键字,当synchronized修饰一个方法时,该方法就是同步方法。

继续解决上面的例子:

public class FetchMoney {
	public static void main(String[] args) {
		
		Bank bank = new Bank();
		//柜台
		MoneyThread mt1 = new MoneyThread(bank);
		//ATM
		MoneyThread mt2 = new MoneyThread(bank);
		
		mt1.start();
		mt2.start();
	}
}
class Bank{
	//存款1000块
	private int money = 1000;
	
	public synchronized int getMoney(int number){
	
	    if(number < 0){
	    	return -1;
	    }else if(number > money){
	    	return -2;
	    }else{
          
	    	try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    	money -= number;
	    	return number;
	    }
	
	}
}
class MoneyThread extends Thread{
	
	private Bank bank;
	
	public MoneyThread(Bank bank){
		this.bank = bank;
	}
	@Override
	public void run() {
		//取钱
		System.out.println(bank.getMoney(800));
	}
}

打印:800  -2


2).java中的每个对象都有一个锁(lock)或者叫做监视器(monitor),当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行方法完毕后(或者抛出了异常),那么该对象的锁释放掉,其他线程才有可能再去访问该synchronized方法。

3).当一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法。

看例子:

public class ThreadTest2 {

	public static void main(String[] args) {
		CountClass cc = new CountClass();
		
		ThreadCount1 tc1 = new ThreadCount1(cc);
		ThreadCount2 tc2 = new ThreadCount2(cc);
		
		tc1.start();
		tc2.start();
	}
}
class CountClass {

	public synchronized void count() {
		for (int i = 0; i < 5; i++) {
			System.out.println("A:" + i);
		}
	}

	public synchronized void count2() {
		for (int i = 0; i < 5; i++) {
			System.out.println("B:" + i);
		}
	}
}

class ThreadCount1 extends Thread {

	private CountClass cc;

	public ThreadCount1(CountClass cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		cc.count();
	}
}
class ThreadCount2 extends Thread {

	private CountClass cc;

	public ThreadCount2(CountClass cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		cc.count2();
	}
}

打印:
A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4

上锁的是对象


4).如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的对象所对应的class对象,因为java中无论一个类有多少个对象,这些对象会对应唯一一个对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,他们的执行顺序也是顺序的,也就是说一个线程先执行方法,执行完毕后另一个线程在执行。

public class ThreadTest2 {
	public static void main(String[] args) {
		CountClass cc = new CountClass();
		
		ThreadCount1 tc1 = new ThreadCount1(cc);
		
		cc = new CountClass();
		
		ThreadCount2 tc2 = new ThreadCount2(cc);
		
		tc1.start();
		tc2.start();
	}
}
class CountClass {

	public synchronized static  void count() {
		for (int i = 0; i < 5; i++) {
			System.out.println("A:" + i);
		}
	}

	public synchronized static void count2() {
		for (int i = 0; i < 5; i++) {
			System.out.println("B:" + i);
		}
	}
}

class ThreadCount1 extends Thread {
    
	private CountClass cc;

	public ThreadCount1(CountClass cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		//因为是静态方法所以会有警告
		cc.count();
	}
}
class ThreadCount2 extends Thread {

	private CountClass cc;

	public ThreadCount2(CountClass cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		cc.count2();
	}
}

打印:

A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4


5).synchronized块,写法synchronized(obj){ ... }表示线程在执行的时候对obj上锁。当执行完毕后释放。

例子:

public class ThreadTest3 {

	public static void main(String[] args) {
		CountClass2 cc = new CountClass2();

		ThreadCount3 tc3 = new ThreadCount3(cc);
		ThreadCount4 tc4 = new ThreadCount4(cc);

		tc3.start();
		tc4.start();
	}

}

class CountClass2 {

	private Object object = new Object();

	public void count() {
		synchronized (object) {

			for (int i = 0; i < 5; i++) {
				System.out.println("A:" + i);
			}
		}
	}

	public void count2() {
		synchronized (object) {

			for (int i = 0; i < 5; i++) {
				System.out.println("B:" + i);
			}
		}
	}
}

class ThreadCount3 extends Thread {

	private CountClass2 cc;

	public ThreadCount3(CountClass2 cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		cc.count();
	}
}

class ThreadCount4 extends Thread {

	private CountClass2 cc;

	public ThreadCount4(CountClass2 cc) {
		this.cc = cc;
	}

	@Override
	public void run() {
		cc.count2();
	}
}

打印:

A:0
A:1
A:2
A:3
A:4
B:0
B:1
B:2
B:3
B:4

如果想实现跟synchronized方法一样的效果只需要将obj换成this表示当前的对象,synchronized方法是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;synchronized块则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内,synchronized块外的代码是可以被多个线程访问到的。



五.死锁(deadlock):

    所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

产生死锁的原因:
1.因为系统资源不足。
2.进程运行推进的顺序不合适。   
3.资源分配不当。
            
产生死锁的条件有四个:
1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

这时我们就要用到Object类中的方法:wait和notify、notifyall方法。

 void wait()
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
 void wait(long timeout)
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
 void wait(long timeout, int nanos)
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
 void notify()
          唤醒在此对象监视器上等待的单个线程。
 void notifyAll()
          唤醒在此对象监视器上等待的所有线程。

例子(交替显示1和0):

public class Sample
{
	private int number;

	public synchronized void increase()
	{
		while (0 != number)
		{
			try
			{
				wait();
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}

		number++;

		System.out.println("increase:"+number);

		notify();
	}

	public synchronized void decrease()
	{
		while (0 == number)
		{
			try
			{
				wait();
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}

		number--;

		System.out.println("decrease:"+number);

		notify();
	}
}
public class IncreaseThread extends Thread
{
	private Sample sample;
	
	public IncreaseThread(Sample sample)
	{
		this.sample = sample;
	}
	
	@Override
	public void run()
	{
		for(int i = 0; i < 5; i++)
		{
			try
			{
				Thread.sleep((long)(Math.random() * 1000));
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			
			sample.increase();
		}
	}
	
}
public class DecreaseThread extends Thread
{
private Sample sample;
	
	public DecreaseThread(Sample sample)
	{
		this.sample = sample;
	}
	
	@Override
	public void run()
	{
		for(int i = 0; i < 5; i++)
		{
			try
			{
				Thread.sleep((long)(Math.random() * 1000));
			}
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
			
			sample.decrease();
		}
	}
}
public class MainTest
{
	public static void main(String[] args)
	{
		Sample sample = new Sample();
		
		Thread t1 = new IncreaseThread(sample);
		Thread t2 = new DecreaseThread(sample);
		
		Thread t3 = new IncreaseThread(sample);
		Thread t4 = new DecreaseThread(sample);
		
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

打印:

increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0
increase:1
decrease:0


注意:wait和notify这两个方法要求在调用时线程已经获得了对象的锁,因此对这两个方法的调用需要放在synchronized方法中或块里,当线程执行了wait方式时,它会释放掉对象的锁。








 








郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。