在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runnable接口.
一、扩展java.lang.Thread类
这里继承Thread类的方法是比较常用的一种,如果说你只是想起一条线程。没有什么其它特殊的要求,那么可以使用Thread.(推荐使用Runnable,后头会说明为什么)。下面来看一个简单的实例
Thread类代表线程类,主要有两个方法:
- run()——包含线程运行时所执行的代码
- start()——用于启动线程
public class Machine extends Thread {
private int i=0;
public void run(){
for( i=0;i<10;i++)
{
System.out.println(currentThread().getName()+":"+i);
try{
sleep(100);
}catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
Machine machine1=new Machine();
Machine machine2=new Machine();
machine1.start();
machine2.start();
machine1.run();
}
}
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。接着调用两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
二、实现java.lang.Runnable接口
public class Machine2 implements Runnable {
private int i=0;
@Override
public void run(){
for( i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName()+":"+i);
try{
Thread.sleep(100);
}catch(InterruptedException e)
{
throw new RuntimeException(e);
}
}
}
}
public static void main(String[] args) {
Machine2 machine=new Machine2();
Thread t1=new Thread(machine);
Thread t2=new Thread(machine);
t1.start();
t2.start();
}
}
实现java.util.concurrent.Callable接口
还有一种可以实现Callable接口,其最大的特点就是 有 返回值,由于run()方法是void类型,没有返回值,而实现Callable接口可以返回线程的执行结果import java.util.concurrent.Callable;
/**
* @author xiaohao
* @date 创建时间:Aug 3, 2017 4:05:40 PM
* @version 1.0
*/
public class Machine_Callable implements Callable <String>{
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return "票卖完了!!!"; }
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask <String> task=new FutureTask <String> (new Machine_Callable());
new Thread(task).start();
System.out.println(task.get());
}
三、Thread和Runnable的区别
public class Thread implements Runnable {
...
}
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:
1)适合多个相同的程序代码的线程去处理同一个资源
2)可以避免java中的单继承的限制
3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4)线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
注意:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实习在就是在操作系统中启动了一个进程。
四、线程状态转换
五、线程调度
1、调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
sleep()和yield()的区别:sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
另外,sleep 方法 允许较低优先级的线程 获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
共同点:
1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
不同点:
1. Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等
2. 每个对象都有一个(唯一)锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。 sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
所以sleep()和wait()方法的最大区别是:sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。
sleep()方法
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait()方法
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
六、线程同步
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能被继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
public synchronized String fun(){...}//同步方法
public String fun(){
synchronized (this){...}//方法内的同步代码块
}
使用synchronized关键字同步的
缺点是会使
程序效率变低,执行时间变长;也有可能会
造成死锁