java程序中会默认存在的三个线程

  • main方法:主线程
  • GC(垃圾回收机制,回收不用的垃圾内容数据)
  • 异常处理机制

线程的创建和使用

创建线程的方式

  • 继承Thread类
  • 实现Runable接口
  • 实现CallBack接口
  • 线程池

Thread类

继承Thread类,那么Talk1这个类就是一个线程类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Talk1 extends Thread {
/*
*run方法,线程体,也就是当前线程需要执行的任务
*线程启动后,系统会调用run方法
* @see java.lang.Thread#run()
*/
@Override
public void run() {
while(true) {
// 获取线程的名字
System.out.println(Thread.currentThread().getName());
System.out.println(getName());
System.out.println("小红,我好喜欢你");
try {
// 让当前线程睡眠指定时间,单位为毫秒
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
1
2
3
4
5
6
7
8
9
// 怎么使用线程?
// 1.创建线程对象
Talk1 t1 = new Talk1();
Talk2 t2 = new Talk2();

// 2.启动线程,start()表示启动当前线程
// 启动两个线程,这两个线程都在同时运行着
t1.start();
t2.start();

Runnable接口

1
2
3
4
5
6
7
8
9
10
11
// 1.定义类实现Runnable接口
class MyThread implements Runnable{
// 2.重写run方法
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + ":这是一个线程");
}

}
}

Callable接口

Callable里面有个call方法,该方法是有返回值的,也就代表线程执行完后会有返回值

需要借助FutureTask类,可以获取返回值

  • 创建类型实现Callable接口,并指定返回值类型
  • 重写call方法,线程最终调用的方法,类似run方法
  • 创建callable的实现类对象
  • 创建FutureTask对象,同时指定泛型,与接口一致
  • 创建线程对象,将FutureTask对象传入,启动线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test06 implements Callable<Boolean> {
// 重写call方法
@Override
public Boolean call() {
return true;
}

public static void main(String[] args){
//创建实现类对象
Test06 t1 = new Test06();

// 创建服务
ExecutorService executorService = Executors.newFixedThreadPool(3);

//提交
Future<Boolean> f1 = executorService.submit(t1);

//获取结果
boolean rs1 = f1.get();

//关闭服务
executorService.shutdownNow();
}
}

线程的常用方法

  • 1.start()启动线程
  • 2.sleep()
  • 3.getName()
  • 4.setName()
1
2
3
4
5
6
7
8
9
// 设置线程的名字
t1.setName("线程1");

// 设置线程的优先级,里面参数 1-10
//t1.setPriority(newPriority);

// 在main方法里面
System.out.println(Thread.currentThread().getName());

线程的中断

线程中是有一个标志位,这个标志位可以进行设置,表示当前线程是否处于中断

线程的中断

  • 条件中断
  • 异常中断
  • stop():暴力中断,不使用
1
2
3
// 获取当前线程中断标志位的状态
// true:表示是中断,false:表示不是
System.out.println(isInterrupted());

线程状态

image-20200808234336936

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,不要用这个方式
boolean isAlive() 测试线程是否处于活动状态

线程的同步

解决线程资源竞争的问题

  • synchronized

    • 可以修饰代码块(同步代码快)

      1
      2
      3
      synchronized(同步监视器){
      ...
      }
      • 同步监视器
      • 1.可以理解成锁。并且任何对象都可以充当锁
      • 2.只有拿到锁的线程才能操作同步代码块中的代码,其他线程只能等待,等锁释放后线程再去抢这把锁
      • 与共享变量操作相关的代码都放在同步代码块中,放进去之后,其他没有拿到锁的线程就没办法接触到这个共享变量,也就不会出现多个线程操作某个共享变量
    • 可以修饰方法(同步方法)

      1
      2
      3
      public synchronized void func(){
      ...
      }
      • 在方法声明中加上synchronized,这样一来整个方法将会被同步,也就是只有拿到锁的线程才能执行该方法,其他线程甚至无法进入该方法
      • 同步方法上的锁默认就是当前方法所在类的对象
  • Lock:JDK5.0中添加的

    • 上锁和解锁
    • Lock是一个接口,主要使用他的实现类ReentrantLock类来操作
  • 两者区别:

    • synchronized相当于自动加锁解锁

    • Lock需要自己手动的加锁和解锁,但是他更加灵活

synchronized

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 同步方法
public synchronized void sell() {
// 与共享变量操作相关的代码都放在同步代码块中
// 放进去之后,其他没有拿到锁的线程就没办法接触到这个共享变量,也就不会出现多个线程操作某个共享变量
if (ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "售票成功,票号:" + ticket.count);
ticket.count--; // 票的数目减1
try {
Thread.sleep(10); // 模拟卖票的耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void sell() {
private final ReentrantLock lock = new ReentrantLock();
// 使用ReentrantLock加锁时,一般放在try代码块中
try {
// 在执行操作共享变量的位置之前上锁
// 上锁后其他线程也是无法执行的,上锁后面的代码只能被当前线程执行
lock.lock();
if(ticket.count > 0) {
System.out.println(Thread.currentThread().getName() + "售票成功,票号:" + ticket.count);
ticket.count--; // 票的数目减1

try {
Thread.sleep(10); // 模拟卖票的耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

线程间通信

线程间通信:

  • 以下方法必须放在syncrhonized里面,否则包参数异常!

  • wait() : 可以让当前线程暂停,暂停之后释放锁,其他线 程可以获取这个锁,等待其他线程唤醒

  • notify():唤醒某个暂停的线程

  • notifyAll():唤醒所有等待的线程

sleep()与wait()的区别?

他们两个都能让线程进行暂停
sleep()不会释放当前线程占用的锁
wait()会释放锁

线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建包含5个线程的线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(5);

Print p = new Print();
// execute():添加线程执行任务,执行线程(背后就是挑一个空闲的线程去执行)

threadPool.execute(p);
threadPool.execute(p);
threadPool.execute(p);
threadPool.execute(p);
threadPool.execute(p);
threadPool.execute(p);

// 线程池不用了关掉
threadPool.shutdown();

阿里Java开发手册禁止使用Executors创建线程池:

  1. 【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors各个方法的弊端:

1) newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。

2) newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

  1. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
//设置线程名字
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread").build();
//创建线程池
ExecutorService service = new ThreadPoolExecutor(10,10,10L, TimeUnit.MICROSECONDS,new LinkedBlockingDeque<Runnable>(),threadFactory);

//执行线程
service.submit(new MyThread());

//关闭线程池
service.shutdown();
}