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