Java多线程 快速入门(二)

这一讲将会涉及线程的生命周期、线程的同步等问题,都是在实际开发中经常要用到的知识 ;

1:Java线程的生命周期

· 创建状态 ;
· 可运行状态 ;
· 不可运行状态(比如经常说的”线程阻塞“了) ;
· 消亡状态 ;
状态相互转换,以下是图示


简单说下这些转换关系

· 一个线程类被new出实例之后,仅仅是个对象,其他任何事情都没发生 ;

· 执行start( )之后系统会为线程准备好资源,但是线程并没有运行(理解为一切就绪)

· 线程Running啦,也就是说run( )方法中定义的东西被执行了,这就是运行状态

Runnable和Running状态是可以相互转换的,简单的理解就是对CPU使用和释放,线程准备好了(Runnable状态),获取到CPU之后自然进入到Running状态,但是就这样一直Running下去么?

如果是这样,那么其他的线程(或者是进程)还能够拿到CPU资源吗?所以,OS不会让某一个线程一直长时间独霸CPU,运行一段时间即使没有出现阻塞和消亡,也要回到Runnable状态(可运行状态),让出CPU让别的线程有机会执行,否则其他的线程也就是憋死了...


以上还有一个线程的状态没有提到,就是Blocked状态,一般被称为线程阻塞了,导致线程阻塞的事件大概有以下几点

· 调用了sleep( )方法 ;

· 调用了wait方法 ;

· 线程发生了IO阻塞 ;

当发生这些事情的时候,线程不在执行,还是处于Blocked中,直到这些事情消失比如“睡醒了、叫醒了、读写完了“等等, 之后又会回到Runnable状态 。最后自然是Dead状态,当线程执行完毕,一般理解为线程中的run( )方法 跑完了,该线程也就是消亡了 ;


2:sleep( )与yield( )

这两个方法都能让线程处于”非常执行状态“但是又有所不同 ;
sleep( )使当前线程进入Blocked状态,执行sleep( )的线程在指定的时间内会被阻塞住 ;
yield( )只是使当前线程重新回到Runnable状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

同时需要了解的是,sleep( )可使优先级低的线程得到执行的机会,当然也可以让同优先级和高优先级的线程有执行的机会, 然而yield( )只能使同优先级的线程有执行的机会。

3:线程中局部变量与成员变量

如果一个变量是局部变量,那么每个线程都会有一个该局部变量的拷贝,
一个线程对该局部变量的改变不会影响到其他的线程 ;
public class ThreadVariate {
	public static void main(String[] args) {
		
		Runnable r = new VariateThread() ;
		Thread t1 = new Thread(r) ;
		Thread t2 = new Thread(r) ;
		
		t1.start(); 
		t2.start();
		
	}
}

class VariateThread implements Runnable {
	
	//int i ; 分别用成员变量和局部变量做实验
	
