进程vs线程
在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。 某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。 进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
多线程
Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()方法,在main()方法内部,我们又可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程等。 和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。
线程状态
- 新建状态(New):新创建了一个线程对象
- 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的 start() 方法,该状态的线程位于可运行的线程池中,变为可运行状态,这个时候,只要获取了 cpu 的执行权,就可以运行,进入运行状态。
- 运行状态(Running): 就绪状态的线程从 cpu 获得了执行权之后,便可进入此状态,执行 run() 方法里面的代码。
- 阻塞状态(Blocked):阻塞状态是线程因为某种原因失去了 cpu 的使用权,暂时停止运行,一直等到线程进入就绪状态,才有机会转到运行状态,阻塞一般分为下面三种:
- 等待阻塞 :运行的线程执行了 wait() 方法, JVM 会把该线程放入线程等待池中,(wait() 会释放持有的锁 )
- 同步阻塞:运行的线程在获取对象的同步锁时,如果该同步锁被其他线程占用,这时此线程是无法运行的,那么 JVM 就会把该线程放入锁池中,导致阻塞
- 其他阻塞:运行的线程执行 sleep() 或者 join() 方法,或者发出了 I/O 请求,JVM 会把该线程置为阻塞状态,当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程会重新进入就绪状态,(注意:sleep() 是不会释放本身持有的锁的)
- 死亡状态(Dead):线程执行完了之后或者因为程序异常退出了 run() 方法,结束该线程的生命周期。
线程调度
调整线程优先级
Java 线程有优先级,优先级高的线程会获得较多的运行机会,Java 线程的优先级用整数表示,取值范围是 1~10 。线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
public class Thread implements Runnable { /** * The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10; }
线程睡眠
Thread.sleep(long millis)
方法,使线程转到阻塞状态。millis 参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。
public class Thread implements Runnable { public static native void sleep(long millis) throws InterruptedException; }
线程等待
public class Object { public final native void wait(long timeout) throws InterruptedException; }
线程让步
Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。线程加入
join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread t = new Thread() { @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("hello"); } }; System.out.println(Thread.currentThread().getName() + " start"); t.start(); t.join(); System.out.println(Thread.currentThread().getName() + " end"); } }
执行結果: main start hello main end
线程唤醒
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
public class TestWait { private Buffer mBuf = new Buffer(); public void produce() { synchronized (this) { while (mBuf.isFull()) { try { System.out.println("full.stop produce"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } mBuf.add(); notifyAll(); } } public void consume() { synchronized (this) { while (mBuf.isEmpty()) { try { System.out.println("empty.stop consume"); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } mBuf.remove(); notifyAll(); } } private class Buffer { private static final int MAX_CAPACITY = 3; private List<Integer> innerList = new ArrayList<>(MAX_CAPACITY); void add() { if (isFull()) { System.out.println("full. stop add"); } else { innerList.add((int) (Math.random() * 100)); System.out.println(Thread.currentThread().toString() + " add"); } } void remove() { if (isEmpty()) { System.out.println("empty. stop remove"); } else { innerList.remove(0); System.out.println(Thread.currentThread().toString() + " remove"); } } boolean isEmpty() { return innerList.isEmpty(); } boolean isFull() { return innerList.size() == MAX_CAPACITY; } } public static void main(String[] args) { TestWait t = new TestWait(); new Thread() { @Override public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } t.produce(); } } }.start(); new Thread() { @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.consume(); } } }.start(); } }
执行结果:
Thread[Thread-0,5,main] add Thread[Thread-0,5,main] add Thread[Thread-1,5,main] remove Thread[Thread-0,5,main] add Thread[Thread-1,5,main] remove Thread[Thread-0,5,main] add Thread[Thread-0,5,main] add full.stop produce Thread[Thread-1,5,main] remove Thread[Thread-0,5,main] add full.stop produce
线程实现方式
1.继承 Thread 类,Thread 类也是实现的Runnable接口。
public class TestThread extends Thread{ @Override public void run() { System.out.println("继承Thread类"); } public static void main(String[] args) { new TestThread().run(); } }
2.实现 Runnable 接口
public class TestRunable implements Runnable { @Override public void run() { System.out.println("实现Runnable接口"); } public static void main(String[] args) { new TestRunable().run(); } }
3.实现 Callable 接口,与 Future配合使用,有返回值
public class TestCallable implements Callable<Object>{ @Override public Object call() throws Exception { System.out.println("实现Callable接口"); return null; } public static void main(String[] args) { try { new TestCallable().call(); } catch (Exception e) { e.printStackTrace(); } } }
实现 Runnable 接口比继承 Thread 类所具有的优势:
1.可以避免java中的单继承的限制 2.线程池只能放入实现 Runable 或 callable 类线程,不能直接放入继承Thread 的类