多线程基本概念
并发与并行
如果一个CPU有多个核心,并允许多个线程在不同的核心上同时执行,则称为并行。
在同一资源上,通过某种调度算法,让用户看起来计算机是在同时执行多个任务,这就是并发。
Thread线程类
基本用法
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
try {
Thread.sleep(10l);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t=new MyThread();
t.start();
System.out.println("OK");
}
}
常用方法
以下是常用的实例方法
- t.start() 将线程状态从新建转换为就绪
- t.checkAccess() 检查当前线程是否具有访问线程t的权限
- t.interrupt() 尝试通知线程t中断,需要在线程的任务代码中用
- t.setPriority(8) 设置线程的优先级1~10,数值越大,得到的执行机会越高
- t.isDaemon() 判断线程t是否为守护线程,当进程中仅剩守护线程时JVM将退出
- t.setDaemon(true) 在调用start()前将t设置为守护线程
- t.isInterrupted() 检查线程是否被要求中断
- t.isAlive() 判断线程t是否存活
- t.join(1000L) 当前线程等待线程t终止,参数为超时时间
- t.setName(“name”) 设置名字
以下是常用的静态方法
- Thread.yield() 让当前线程让出CPU,并转为就绪状态,重新参与CPU使用权的竞争.只有优先级大于等于当前线程的线程才能获得CPU的使用权
- Thread.sleep(100L) 让当前线程让出CPU,并睡眠(阻塞),然后回到就绪状态,重新参与竞争
- Thread.currentThread() 得到当前线程对象的引用
wait和sleep的区别
以上之所以没有列出wait()是因为,它是所有Object类的方法,两者都可以让程序阻塞指定的毫秒数,并且可以通过interrupt()方法打断
两者的不同:
- wait必须在sychronized同步块或方法中使用
- wait会释放sychronized锁上的对象锁,而sleep不会
- wait形成的阻塞,可以通过针对同一个对象锁的synchronized作用域调用notify()/notifyAll() 来唤醒;而sleep则无法被唤醒,它只能定时醒来或被interrupt()方法中断
sleep和yield的区别
- 线程执行sleep之后进入阻塞状态,并在休眠一段时间之后自动苏醒过来,回到就绪状态。而执行yield方法后,当前线程立刻转入就绪状态。
- 线程执行sleep之后,无论线程的优先级高低,都有机会得以运行;而执行yield方法只会给那些具有相同优先级或者更高优先级的线程运行的机会。
- sleep方法需要声明抛出InterruptException,而yield方法没有声明任何异常。
- 如果在循环中能使用yield方法,容易导致死循环,当前线程在yield后总是又立即抢占到CPU,导致其他线程不能执行。
Runnable接口
除了继承Thread外,还可以实现Runnable接口来编写线程代码。尽量使用接口而不是继承来开发代码。使用Runnable接口可以将线程本身与线程所要执行的代码分离,同时避免Java的单继承限制。而且,线程池只能接受Runnable或Callable接口类型的对象作为任务。
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Java");
}
public static void main(String[] args) {
new Thread(new MyRunnable()).start();
}
}
线程池
线程的创建和销毁会消耗资源。在大量并发的情况下,频繁地创建和销毁线程会严重降低系统的性能。因此,需要预先创建多个线程,并集中管理起来,形成一个线程池。在需要的时候直接从线程池中拿出一个线程来直接使用,执行完毕之后再放回线程池。