这一讲将会涉及线程的生命周期、线程的同步等问题,都是在实际开发中经常要用到的知识 ;
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( )方法执行完毕)
未完待续 ...