目录
1.线程创建的三种方式
1.1、继承Thread类(重点)
public class MyThread extends Thread{
@Override
public void run() {
}
public static void main(String[] args) {
MyThread myThread=new MyThread();
myThread.start();
}
}
注:
- 通过继承Thread类,然后重写run方法实现多线程
- 通过在main方法中调用实现了Thread类的对象的start方法
- 线程不一定立即执行,而是有CPU调度
- 不建议使用,避免oop单继承的局限性
1.2、实现Runnable接口(重点,推荐)
public class MyThread1 implements Runnable{
@Override
public void run() {
}
public static void main(String[] args) {
MyThread1 myThread1=new MyThread1();
new Thread(myThread1).start();
}
}
注:
- 通过实现Runnable接口,然后重写接口的run方法
- 通过在main方法中new一个Thread对象,然后将实现了Runnable接口的类的对象当做参数传入Thread构造方法,再调用Thread对象的start方法
- 本质上来说这两种方式都是一样的,因为Thread类也实现了Runnable方法,而继承Thread类就相当于实现了Runnable接口
- 推荐使用,方便同一个对象被多个线程调用
1.3、实现Callable接口(了解)
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MyCallable implements Callable {
private int flag=1;
@Override
public String call() throws Exception {
Thread.sleep(10);
return "你好"+flag++;
}
public static void main(String[] args) {
MyCallable myCallable=new MyCallable();
Future<String> future=null;
List<Future<String>> list=new ArrayList<>();
ExecutorService service= Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
future=service.submit(myCallable);
list.add(future);
}
try {
for (int i = 0; i < 10; i++) {
String str1=list.get(i).get();
System.out.println(str1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
service.shutdownNow();
}
}
注:
- 通过实现Callable接口,重写call方,该方法与run方法不同,run方法没有返回值而call有
- 重写call方法时需要抛出异常
- 创建实现了Callabl接口的类的对象
- 通过ExecutorService service=Executors.newFixedThreadPool(10)方法创建执行服务,参数为线程的个数
- 通过service.submit(myCallable)提交执行,并返回一个Future<>类型的结果,参数为实现了Callabl接口的类的对象
- 通过Future<>对象的get方法获取返回的结果
- 最后通过ExecutorService 的对象的shutdownNow()方法关闭服务
2.线程的五大状态
- 新生状态:当一个线程被new出来的时候,该线程就处于新生状态
- 就绪状态:首先第一种是一个线程在新生状态调用了start方法就到了就绪就绪状态,,而第二种就是线程从阻塞状态结束后,也会进入就绪状态,就绪状态不等于线程在运行了,只是已经准备就绪,随时可以被cpu调度进入运行状态,第三种就是线程礼让,通过yield方法将当前处于运行状态的线程暂停,让其从运行状态变为就绪状态
- 运行状态:当一个线程处于就绪状态然后被cpu调度的时候,那么该线程就进入了运行状态,运行状态的线程可以有过一些诸如sleep(),wait()方法进入阻塞状态
- 阻塞状态:当处于运行状态的线程执行了sleep(),wait()等方法时就会进入阻塞状态,当阻塞事件结束的时候,又会从阻塞状态进入就绪状态,随时等待cpu的调度
- 死亡状态:当线程中断结束,就会进入死亡状态,进入该状态的时候就无法再次启动该线程了
3.Lamda表达式
-
使用Lamda的好处
- 避免匿名内部类定义过多
- 可以让代码更加简洁
- 丢掉了一些没有意义的代码,只留下核心的逻辑
-
函数式接口:只包含一个方法的接口叫做函数式接口,对于函数式接口就可以通过lamda表达式来创建该接口的对象
-
lamda表达式演化过程:
-
正常定义的类
public class Lamda { public static void main(String[] args) { Test1 test1=new Test1(); test1.talk(); } } interface Test{ void talk(); } class Test1 implements Test{ @Override public void talk() { System.out.println("test1"); } }
-
静态内部类
public class Lamda { static class Test2 implements Test{ @Override public void talk() { System.out.println("test2"); } } public static void main(String[] args) { Test2 test2=new Test2(); test2.talk(); } } interface Test{ void talk(); }
-
局部内部类
public class Lamda { public static void main(String[] args) { class Test3 implements Test{ @Override public void talk() { System.out.println("test3"); } } Test3 test3=new Test3(); test3.talk(); } } interface Test{ void talk(); }
-
匿名内部类
public class Lamda { public static void main(String[] args) { Test test4=new Test() { @Override public void talk() { System.out.println("test4"); } }; test4.talk(); } } interface Test{ void talk(); }
-
Lamda表达式
public class Lamda { public static void main(String[] args) { Test test5=()-> System.out.println("test5"); test5.talk(); } } interface Test{ void talk(); }
注:Lamda再简化
- 如果参数只有一个,那么可以不写返回值类型以及外面的小括号
- 如果有多个参数,那么可以不写返回值(都去掉),但是不能去掉小括号
- 如果只有一行代码,可以不写函数体外面的大括号
- 使用Lamda表达式的前提是函数式接口!!
-
4.线程初进阶
-
Thread.currentThread().getName()--拿到当前线程的名字,Thread.currentThread()表示当前线程
public void run() { System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票"); }
-
Thread.sleep(int)--线程休眠指定的时间,单位毫秒
try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
注:
- 使用该方法可以模拟网络延时,放大问题以便让我们观察到
- 使用该方法还可以模拟倒计时
- 该方法不会释放锁
-
new Thread(myThread1,"小明")--构造函数,第二个参数为线程的名字
public static void main(String[] args) { MyThread1 myThread1=new MyThread1(); new Thread(myThread1,"小明").start(); new Thread(myThread1,"小红").start(); new Thread(myThread1,"小东").start(); }
-
join()--相当于插队,必须该线程执行完才能张执行其他的线程
public class Yield implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { if(i==0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("插队线程======="+i); } } public static void main(String[] args) { Yield yield1=new Yield(); Thread thread1=new Thread(yield1); thread1.start(); for (int i = 0; i < 500; i++) { if(i==200){ try { thread1.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("main线程======"+i); } } }
-
yield()--暂停当前线程,并执行其他线程,也叫做礼让线程
public class Yield implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName()+"线程结束执行"); } public static void main(String[] args) { Yield yield1=new Yield(); new Thread(yield1,"a").start(); new Thread(yield1,"b").start(); } }
注:
- 礼让不一定成功,相当于重新竞争
-
interrupt()--中断线程,不建议使用这种方式
-
isAlive()--测试线程是否处于活动状态
-
getStates()--得到线程的状态
Yield yield1=new Yield(); Thread thread1=new Thread(yield1); System.out.println(thread1.getState()); thread1.start(); System.out.println(thread1.getState()); for (int i = 0; i < 10; i++) { System.out.println(thread1.getState()); if(i==200){ try { thread1.join(); System.out.println(thread1.getState()); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(thread1.getState()); System.out.println("main线程======"+i); } }
- [
NEW
]
尚未启动的线程处于此状态。 - [
RUNNABLE
]
在Java虚拟机中执行的线程处于此状态。 - [
BLOCKED
]
被阻塞等待监视器锁定的线程处于此状态。 - [
WAITING
]
正在等待另一个线程执行特定动作的线程处于此状态。 - [
TIMED_WAITING
]
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。 - [
TERMINATED
]
已退出的线程处于此状态。
- [
-
线程停止方法:
- 建议让线程正常终止,利用次数,不建议死循环
- 建议使用一个标志位,去设置一个flag
- 不要使用stop(),destory()等过时或者jdk不建议的方法
-
线程的优先级
- getPriority()--得到线程的优先级
- setPriority(int)--设置线程的优先级
注:
- 主线程的优先级不能更改,使用默认的优先级5
- 先设置优先级再启动
- 优先级大只是被调用的概率大,不一定优先级高的就先被调用
- 优先级范围为1~10
-
守护线程:线程分为用户线程和守护线程,JVM必须等待用户线程执行完成,而不必等待守护线程进行完毕,通过setDaemon(true)设置线程为守护线程,默认为false
5.线程同步
5.1、了解线程同步
问题:当多个线程访问一个资源的时候,就会出现线程不安全的问题,导致资源的变化出现不一致!!
解决:通过锁和队列的机制解决
具体:
- 每一个对象都有一个锁,线程必须拿到该对象的锁才可以对该资源进行操作
- 当有多个线程访问同一个对象时,就会形成一个队列
- 当线程访问该对象的时候,如果该对象有锁,那么线程拿到对象的锁
- 后续线程按照一定的顺序去访问这个对象,访问的时候会去检查该对象是否有锁,如果没有则说明上一个线程还没有操作完毕,也就是还没有释放该对象的锁,即该等待的线程还不能操作该对象
弊端:
- 虽然解决了安全的问题,但是使用锁以及排队会降低性能
- 如果出现优先级高的线程等待优先级低的线程,那么可能会出现优先级倒置的情况
5.2、代码模拟
-
使用synchronized关键字修饰方法,锁的是this,也即是当前对象
class MyThread1 implements Runnable{ private int ticket=10; boolean flag=true; @Override public void run() { while(flag) { buy(); } } public synchronized void buy(){ if(ticket==0) { flag=false; return; } else { System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票"); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { MyThread1 myThread1=new MyThread1(); new Thread(myThread1,"小明").start(); new Thread(myThread1,"小红").start(); new Thread(myThread1,"小东").start(); } }
-
synchronized(Object){}块:Object称为同步监视器,可以是任何对象,推荐使用共享资源作为同步监视器
class MyThread1 implements Runnable{ private int ticket=10; boolean flag=true; @Override public void run() { while(flag) { buy(); } } public void buy(){ synchronized (this){ if(ticket==0) { flag=false; return; } else { System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票"); } } } public static void main(String[] args) { MyThread1 myThread1=new MyThread1(); new Thread(myThread1,"小明").start(); new Thread(myThread1,"小红").start(); new Thread(myThread1,"小东").start(); } }
注:object应该是变化的量的拥有者,即锁的应该是变化的量所属的对象!!
5.3、Lock锁
-
Lock和synchronized的区别
- synchronized是Java内置的关键字,在jvm层面上起作用,Lock是一个Java类
- synchronized无法判断是否获取了锁,Lock可以判断是否获得锁
- synchronized会自动释放锁,Lock必须手动释放锁,而且释放锁的代码必须写在finally代码块中
- synchronized修饰的代码块,由其中一个线程获得锁之后,这个线程会阻塞,等待的其他线程会一直等待下去,Lock不一定会死等
- synchronized是可重入、不可中断、非公平锁;Lock是可重入锁,自己配置是否可中断,自己配置是否公平
- Java1.6之前synchronized性能低效,Java在1.6之后对其性能进行一个优化。从此,两者的区别只在于一些功能性区别。其实,更加推荐使用synchronized,因为升级Java版本会获得免费的性能提升
-
使用Lock锁:
import java.util.concurrent.locks.ReentrantLock; class MyThread1 implements Runnable{ private final ReentrantLock lock=new ReentrantLock(); private int ticket=10; boolean flag=true; @Override public void run() { while(flag) { buy(); } } public void buy(){ try{ lock.lock(); if(ticket==0) { flag=false; return; } else { System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票"); } }finally { lock.unlock(); } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) { MyThread1 myThread1=new MyThread1(); new Thread(myThread1,"小明").start(); new Thread(myThread1,"小红").start(); new Thread(myThread1,"小东").start(); } }
注:在使用Lock的时候,一般通过创建一个ReentrantLock对象来实现加锁,因为ReentrantLock也实现了Lock接口,在使用时通过调用lock()方法加锁,unlock解锁!!
5.4、死锁
产生情况:当有多个线程相互拿着对方资源,并且需要获得对方的资源才能释放锁的时候,就会产生死锁,因为都需要彼此的资源来释放自己的资源,导致资源一直不能被释放进而程序卡死,出现死锁的情况!!!
死锁的四个必要条件
-
互斥条件:一个资源每次只能被一个进程使用;
-
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放;
-
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺;
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系;
解决死锁的4种基本方法
-
预防死锁:通过设置一些限制条件,去破坏产生死锁的必要条件
2. 避免死锁:在资源分配过程中,使用某种方法避免系统进入不安全的状态,从而避免发生死锁-
检测死锁:允许死锁的发生,但是通过系统的检测之后,采取一些措施,将死锁清除掉
-
解除死锁:该方法与检测死锁配合使用
-
6.线程通信
-
为什么需要线程通信:
- 线程是操作系统调度的最小单位,有自己的栈空间,可以按照既定的代码逐步的执行,但是如果每个线程间都孤立的运行,那就会造资源浪费。所以在现实中,我们需要这些线程间可以按照指定的规则共同完成一件任务,所以这些线程之间就需要互相协调,这个过程被称为线程的通信。
- 线程的通信可以被定义为:
线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。
-
有关线程通信的三个方法:
- wait() :当前线程释放锁并进入等待(阻塞)状态,当传入参数时等待参数的时间,当没有参数时,表示一直等待
- notify() :唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁
- notifyAll() :唤醒所有正在等待相应对象锁的线程,使其进入就绪队列,以便在当前线程释放锁后继续竞争锁
-
线程通信的几种方式:
-
共享内存
public class Cache { public static void main(String[] args) { CacheArea cacheArea=new CacheArea(); new Provider(cacheArea).start(); new Consumer(cacheArea).start(); } } class Provider extends Thread{ CacheArea cacheArea; public Provider(CacheArea cacheArea) { this.cacheArea = cacheArea; } @Override public void run() { for (int i = 0; i < 200; i++) { cacheArea.push(); } } } class Consumer extends Thread{ CacheArea cacheArea; public Consumer(CacheArea cacheArea) { this.cacheArea = cacheArea; } @Override public void run() { for (int i = 0; i < 200; i++) { cacheArea.pop(); } } } class CacheArea{ int count=0; public synchronized void push(){ if(count==100){ try { System.out.println("商品满了,快来消费"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } count++; System.out.println("第" + count + "个商品产出"); this.notifyAll(); } public synchronized void pop(){ if(count==0){ try { System.out.println("没有商品了,快来生产"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("第" + count-- + "个商品消费"); this.notifyAll(); } }
-
消息传递
-
管道流
-
-
线程池:
- 线程池的好处:
- 降低系统资源消耗, 通过重用已存在的线程, 降低线程创建和销毁造成的消耗;
- 提高系统响应速度, 当有任务到达时, 无需等待新线程的创建便能立即执行;
- 方便线程并发数的管控, 线程若是无限制的创建, 不仅会额外消耗大量系统资源, 更是
占用过多资源而阻塞系统或内存不足等状况, 从而降低系统的稳定性。 线程池能有效管控线程, 统一分配、 调优, 提供资源使用率; - 更强大的功能, 线程池提供了定时、 定期以及可控线程数等功能的线程池, 使用方便简
单
- 参数为Runnable类型:使用ExecutorService对象的execute()方法,没有返回值
- 参数为Callable类型使用ExecutorService对象的submit方法,有返回值
- 关闭线程池:shutdown()
- Executors:线程池工具类,用于创建并返回不同类型的线程池
- 线程池的好处: