文章目录
多线程
线程的创建方式
实现Runnable接口
public class Test {
public static void main(String[] args){
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable,"t1"); //第一个参数传入实现了Runnable的类,第二个参数设置线程名
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i < 10; i++){
System.out.println("这是分支线程第" + i + "次打印");
}
}
}
也可以使用匿名内部类的方式:
public class Test {
public static void main(String[] args){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i < 10; i++){
System.out.println(i);
}
}
}, "t1");
thread.start();
}
}
继承Thread
public class Test {
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
System.out.println("这是分支线程");
}
}
实现Runnable接口与继承Thread相比有如下几点优势:
1、可以避免单继承带来的局限性
2、任务与线程是分离的,提高了程序的健壮性
3、线程池只能接受Runnable类型的任务,
4、通过创建任务,给线程分配的方式实现多线程,更适合多个线程执行相同任务的场景
Callable
public class Test {
public static void main(String[] args) {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread t1 = new Thread(futureTask);
t1.start();
}
}
class MyCallable implements Callable<Integer>{ //
@Override
public Integer call() throws Exception {
System.out.println("这是重写的call方法");
return 1;
}
}
与上述两种线程创建方式不同,实现callable接口的线程执行完会产生一个返回值
其中call方法会抛出异常,但是run方法不会
Thread类
常用方法
String getName(); //获取当前线程名称
void start(); //启动线程
static void sleep(long millis, int nanos); //让线程休眠millis毫秒+nanos纳秒(第二个参数可以不填)
void setDaemon(boolean on); //将此线程标记为守护(daemon)线程或用户线程
获取、修改当前线程名
public class Test {
public static void main(String[] args){
Thread thread = Thread.currentThread();//获取执行这段代码的当前线程对象
thread.getName();//获取线程名
thread.setName("t1");//设置线程名
}
}
线程的中断
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//循环执行任务
while (true) {
for (int i = 1; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("响应中断,结束线程");
return;
}
}
}
}
});
thread.start();//启动线程
Thread.sleep(3000);//main线程先睡3秒
thread.interrupt();//中断线程,执行catch代码块
/* 注:线程会继续执行当前任务,直到执行到以下方法时才会响应中断
java.lang.Object#wait()
java.lang.Object#wait(long)
java.lang.Object#wait(long, int)
java.lang.Thread#sleep(long)
java.lang.Thread#interrupt()
java.lang.Thread#interrupted()*/
}
}
线程安全
同步代码块
格式:
synchronized (object o){
}
Java中,每个对象都有一个锁标记
当第一个线程执行到同步代码块时,会占有括号中的这个对象锁
后续线程执行到此处时会先观察,括号中的对象锁是否被占用,如果被占用就排队等待
当线程执行完同步代码块后会自动释放掉占用的括号中的对象锁
特点:
锁的粒度比较小
同步方法
将synchronized关键字加在方法上,格式:
public synchronized void say(){
}
表示锁的this,即调用该方法的对象
当线程执行到某对象的say()方法时,该对象的对象锁被占用
synchronized也可以用在静态方法上,表示锁的当前类的class对象(类名.class)
同步代码块和同步方法都是隐式锁(即自动上锁,自动解锁)
Lock显式锁
public static void main(String[] args) {
//任务对象
Runnable r = new Runnable() {
private int i = 1;
//创建锁对象
private Lock l = new ReentrantLock();
@Override
public void run() {
while (true){
l.lock();//当线程执行到此处时会观察l对象是否被锁住,如果是就排队,否则就将l对象锁住并继续往下执行
try {
if (i < 10){
System.out.print("第" + i + "次");
System.out.println(Thread.currentThread().getName() + "正在执行");
i++;
}else {
break;
}
}finally {
l.unlock();//锁的释放要放在finally代码块里,防止占用资源
}
}
}
};
new Thread(r, "t1").start();
new Thread(r, "t2").start();
new Thread(r, "t3").start();
}
公平锁与非公平锁
公平锁:按先来后到排队获取锁
非公平锁:同时竞争一把锁
上述三种锁的方式都是非公平锁
公平锁的实现方式:
Lock l = new ReentrantLock(true);//创建lock对象时传入一个参数,默认是false表示非公平锁
线程的状态
New:新建状态,调用start方法之后进入可运行状态
Runnable:可运行状态(未抢夺到CPU时间片而陷入等待的线程,依然属于Runnable状态)
Blocked:阻塞状态(没能进入同步代码块的线程会进入该状态)
Waiting:等待状态(调用了wait方法、Thread.join方法会使线程进入该状态)
Timed_Waiting:超时等待状态(调用了Thread.sleep、wait带时间参数的方法、join带时间参数的方法会进入该状态)
Terminated:结束状态
线程池
缓存线程池
public class Test {
public static void main(String[] args) {
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
}
定长线程池
public class Test {
public static void main(String[] args) {
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
}
单线程线程池
public class Test {
public static void main(String[] args) {
/**
* 单线程线程池.
* 执行流程:
* 1. 判断线程池 的那个线程 是否空闲
* 2. 空闲则使用
* 4. 不空闲,则等待 池中的单个线程空闲后 使用
*/
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
}
}
周期性任务定长线程池
public class Test {
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
* *
周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
}
}
Lamda表达式
lamda表达式是一种函数式编程思想
在开发过程中经常遇到这样的情况:
在创建线程时,为了实现run方法,要写一堆与run方法的实现无关的代码(即先要创建对象,再通过对象调用方法)
lamda表达式略去了那些无关的要素,只关注于方法
接口中只有一个抽象方法需要被实现的(不是规定接口只能有一个方法),可以使用lamda表达式
语法格式:
() -> {}
由于只有一个方法需要被实现
所以其中的()表示的是需要被实现的方法后面的括号,有入参就在括号里加上入参
后面的大括号表示需要实现方法的方法体