	@Override
	public void run() {
		int i  = 0 ;
		while(true) {
			System.out.println("number : " + i++) ;
			
			try {
				Thread.sleep((long)(Math.random() * 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			if(i == 50) {
				break ;
			}
			
		}		
	}
}

4:多线程的同步问题 synchronized 关键字

在多线程环境中,可能会有两个甚至更多的线程试图同时访问一个有限的资源。必须对这种潜在资源冲突进行预防。
解决的办法,在线程使用一个资源时为其加锁即可。访问资源的第一个线程为其加上锁以后,其他线程便不能再使用那个资源,除非被解锁。

注意是线程为资源加上锁,有的时候也称为线程获取到了资源的锁 ;

先举一个被说烂的例子,“银行提款机”,两个线程同时取出800元,总额只有1000元,对比的是在getMoney( )这个方法前加上或者取消掉synchronized 关键字的修饰 。

public class BankMoney {
	public static void main(String[] args) {
		Bank bank = new Bank() ;
		
		Thread t1 = new MoneyThread(bank) ;
		Thread t2 = new MoneyThread(bank) ;
		
		t1.start(); 
		t2.start();
		
	}
	
}

class Bank {
	private int money = 1000 ;
	
	public synchronized int getMoney (int number) {
		
		if(number < 0) {
			return -1 ;
		} else if(number > money) {
			return -2 ;
		} else if(money < 0) {
			return -3 ;
		} else {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace() ; 
			}
			
			money -= number ;
			System.out.println("left money: " + money);
			return number ;
		}
		
	}
	
}

class MoneyThread extends Thread {
	private Bank bank ;
	
	public MoneyThread (Bank bank) {
		this.bank = bank ;
	}
	
	@Override
	public void run() {
		
		int tmp = bank.getMoney(800) ;
		if(tmp != 800) {
			System.out.println("你的操作失误");
		} else {
			System.out.println("你取出了 " + tmp + " 元");
		}
	}
	
}
Java 中的每一个对象都有一个moniter(监视器),也被称之为Lock(锁),当线程访问一个对象中被synchronized关键字修饰的方式时,表示为将该对象上锁,此时其他任何线程都无法再去访问该synchronized方法了,直到之前的那个线程执行完方法,或是是出现了异常,那么该对象的锁将被释放掉,其他线程才有可能再去方法该synchronized方法 ;


现在需要探讨的是多个方法被synchronized修饰的情况了 

public class MultiSynchronized {
	public static void main(String[] args) {
		
		Example example = new Example() ;
		Thread t1 = new Thread1(example) ;	
		Thread t2 = new Thread2(example) ;
		
		t1.start(); 
		t2.start();
	}
	
}

class Example {
	
	public synchronized void execute1() {
		for(int i = 0; i < 20; i++) {
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("Hello " + i) ;
		}
	}
	
	public synchronized void execute2() {
		for(int i = 0; i < 20; i++) {
			
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			System.out.println("world " + i);
		}
	}
	
}

class Thread1 extends Thread {
	private Example example ;
	
	public Thread1(Example example) {
		this.example = example ;
	}
	
	@Override
	public void run() {
		example.execute1();
	}
	
}

class Thread2 extends Thread {
	private Example example ;
	
	public Thread2(Example example) {
		this.example = example ;
	}
	
	@Override
	public void run() {
		example.execute2();
	}
	
}
Java对象中有多个方法,假设这些方法都被synchronized修饰,当某个线程访问这个对象的某一个被synchronized修饰的方法时,首先该对象将被锁住(也就是说该线程获得了这个对象的锁),这时该对象的所有被synchronized修饰方法都将被“锁住”,也就是说其他的线程都不能访问这些方法,除非是最先执行方法的线程执行完毕(或者是出现异常),这时线程释放掉对象的锁,其他的线程就都可以方法该对象,执行该对象的方法了 ;


synchronized 关键字修饰的方法,如果加上了static 修饰,将别样的效果。

这里首先要明确一个概念,任何被static修饰的方法(或者属性)都并不属于该对象,而生属于该对象的”类对象“(也就是class对象,一个类,无论有多少个实例,都只有一个class对象),所以这个时候synchronized修饰的是class对象中的方法 。

举例来说,如果一个对象中有两个方法,两个方法都同时被synchronized和static修饰着,这时两个线程启动,同时访问同一个对象的这两个方法,会发生什么呢?必然是顺序执行,因为这时这两个方法都是Class对象的方法,而Class对象被锁定了。-



PS:Synchronized 关键字修饰代码块


public class ThreadTest5
{
	public static void main(String[] args)
	{
		Example2 e = new Example2();

		TheThread3 t1 = new TheThread3(e);
		
		e = new Example2();
		
		TheThread4 t2 = new TheThread4(e);

		t1.start();
		t2.start();
	}
}

class Example2
{
	private Object object = new Object();

	public void execute()
	{
		synchronized (this)
		{
			for (int i = 0; i < 20; i++)
			{
				try
				{
					Thread.sleep((long) (Math.random() * 1000));
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}

				System.out.println("hello: " + i);
			}
		}

	}

	public void execute2()
	{
		synchronized(this)
		{
			for (int i = 0; i < 20; i++)
			{
				try
				{
					Thread.sleep((long) (Math.random() * 1000));
				}
				catch (InterruptedException e)
				{
					e.printStackTrace();
				}

				System.out.println("world: " + i);
			}
		}
		
		
	}
}

class TheThread3 extends Thread
{
	private Example2 example;

	public TheThread3(Example2 example)
	{
		this.example = example;
	}

	@Override
	public void run()
	{
		this.example.execute();
	}
}

class TheThread4 extends Thread
{
	private Example2 example;

	public TheThread4(Example2 example)
	{
		this.example = example;
	}

	@Override
	public void run()
	{
		this.example.execute2();
	}
}
如何理解”Synchronized 代码块“? new出来的object 对象是一个无实际意义的对象,线程访问点一个代码块的时候,会去锁定object对象(获取object的锁),另一个线程访问第二个代码块,这个线程也要去获得object对象的锁(锁定object),但是这个线程发现object已经被锁定了,那么这个线程只能继续等待object对象的锁被释放掉(也就是之前线程的run( )方法执行完毕)


未完待续 ...
























  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值