Java 多线程
在Java中,线程类Thread创建方式有两种:一是继承Thread类,重写run方法;二是,实现Runnable接口。大多数情况下,推荐使用第二种方式,实现runnable接口,这样可以很好的将任务与执行单元分离,更加突出面向对象的思想。
在JDK1.5之前,线程间互斥主依靠内置锁(监视器),而线程间通信则采用Object实例的wait,notify等方法。在JDK1.5之后,增加了很多线程新技术,如线程池、锁、信号量、条件、栅栏、阻塞队列、同步容器等。
在Java中,提供了内置锁(监视器)的概念。每一个对象可以看作是与一把锁关联,在进入临界区前需要获取与对象所关联的锁,在离开临界区释放对象关联的锁。
Java提供了synchronized关键字来方便的获取和释放对象所关联的锁。在Java中synchronized关键字可以修饰方法或代码块,被synchronized关键字修饰的方法或代码块会在进入作用域前获取相应的锁,在离开作用域后释放相应的锁,如下
public class Ticket
{
private int tickets=10;
public void sale()
{
synchronized (this)
{
if (tickets>0)
{
tickets--;
}
}
}
}
通过上面的代码,我们知道使用synchronized代码块需要显示指定那个对象的关联锁。synchronized关键字既可以修饰实例方法,也可以修饰类方法,那么synchronized修饰实例方法获取的是那个对象的关联锁,而修饰类方法又获取的是那个对象的关联锁,如下
public class Factory
{
private static int count=10;
public synchronized void consume()
{
if (count>0)
{
count--;
}
}
public synchronized static void show()
{
System.out.println("count="+count);
}
}
synchronized修饰实例方法时,获取的是该实例所关联的锁(对象锁);而修饰类方法其实获得是该类对应的Class对象的锁(类锁)。由于对象锁和类锁并不是一把锁,所以默认采用synchronized来修饰实例方法和类方法,这些方法是达不到互斥作用的,上面的互斥代码是有问题的。若想实例方法和类方法达到互斥效果,需要采用synchronized代码块,显示指定获取那个对象的关联锁(如同一把类锁,或同一个对象的关联锁),如下
public class Factory
{
private static int count=10;
private static final Object lock=new Object();
public void consume()
{
synchronized (lock)
{
if (count>0)
{
count--;
}
}
}
public static void show()
{
synchronized (lock)
{
System.out.println("count="+count);
}
}
}
synchronized关键字可以使线程间互斥,而线程间的通信需要使用Object实例的wait、notify等方法,但前提是不同的线程必须获取同一个实例的监视器,也就是说wait,notify方法必须在互斥块内使用,而且不同的线程所要获取的监视器是同一个监视器。
wait方法可以使一个线程等待,并释放该线程获得的监视器,直到被另一个持有同一个监视器的线程所唤醒;当条件满足时,需要在其他线程中使用notify或notifyAll方法来唤醒等待的线程,被唤醒的线程可以重新获取监视器对象。
在上面我们强调了不同线程间通信的前提是,这些线程所持有或申请持有的是同一个监视器。为了便于不同线程间可以获取同一个监视器,也为了任务和执行单元分离,在涉及多线程的编程时往往将要执行的一组任务封装到一个类中,如下(子线程先打印10句话,主线程接着打印100句;子线程再打印10句,主线程也接着打印100句;如此顺序子线程、主线程个执行50次):
public class Service
{
private boolean isSubRunable=true;
public void sub() throwsInterruptedException
{
synchronized (this)
{
while (!isSubRunable)
{
this.wait();
}
for (int i = 0; i < 10; i++)
{
System.out.println("sub loopof "+i);
}
isSubRunable=false;
this.notifyAll();
}
}
public void main() throwsInterruptedException
{
synchronized (this)
{
while(isSubRunable)
{
this.wait();
}
for (int i = 0; i < 100; i++)
{
System.out.println("main loopof "+i);
}
isSubRunable=true;
this.notifyAll();
}
}
public static void main(String[] args) throws InterruptedException
{
final Service service=new Service();
new Thread(new Runnable()
{
@Override
public void run()
{
for (int i = 0; i <50; i++)
{
try
{
service.sub();
}
catch (InterruptedException e)
{
}
}
}
}).start();
for (int i = 0; i < 50; i++)
{
service.main();
}
}
}
注意:
l wait方法,使线程等待时可以释放获得的监视器;
l sleep方法,也可以使线程等待一段时间后再执行,但是在等待期间并不会释放锁获得的监视器;
l join:一直等待该线程执行完毕才去执行其他线程,也即调用join方法的线程获得CPU控制权,直到该线程执行完毕;在实际应用中,可以使一个线程等待另一个线程执行完毕,才开始执行;
l yield:该方法指明该线程主要的任务已执行完毕,可以让出当次CPU控制权(让出CPU控制权后立即变为就绪态,参与下次CPU控制权的竞争),使其他同优先级的线程获得执行机会;注意yield并不会释放锁。
当某些条件不满足时,我们需要使线程等待,直到这些条件得到满足。在进行条件判断时,为什么要使用循环语句,而不是if语句呢?
由于一些中断或唤醒会提前发生,所以在判断条件满足时需要采用循环语句,以防止提前唤醒。
在大多数情况下,我们不用显示终止线程,而是等待线程执行完毕。但是在与用户交互的应用中或出现一些其他突发状况,需要终止线程的执行,如用户点击了取消按钮,JVM马上要关闭等。
出现上述情况,该如何安全的终止线程,是立即终止,还是缓慢的终止(等待已提交的任务执行完毕,而不接受新的任务)?
一个线程是一条独立的执行路径,要终止线程的执行,正确的做法是线程自己内部终止本线程的运行。
interrupt方法并不是中断一个线程的执行,而是将线程的中断标志设置为true,也即isInterrupted=true;相当于只是打了一个标记,告诉线程要中断了。但是,具体的中断操作是需要线程内部来实现的。
注意:调用interrupt方法将中断标志设置为true后,若调用该线程的sleep、wait、join等方法,将会清除中断标志位,并抛出InterruptedException。由于interrupt方法意味着将要中断线程,而sleep、wait、join等方法与interrupt方法是冲突的,并不能一起执行,所以会将中断标志清除,并抛出InterruptedException异常。
采用中断标志为来终止线程的执行,如下
public static void main(String[] args)
{
Thread thread= new Thread(new Runnable()
{
@Override
public void run()
{
Thread currentThread=Thread.currentThread();
while (!currentThread.isInterrupted())
{
System.out.println(currentThread.getName()+" is running....");
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
//需要再次调用interrupt
currentThread.interrupt();
e.printStackTrace();
}
}
}
});
//开启线程
thread.start();
for (int i = 0; i < 20; i++)
{
System.out.println("main thread is run loop of "+i);
try
{
if (i==10)
{
thread.interrupt();
}
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
在实际应用中,往往采用自定义标志位来终止线程的执行,如下
public class Test
{
public static void main(String[] args)
{
Task task=new Task();
//开启线程
new Thread(task).start();
for (int i = 0; i < 20; i++)
{
System.out.println("main thread is run loop of "+i);
try
{
if (i==10)
{
task.setStoped(true);
}
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
class Task implements Runnable
{
privatevolatilebooleanisStoped=false;
@Override
public void run()
{
while (!isStoped)
{
System.out.println("task isrunning.......");
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public boolean isStoped()
{
return isStoped;
}
public void setStoped(boolean isStoped)
{
this.isStoped = isStoped;
}
}
若一些数据只在一个线程内共享,并不会被其他线程访问,可以采用线程封闭技术,将这些数据封闭在具体的执行线程中。在Java中,线程封闭技术采用ThreadLocal技术来实现,我们可以将只在一个线程内共享的数据放入ThreadLocal中,这样在获取数据时只会获取与本线程关联的数据,而不会获取其他线程的数据,这样可以避免线程同步的性能开销,如下
public class UserService
{
private ThreadLocal<Connection> threadLocal=new ThreadLocal<UserService.Connection>(){
protected Connection initialValue()
{
return new Connection();
};
};
public Connection getCurrentConnection()
{
return threadLocal.get();
}
static class Connection
{
}
}
在Java中线程分为用户线程和守护线程。只要有用户线程正在运行,JVM就不会退出;而守护线程是依附用户线程存在的,如负责垃圾收集的线程就是典型的守护线程,但所有用户线程执行完毕,负责垃圾收集的线程也没有存在的必要。
在Java中,默认开启的线程都为用户线程,若要使某个线程称为守护线程,可以在线程启动前,调用setDaemon方法。
版权声明:本文为博主原创文章,未经博主允许不得转载。
原文地址:http://blog.csdn.net/sunshuolei/article/details/47781613