Java 多线程编程
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行 不同的任务。
多线程是多任务的一种特别的形势,但多线程用了更小的资源开销。
线程的四个状态
1 新建状态: 使用new关键字和thread类或其子类建立一个线程对象后,该线程对象就处于新建状态,保持这个状态 直到启动这个线程。
2 就绪状态: 当线程对象调用了start();方法之后,该线程就处于就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM里线程调度器的调度。
3 运行状态: 执行run()方法后此线程就处于运行状态。处于运行状态的线程可以变为 阻塞状态、就绪状态、死亡状 态。
4 阻塞状态:当线程执行了sleep() 、suspend()等方法,失去其占用的资源,就会从运行转变为阻塞状态,在睡眠时 间已过或重新获得设备资源后可重新进入就绪状态。
等待阻塞:运行中执行wait()方法
同步阻塞:同步锁失败
其它阻塞: 通过调用线程的sleep、jojin 发出了i/o的请求时,线程就会进入阻塞状态当sleep 状态超时 join等待线 程终止或超时 或者i/o处理完毕,线程重新转入就绪状态。
了解
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
创建线程的三个方法
通过实现Runnable接口
通过继承Thread类本身
通过Callable 和Future创建线程
1 通过实现Runnable接口
只需调用run实现
public void run()
例如
public class RunnableT extends Thread{
private int ticket = 5 ; // 表示一共有5张票
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
@Test
public void RunnableTest(){
RunnableT mt1 = new RunnableT();//实例化
RunnableT mt2 = new RunnableT();
mt1.run() ; // 调用线程主体
mt2.run() ; // 调用线程主体
};
}
2 通过继承Thread类本身
public class MyThread implements Runnable {
private int ticket = 5 ; // 表示一共有5张票
public void run(){ // 覆写run()方法,作为线程 的操作主体
for(int i=0;i<100;i++){
if(this.ticket>0){
System.out.println("卖票:ticket = " + ticket--) ;
}
}
}
}
class RunnableDemo0{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化对象
new Thread(mt).run() ; // 调用线程主体
new Thread(mt).run() ; // 调用线程主体
new Thread(mt).run() ; // 调用线程主体
}
};
Runnable相较于thread的优点:
继承Thread: 线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。
1.适合多个相同代码的线程去处理同一个资源的情况
2.可以避免由于java的单继承特性带来的局限
3.增强了程序的健壮性,代码能够被多个线程共享,代码与数据时独立的
3 通过Callable 和Future创建线程
public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}
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()
测试线程是否处于活动状态。
死锁
所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
产生死锁的必要条件:
-
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
-
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
-
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
-
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。
解决死锁的方法:
预防:
1.资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
2.只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
3.可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
4.资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
检测死锁:
- 首先为每个进程和每个资源指定一个唯一的号码;
- 然后建立资源分配表和进程等待表。
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:
1.剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
2.撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。