文章目录
Java并发
何时需要多线程
- 程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
- 需要一些后台运行的程序时。
一、线程的使用
继承Thread类
需要在类中实现run()方法,因为Thread类也实现了Runnable接口。
当调用start()方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的run()方法。
class MyThread extends Thread{
public void run(){
//...
}
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
实现Runnable接口
需要实现接口中的run()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
使用Runnable实例再创建一个Thread实例,然后调用Thread实例的start()方法来启动线程
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
实现Callable接口
与Runnable相比
- call()可以有返回值,返回值通过FutureTest进行封装
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
class CallableThread implements Callable{
public Object call() throws Exception {
//...
return 123;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
CallableThread ct = new CallableThread();
FutureTask<Object> ft = new FutureTask<>(ct);
Thread t = new Thread(ft);
t.start();
System.out.println(ft.get());
}
实现接口VS继承Thread
实现接口相比于继承更加好
- Java不支持多重继承,因此继承了Thread类就无法继承其它类,但是可以实现多个接口。
- 类可能只要求可执行就行,继承整个Thread类开销过大。
线程池
优势:
-
提高响应速度(减少了创建新线程)
-
降低资源消耗(重复利用了线程池中的线程,不需要每次都创建)
-
便于线程管理
corePoolSize:核心池大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
class NumberThread implements Runnable{
public void run() {
//...
}
}
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);//提供线程数量
service.execute(new NumberThread());//执行指定的线程操作
service.shutdown();//关闭连接池
}
二、线程的同步机制
synchronized
-
同步代码块
synchronized(同步监视器){ //需要被同步的代码}
说明
- 操作共享数据的代码,不能包含代码多了,也不能包含代码少了。
- 共享数据:多个线程共同操作的数据。
- 同步监视器:俗称:锁。任何一个类的对象,都可以充当锁。(要求多个线程必须共用一把锁)
-
同步方法
如果操作数据共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
非静态同步方法,同步监视器是:this
class Window3 implements Runnable{
private int ticket = 100;
public void run() {
while(true){
show();
if(ticket <= 0){
break;
}
}
}
private synchronized void show(){//同步监视器:this
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":" + "票号为:" + ticket);
ticket --;
}
}
}
静态的同步方法,同步监视器是:window.class
class Window extends Thread{
private static int ticket = 100;
public void run() {
while(true){
show();
}
}
private static synchronized void show(){//同步监视器:Windows4.class
//同步监视器:t1,t2,t3
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":买票, 票号为:" + ticket);
ticket --;
}
}
}
Lock
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
synchronized与Lock
-
锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
-
性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
-
等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
-
公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
使用选择
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
三、线程之间的通讯
wait()/notify()/notifyAll()
- wait():当前线程就进入阻塞状态,并释放同步监视器。
- notify():唤醒wait的一个线程。如果有多个线程wait,就唤醒优先级高的。
- notifyAll():唤醒所有被wait的线程。
它们都属于 Object 的一部分,而不属于 Thread。
wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。否则会抛出IllegalMonitorStateException异常。
wait() 和 sleep() 的区别
- wait()是Object的方法,而sleep()是Thread的静态方法。
- wait()会释放锁,而sleep()不会。