Java多线程
1. 实现线程的三种方式
1.1 继承Thread类
继承Thread类首先得重写run方法
class Thread1 extends Thread {
@Override
public void run(){
super.run();
}
public void static main(string[] args){
Thread1 th = new Thread1();
//调用run方法不会同时执行。
th.run();
//start方法开启线程,与主线程同时运行,交替执行
th.start();
}
}
1.2 实现runnable接口
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
//The following code would then create a thread and start it running:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
推荐使用runnable接口的实现方式,由于Java单继承类的局限性,方便一个对象被多个线程使用
new Thread(p,"name1").start; new Thread(p,"name2").start;
1.3 实现Callable接口
步骤:
-
实现callable接口(需要返回值类型)
-
class PrimeRun implements Callable<返回值类型>
-
-
重写call方法
带返回值
-
创建目标对象
-
使用线程池
-
ExecutorService s = Executors.newFixdeThreadPool(3);
-
Future<> f = s.submit(对象);
-
f.get()
-
s.shutdownNow();
-
2.静态代理
真实对象和代理对象都要实现同一个接口。
代理对象要代理真实角色。
好处
:代理对象可以做很多真实对象做不了的事情。
线程中 Thread
就相当于代理对象,他与自己定义的线程类都实现了 runnable
接口
3.Lambda表达式
new Thread(()->System.out.println("多线程")).start();
为什么要使用Lambda表达式?
- 避免匿名内部类过多
- 简洁代码
- 去掉无意义的代码,只留下核心逻辑
函数式接口 Functional Interface
-
定义
任何接口只要只包含唯一一个抽象方法,那么他就是一个函数式接口
public interface Runnable{ public abstract void run(); }
-
对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
写lambda表达式的前提就是先有函数式接口
4.线程状态
线程的五大状态:
- 创建状态
- 就绪状态
- 阻塞状态
- 运行状态
- 死亡状态
4.1线程停止
不推荐使用JDK提供的stop() 暴力停止线程,已经弃用
、destroy()方法
推荐
-
线程自己停下来,利用次数,不建议死循环
-
使用一个标志位进行终止变量,当flag=false,则线程终止运行
//设置一个公开的方法停止线程 class PrimeRun implements Runnable { long minPrime; boolean flag = true; PrimeRun(long minPrime) { this.minPrime = minPrime; } public void run() { // compute primes larger than minPrime while(flag){ ... } } //停止线程 public void stop(){ this.flag = false } }
-
使用interrupt()
public class InterruptThread1 extends Thread{ public static void main(String[] args) { try { InterruptThread1 t = new InterruptThread1(); t.start(); Thread.sleep(200); t.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void run() { super.run(); for(int i = 0; i <= 200000; i++) { System.out.println("i=" + i); } } }
4.2线程休眠(sleep)
sleep()方法来让线程休眠
每个对象都有锁,sleep不会释放锁
主要用于
- 模拟网络延时
- 倒计时
- 显示时间
4.3线程礼让(yield)
礼让不一定会成功,将当前线程转为就绪状态,与其他进程再次同时竞争CPU,不一定礼让成功
yield()
4.4线程强制执行(join)
join合并线程,待此线程执行完成后,再执行其他线程
死亡的线程不能再启动
5.线程优先级
main方法默认优先级为5(范围是1-10)
优先级高的会先执行,(也不百分百会先执行)
6.守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕如
main
方法 - 虚拟机不用等待守护线程执行完毕如后台记录操作日志、监控内存、垃圾回收等
设置守护线程
threadname.setDaemon(true);//默认为false,即为用户线程
7.线程同步
每个对象都有一把锁
线程安全:
加了synchronized关键字的方法都必须获得调用该方法的对象的锁才能执行,否则会线程阻塞
缺陷:
会影响效率
对于普通同步方法,锁是当前实例对象。 如果有多个实例 那么锁对象必然不同无法实现同步。
对于静态同步方法,锁是当前类的Class对象。有多个实例 但是锁对象是相同的 可以完成同步。
对于同步方法块,锁是Synchonized括号里配置的对象。对象最好是只有一个的 如当前类的 class 是只有一个的 锁对象相同 也能实现同步
8.显式加锁lock
与synchronized代码块不同
ReentrantLock
(可重入锁)可以实现显式加锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
//to do....
}finally{
lock.unlock();
}
使用lock锁,jvm将花费较少时间调度线程,性能更好,具有更好的扩展性
使用顺序
- lock>同步代码块>同步方法
9.线程池
ExecutorService和Executors
ExecutorService service = Executors.newFixedThreadPool(10);
//执行callable使用submit,带返回值
//执行runnable使用execute,不带返回值
service.execute();
service.submit();
//关闭连接
service.shutdown();
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池