目录
线程是程序中执行的线程,Java虚拟机允许应用程序同时执行多个执行线程
每个线程都有优先权,具有较高优先级的线程优先于优先级较低的线程执行
一.线程创建
1.继承Thread类,重写run方法,编写线程执行体,创建线程对象,调用start()方法启动线程。(由于类单一继承,不建议使用)
public class TestDemo extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":我在看代码");
}
@Test
@DisplayName(value = "第一次测试")
public void test1() {
TestDemo testDemo = new TestDemo();
testDemo.start();
}
}
2.实现Runnable接口,实现run()方法,编写线程执行体,创建线程对象,调用start()方法启动线程。(建议使用)
public class TestDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":我在看代码");
}
@Test
@DisplayName(value = "第一次测试")
public void test1() {
TestDemo testDemo = new TestDemo();
new Thread(testDemo,"testDemo").start();
}
}
3.实现Callable接口(建议常用)
- 实现Callable接口,需要返回值类型:implements Callable<T>
- 重写call方法,需要抛出异常:throws InterruptedException, ExecutionException
- 创建目标对象:TestDemo testDemo1 = new TestDemo();
- 创建执行服务:ExecutorService executorService = Executors.newFixedThreadPool(2);
- 提交执行:Future<Boolean> rs1 = executorService.submit(testDemo1);
- 获取结果:Boolean bl1 = rs1.get();
- 关闭服务:executorService.shutdown();
public class TestDemo implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
System.out.println(Thread.currentThread().getName()+":我在看代码");
return true;
}
@Test
@DisplayName(value = "第一次测试")
public void test1() throws InterruptedException, ExecutionException {
TestDemo testDemo1 = new TestDemo();
TestDemo testDemo2 = new TestDemo();
ExecutorService executorService = Executors.newFixedThreadPool(2);
Future<Boolean> rs1 = executorService.submit(testDemo1);
Future<Boolean> rs2 = executorService.submit(testDemo2);
Boolean bl1 = rs1.get();
Boolean bl2 = rs2.get();
System.out.println("bl1:"+bl1+",bl2:"+bl2);
executorService.shutdown();
}
}
二.线程停止(flag)
线程五大状态
- 创建状态:new
- 就绪状态:start
- 运行状态:run
- 阻塞状态:sleep,wait,join等等
- 死亡状态:stop,destroy(jdk已废弃,不建议使用)
停止线程:
- 不推荐使用JDK提供的stop(),destroy()方法(已废弃)
- 推荐线程自己停下来
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
public class TestDemo implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=0;
while(true) {
if(flag) {
System.out.println(Thread.currentThread().getName()+":我在看代码:"+i++);
}
}
}
private void stop() {
this.flag = false;
}
@Test
@DisplayName(value = "第一次测试")
public void test1() {
TestDemo testDemo = new TestDemo();
new Thread(testDemo,"testDemo").start();
for (int i = 0; i < 1000; i++) {
System.out.println("main__"+i);
if(i == 900) {
testDemo.stop();
System.out.println("线程停止");
}
}
}
}
三.线程休眠(sleep)
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延迟,倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
四.线程礼让(yield)
Thread.yield();
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看cpu心情
public class TestDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName()+":线程结束执行");
}
@Test
@DisplayName(value = "第一次测试")
public void test1() {
TestDemo testDemo = new TestDemo();
new Thread(testDemo,"testDemo1").start();
new Thread(testDemo,"testDemo2").start();
}
}
五.线程插队(join)
- Join合并线程,待此线程执行完毕,再执行其他线程,其他线程阻塞
- 可以想象成插队
public class TestDemo implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join...."+i);
}
}
@Test
@DisplayName(value = "第一次测试")
public void test1() throws InterruptedException {
TestDemo testDemo = new TestDemo();
Thread thread = new Thread(testDemo);
thread.start();
for (int i = 0; i < 50; i++) {
if (i == 20) {
thread.join();//main线程阻塞
}
System.out.println("main...."+i);
}
}
}
六.线程观测状态(state)
NEW,RUNNABLE,BLOCKED,WAITING,TIME WAITING,TERMINATED
public class TestDemo{
@Test
@DisplayName(value = "第一次测试")
public void test1() throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("\\\\\\\\");
});
State state = thread.getState();
System.out.println(state);
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED) {
Thread.sleep(100);
state = thread.getState();
System.out.println(state);
}
}
}
线程中断或者死亡,不能再次启动
七.线程优先级
- java提供了一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定调度那个线程来执行。
- 线程优先级用数字表示,范围1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
- 使用以下方式改变或者获取优先级
getPriority()和setPriority(int xxx)
- 优先级的设定建议在start()调度之前
- 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都看CPU调度
- 默认优先级都是5
public class TestDemo implements Runnable{
@Test
@DisplayName(value = "第一次测试")
public void test1() throws InterruptedException {
System.out.println("主线程--->优先级:"+Thread.currentThread().getPriority());
TestDemo testDemo = new TestDemo();
Thread thread = new Thread(testDemo,"testDemo1");
thread.start();
Thread thread2 = new Thread(testDemo,"testDemo2");
thread2.setPriority(Thread.MAX_PRIORITY);
thread2.start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"--->优先级:"+Thread.currentThread().getPriority());
}
}
八.守护线程(daemon)
- 线程分为用户线程(main)和守护线程(gc)
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 例如:后台记录操作日志,监控内存,垃圾回收
public class TestDemo implements Runnable{
@Test
@DisplayName(value = "第一次测试")
public void test1() throws InterruptedException {
TestDemo testDemo = new TestDemo();
Thread thread = new Thread(testDemo,"testDemo1");
thread.setDaemon(true); //设置成守护线程,默认false,是用户线程
thread.start();
Thread thread2 = new Thread(testDemo,"testDemo2");
thread2.start();
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"--->跑了第:"+i+"步");
}
}
}
九.线程同步(排队)
- 多个线程操作同一个资源
- 线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列
- 队列+锁,才能保证线程同步的安全性
在java中保证数据在方法中被访问的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
保证线程安全的同时,也会带来性能低下。
线程不安全实例:买票案例
public class TestDemo{
@Test
@DisplayName(value = "买票")
public void test1() throws InterruptedException {
buyTicket buyTicket = new buyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
Thread.sleep(10000);
System.out.println("=================");
}
}
class buyTicket implements Runnable {
private int ticket = 10;
private boolean flag = true;
@Override
public void run() {
while(flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void buy() throws InterruptedException {
if(ticket <= 0) {
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--+"票");
}
}
十.同步关键字(synchronized)
两种用法:
- synchronized方法
- synchronized块
synchronized方法:控制对象的访问,每个对象对应一把锁,必须获得该方法的对象的锁才能执行,否则线程阻塞,方法一旦执行,就独占锁,直到该方法返回才释放锁,后续的线程才能获得这个锁,继续执行,synchronized方法锁的是这个类的本身this
synchronized块:锁的对象是变化的量,需要增查删改的对象,写法synchronized(object){...}
缺陷:若将一个大的方法申明为synchronized将会影响效率
十一.死锁
- 多个线程各自独占一些共享资源,并且相互等待其他线程占有的资源才能运行,导致都在等待对方释放资源而停止执行的情形
- 同时获得两个对象的锁,有可能会发生死锁
死锁的四个必要条件,打破一个或者多个就可以避免死锁
- 互斥条件
- 请求与保持条件
- 不剥夺条件
- 循环等待条件
十二.Lock(锁)
- jdk5.0开始,Java提供了一个更加强大的线程同步机制,通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,访问共享资源之前应该先获得Lock对象锁
- ReentrantLock类实现了Lock,它拥有跟synchronized相同的并发性和内存语义,可以显式加锁,释放锁,也叫可重入锁
public class TestDemo{
@Test
@DisplayName(value = "买票")
public void test1() throws InterruptedException {
buyTicket buyTicket = new buyTicket();
new Thread(buyTicket,"张三").start();
new Thread(buyTicket,"李四").start();
new Thread(buyTicket,"王五").start();
Thread.sleep(3000);
System.out.println("=================");
}
}
class buyTicket implements Runnable {
private int ticket = 10;
private boolean flag = true;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(flag) {
try {
lock.lock();
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
private void buy() throws InterruptedException {
if(ticket <= 0) {
flag = false;
return;
}
//模拟延迟
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--+"票");
}
}
synchronized与lock对比:
- Lock是显式锁(手动开启和关闭,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放锁
- Lock只有代码锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能和扩展性(提供更多的子类)更好
- 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法(在方法体之外)
十三.线程通信(生产者和消费者问题)
Object对象中提供了几个方法用于解决线程通信的问题
- wait():线程一直等待,直到其他线程通知,与sleep不同,会释放锁
- wait(long timeout):指定等待的毫秒数
- notify():唤醒一个处于等待状态的检查
- notifyAll():唤醒一个对象上所有调用的wait()方法的线程,优先级高的优先调度
都是Object类的方法,都只能在同步方法或者同步块中使用,否则抛出异常IIIegalMonitorStateException
管程法(增加一个缓冲区)
生产者将生产好的数据加入缓冲区,消费者从缓冲区中拿出数据
public class TestDemo{
@Test
@DisplayName(value = "生产者和消费者---管程法")
public void test1() throws InterruptedException {
DogContainer dogContainer = new DogContainer();
new Productor(dogContainer).start();
new Customor(dogContainer).start();
}
}
class Productor extends Thread {
DogContainer dogContainer;
public Productor(DogContainer dogContainer) {
this.dogContainer = dogContainer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
dogContainer.push(new Dog(i));
System.out.println("生产者生产了"+i+"狗");
}
}
}
class Customor extends Thread {
DogContainer dogContainer;
public Customor(DogContainer dogContainer) {
this.dogContainer = dogContainer;
}
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
dogContainer.pull();
System.out.println("消费者消费了"+i+"狗");
}
}
}
class Dog{
int id;
public Dog(int id) {
super();
this.id = id;
}
}
class DogContainer{
Dog[] dogContainer = new Dog[10];
int size = 0;
//放入dog
public synchronized void push(Dog dog) {
//如果缓存池满了,生产者等待,通知消费者消费
if(size == dogContainer.length) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
dogContainer[size] = dog;
size++;
this.notifyAll();
}
//取出dog
public synchronized Dog pull() {
//如果缓存池空了,消费者等待,通知生产者生产
if(size == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
size--;
this.notifyAll();
return dogContainer[size];
}
}
信号灯法(增加标志位)
public class TestDemo{
@Test
@DisplayName(value = "生产者和消费者---信号灯法")
public void test1() throws InterruptedException {
School school = new School();
new Player(school).start();
new Watcher(school).start();
}
}
class Player extends Thread {
School school;
public Player(School school) {
this.school = school;
}
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
if(i%2 == 0) {
school.play("快乐大本营");
}else {
school.play("广告");
}
}
}
}
class Watcher extends Thread {
School school;
public Watcher(School school) {
this.school = school;
}
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
school.watch();
}
}
}
class School{
private String voice;
/**
* 表演者表演,观看者等待 true
* 观看者观看,表演者等待false
*/
private boolean flag = true;
//表演
public synchronized void play(String voice) {
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//表演者表演完毕,通知观看者观看
System.out.println("表演者正在表演:"+voice);
this.voice = voice;
this.flag = !this.flag;
this.notifyAll();
}
//观看
public synchronized void watch() {
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//观看者观看完毕之后,通知表演者表演
System.out.println("观看者观看了:"+voice);
this.flag = !this.flag;
this.notifyAll();
}
}
无论是管程法还是信号灯法,都存在一个“虚假唤醒”的问题
解决方法:将if判断修改成while判断即可
十四.线程池
频繁的创建,销毁线程对性能影响很大,创建好线程池,实现重复利用,类似生活中的公用交通工具
好处:
- 提高了响应速度(减少了创建新线程的时间)
- 降低了资源消耗(重复利用了线程池中的线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池大小
- maximumPoolSize:最大线程数
- keepAliveTIme:线程没有任务时候最多保持多长时间后终止
ExecutorService和Executors线程工具类的使用,例如:
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(10);