一,什么是多线程
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。
二,创建线程的三种方式
1,通过继承 Thread 类本身;
一个类如果继承Thread类后,它就拥有了Thread类的所有方法
//创建方式1:继承线程类Thread
//使用步骤1.继承Thread,
public class Create01 extends Thread{
@Override
//2.重写run()方法
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
public static void main(String[] args) {
//3.创建该类的对象开启线程start();
Create01 c = new Create01();
c.setName("haha");//设置该线程的名称
c.start();//开启线程
for (int i = 0; i <100 ; i++) {
//主线程输出
//Thread.currentThread().getName()得到当前线程的名字
System.out.println(Thread.currentThread().getName()+i);
}
}
}
执行效果图:
创建的线程和主线程交替输出,表示交替执行,主线程开启其他线程后,线程执行顺序主要由CPU调度
2,通过实现 Runnable 接口;
package study01;
//创建方式二:实现Runnable接口
//步骤1:实现Runnable接口
public class Create02 implements Runnable{
//步骤2.实现run()方法
public void run() {
for (int i = 0; i <100 ; i++)
System.out.println(Thread.currentThread().getName()+i);
}
public static void main(String[] args) {
//步骤三,通过该类的对象创建Thread类对象开启线程(静态代理)
//静态代理:即通过引用/对象传递(一般用接口进行约束)借助代理类的对象,对该引用/对象做某些使用和修改
//例如别人用你的身份证去上网,帮你开了会员还充了网费
new Thread(new Create02(),"小李子").start();
/*完整步骤:
1.
Thread t1 = new Thread(new Create02());
t1.setName("线程名字");
t1.start();
2.
Thread t1 = new Thread(new Create02(),"线程名字");
t1.start();
*/
for (int i = 0; i <100 ; i++)
System.out.println(Thread.currentThread().getName()+i);
}
}
3,通过 Callable 和 Future 创建线程。
Callable接口优点
- 1.Callable接口要重写的call()方法是可以有返回值的。
- 2.call()方法可以抛出异常,供外部捕获。
- 3.Callable接口支持泛型。
import util.DownloadUtil;
import java.util.concurrent.*;
//使用步骤1:实现Callable接口
public class Create03 implements Callable<String> {
private String url;
private String fileName;
public Create03(String url, String fileName) {
//通过构造器给线程传递参数
this.url = url;
this.fileName = fileName;
}
//步骤2:实现call方法,类似于run(),但可以有返回值,能抛出异常
public String call(){
DownloadUtil.download(this.url,this.fileName);//自己写的一个网络文件下载方法
return "成功下载了"+fileName;
}
public static void main(String[] args) {
//步骤3:创建该类对象
Create03 c1 = new Create03("https://www.aa.png","1.png");
Create03 c2 = new Create03("https://v/index10.ts","2.ts");
//步骤4:创建执行服务/线程池,后面会说
ExecutorService ser = Executors.newFixedThreadPool(2);//池子容量
//步骤5:提交执行
Future<String> result1 = ser.submit(c1);
Future<String> result2 = ser.submit(c2);
//步骤6,:获取结果
try {
String msg1 = result1.get();
String msg2 = result2.get();
System.out.println(msg1);
System.out.println(msg2);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
4.龟兔赛跑案例
package study01;
public class Race implements Runnable{
private static String winner;//静态的,输入该类,而不属于该类的任何对象
public void run() {
for (int i = 0; i <=100; i++) {
if (i%20==0&&"兔子".equals(Thread.currentThread().getName())) {
try {
System.out.println("兔子睡觉了");
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (isWin(i)){
System.out.println("胜利者"+winner);
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
public Boolean isWin(int steps){
if (winner!=null){
return true;
}else if (steps>=100){
winner=Thread.currentThread().getName();
return true;
}
return false;
}
public static void main(String[] args) {
Race race =new Race();
Thread a = new Thread(race,"兔子");
Thread b = new Thread(race,"乌龟");
a.start();
b.start();
System.out.println(winner+"666666");
}
}
三,线程方法
线程类的常用方法:
终止线程:建议使用信号灯/设置标志位进行终止,不建议使用stop()和destroy()方法
返回值 | 方法 | 作用 |
---|---|---|
static void | sleep(long millis) | 线程休眠指定时间,单位毫秒,不会让当前线程释放它所持有的锁。 |
String | toString() | 返回此线程的字符串表示形式,包括线程的名称、优先级和线程组。 |
static void | yield() | 线程礼让,停止当前线程的运行状态,使线程从运行状态回到就绪状态等待CPU调用,不一定礼让成功,主要看CPU心情 |
void | start() | 导致该线程开始执行;java虚拟机调用这个线程的 run方法。 |
void | stop() | (方法已经过时)停止该线程 |
void | destroy() | (方法已经过时)摧毁该线程 |
int | getPriority() | 返回此线程的优先级 |
Thread.State | getState() | 返回此线程的状态。 |
void | interrupt() | 中断这个线程。 |
boolean | isDaemon() | 判断该线程是守护线程。 |
void | join() | 合并所有线程,让调用该线程的对象优先执行,知道该线程死亡 |
void | join(long millis) | 给该线程millis毫秒的时间执行(相当于join()方法加了限时条件)。 |
void | setName(String name) | 改变该线程的名称等于参数 name。 |
void | setPriority(int newPriority) | 更改此线程的优先级。 |
boolean | isAlive() | 测试这个线程是否还活着。 |
String | getName() | 返回此线程的名称。 |
long | getId() | 返回此线程的名称。 |
ThreadGroup | getThreadGroup() | 返回此线程所属的线程组。 |
void | setDaemon(boolean on) | true为设置该线程为守护线程 |
四,线程状态
1.新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
2.就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
3.运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
4.阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
5.等待阻塞:
运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
6.同步阻塞:
线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
7.其他阻塞:
通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
8.死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程状态
9.Thread.State
Thread.State是Thread类的内部枚举类,用于表示线程运行的状态
通过调用getState()
方法得到线程当前的的运行状态
它的常用枚举量有
1.NEW
新建状态:即单纯地创建一个线程。
2.RUNNABLE
运行状态:当线程获得CPU时间,线程才从就绪状态进入到运行状态。
3.BLOCKED
堵塞状态,线程被堵塞
4.WAITING
等待:线程真在等待另一个线程执行特定动作
5.TIMED_WAITING
限时等待:线程正在等待指定的的时间,sleep()
6.TERMINATED
终止/退出:线程已经退出执行,
10,线程的生命周期
五,线程优先级和守护线程
1.线程优先
优先级:priority
Thread.MIN_PRIORITY==1//最低优先级
Thread.NORM_PRIORITY==5//一般优先级(默认)
Thread.MAX_PRIORITY==10//最高优先级
使用setPriority(int newPriority)
方法设置线程的优先级
newPriority范围1~10
注意:
优先级建议设置在线程开启,start()之前优先级高,只代表CPU调用该线程的概率高,最高的不一定最先调用
2.守护线程
守护线程Daemon
开启方式:使用方法setDaemon(true)
特点:开启后一保持存活,虚拟机不需要等待守护线程执行完成,用户线程执行完毕后守护线程会自动结束
例子,gc线程
作用:后台操作记录日志,,监控内存,垃圾回收等
六:线程同步:
1:synchronized锁机制
synchronized锁机制分为synchronized方法和synchronized块
1.synchronized方法:
使用synchronized关键字使线程同步,每个对象都会有一把锁,synchronized的方法只有获得调用该方法的对象的锁的线程才能执行该方法
1.使用:在一般的方法前加synchronized关键字
注意:synchronized默认锁住的是this,即方法所属类的对象,synchronized关键字和static关键字同时存在于方法签名上,则synchronized锁住的是这个类
2.优点:实现同步
3.缺点:造成性能降低,性能倒置的问题
只有会修改数据的方法才需要用synchronized,避免资源浪费影响效率
2.synchronized块:
1.使用
synchronized(Object obj){//需要锁住的对象
//使用该对象执行数据修改的代码
}
2.锁的对象通常是会发生变化的量,避免资源浪费影响效率
3.优点:实现同步
4.缺点:造成性能降低,性能倒置的问题
3.死锁
当一个线程需要同时获得两个对象的锁时,可能会发生死锁现象
案例:
package study03;
public class DeadLock implements Runnable{
static final A a = new A();
static final B b = new B();
int choose;
public DeadLock(int choose){
this.choose = choose;
}
@Override
public void run() {
if (choose==1){
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
try {
Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(b){//获得a对象锁的同时,还需要获得b对象的锁
System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
}
}
}else{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
try {
Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(a){//获得b对象锁的同时,还需要获得a对象的锁
System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
}
}
}
}
public static void main(String[] args) {
new Thread(new DeadLock(1),"小明").start();
new Thread(new DeadLock(0),"小红").start();
}
}
class A{
}
class B{
}
解决办法:将run()方法修改为
public void run() {
if (choose==1){
synchronized (a){
System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
try {
Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized(b){//使用a对象锁后退回,再等待获得b对象的锁
System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
}
}else{
synchronized (b){
System.out.println(Thread.currentThread().getName()+"获得了B对象的锁");
try {
Thread.sleep(1000);//休息一会,扩大不安全事件的发生性
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized(a){//使用b对象锁后退回,再等待获得a对象的锁
System.out.println(Thread.currentThread().getName()+"获得了A对象的锁");
}
}
}
2.lock可重复锁
lock锁:每次只能有一个线程能对Lock对象加锁,显式的锁的。线程开始访问共享资源前需要先获得Lock对象,达到独占资源的目的。
注意:锁住的是Lock对象
1.锁的创建
1.简单的使用案例
//一般会使用final关键字
private final ReentrantLock lock = new ReentrantLock();
public void a() {
try{
lock.lock();//上锁
//此处为共享资源
}finally {
lock.unlock();//解锁
//如果同步块会有异常,建议将unlock()方法写到finally里
}
lock.lock()
与lock.unlock()
方法之间的部分不属于同步代码块,也不属于同步方法中,不能使用wait()和notify()方法
2.Lock锁和synchronized的区别
- 1.Lock 的锁定是通过代码实现的,而 synchronized 是在 JVM 层面上实现的。
- 2.lock锁特点:性能更好,JVM花费更少的时间来调度线程,拥有更好的扩展性(能有很多子类)
- 3.lock只有代码块锁,synchronized有方法锁和代码块锁
- 4.lock是显式的锁需要手动关闭,synchronized是隐式的锁出了作用域自动释放
- 5.优先使用顺序:Lock>同步代码块(已经进入了方法体,分配了相应的资源)>同步方法(在方法体之外)
3.线程通信
- synchronized只能实现资源同步,不能实现线程通信
- wait():方法表示线程等待,直到其他线程通知,与sleep()不同,会释放锁
- wait(long timeout): 等待多少毫秒
- notify():唤醒一个处于等待的线程
- notifyAll():唤醒同一个对象上所有调用wait()方法进入等待的线程,优先级高的线程优先调度
- 这四个方法均属于
Object
类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出非法监视器状态异常IllegalMonitorStateException
4.线程同步三大经典案例
1.银行取钱
package study03;
public class WithdrawMoney implements Runnable{
Account account;//账户
int withOut;//取出的钱
public void withOutMoney(int withOut){
Account.balance = Account.balance-withOut;
}
public WithdrawMoney(Account account,int withOut){
this.account=account;
this.withOut=withOut;
}
public void run(){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (account){//锁住账户对象,每次只能有一个线程进入代码块获取资源
if (Account.balance-this.withOut>0){
this.withOutMoney(this.withOut);
}else {
System.out.println(Thread.currentThread().getName()+"取钱"+this.withOut+",余额不足,取不到");
return;
}}
System.out.println(Thread.currentThread().getName()+"取出"+this.withOut);
System.out.println("当前账户余额为"+Account.balance);
}
public static void main(String[] args) {
Account account = new Account(1000);//创建一个拥有1000元的账户
new Thread(new WithdrawMoney(account,500),"小明").start();
new Thread(new WithdrawMoney(account,1000),"小红").start();
}
}
class Account{//账户类
static int balance;//余额
public Account(int balance){
Account.balance=balance;
}
}
2.购买车票
package study03;
public class BuyTicket implements Runnable{
static int ticket = 10;
public synchronized boolean hasTicket(){//使用synchronized关键字,只有获得实例对象的锁的线程才能访问,同步方法,
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"得到了第"+ticket--+"张票");
return true;
}else {
return false;
}
}
public void run(){//不建议直接锁住run方法,这样只有一个对象时可能会导致多线程变为单线程
while(this.hasTicket()){
try {
Thread.sleep(1000);//模拟延时,扩大不安全性的发生
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
BuyTicket a = new BuyTicket();//一份资源,三份并发
new Thread(a,"小明").start();
new Thread(a,"我").start();
new Thread(a,"黄牛").start();
}
}
3.ArrayList集合
ArrayList再高并发中是不安全的
使用Lock锁,实现同步
package study03;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
public class Aggregate {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
ReentrantLock lock = new ReentrantLock();//创建一个Lock锁,同时只能有一个线程能获得该锁的对象
for (int i = 0; i <100 ; i++) {
new Thread(()->{
try{
lock.lock();//获取Lock对象的锁,只有获得该锁的对象的线程才能继续访问资源
list.add(Thread.currentThread().getName());
}finally{
lock.unlock();//释放锁
}
}).start();
}
try {
Thread.sleep(5000);
//remove(int index)移除指定下标的元素并返回被移除的元素
System.out.println(list.get(list.size()-1));
//获取集合中最后一个元素
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4.安全的集合
CopyOnWriteArrayList类
package study03;
import java.util.concurrent.CopyOnWriteArrayList;
public class SafeAggregate {
//CopyOnWriteArrayList是线程安全的集合,该类不需要我们再去实现线程同步
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
for (int i = 0; i <10000 ; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(5000);
System.out.println(list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七.生产者与消费者模型
1.管程法
package study04;
public class TestPC {
//生产者与消费者模型,管程法
public static void main(String[] args) {
Container container = new Container();//一份资源,开启四条线程
new Thread(new Producer(container),"小明").start();
new Thread(new Producer(container),"小明助手").start();
new Thread(new Consumer(container),"小红").start();
new Thread(new Consumer(container),"小红助手").start();
try {
Thread.sleep(3000);//等待所有线程执行完成
System.out.println("生产了"+Container.pTimes);
System.out.println("消费了"+Container.cTimes);
System.out.println("剩余产品数量"+container.getCount());
System.out.println("消费者一共等待"+Container.cWait+"次,生产者一共等待"+Container.pWait+"次");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//生产者
class Producer implements Runnable{
Container container;
public Producer(Container container){
this.container = container;
}
//生产者只负责生产产品
public void run() {
for (int i = 0; i <100 ; i++) {
container.push(new Product(Thread.currentThread().getName()+"生产的"+i));//生产
System.out.println(Thread.currentThread().getName()+"生产了一个"+i+"号产品,剩余"+container.getCount()+"个产品");
}
}
}
//消费者
class Consumer implements Runnable{
Container container;
public Consumer(Container container){
this.container = container;
}
//消费者只消费产品
public void run() {
for (int i = 0; i <100 ; i++) {//消费者光顾次数
String a = container.pop().getId();
System.out.println( Thread.currentThread().getName()+"取出一个"+a+"号产品,剩余"+container.getCount()+"个产品");
}
}
}
//产品
class Product{
private String id;
public Product(String id) {
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
//容器
class Container{
static int pTimes = 0;//成功生产次数
static int cTimes = 0;//成功消费次数
static int pWait = 0;//生产者等待次数
static int cWait = 0;//消费者等待次数
private Product[] products = new Product[10];//设置容量为10;
private Product product;//取出的产品
private int count = 0;//产品数量
//放入产品
public synchronized void push(Product product){
if (count>=products.length){
//容器满了,停止生产
try {
this.wait();//此时会交出对象的锁,等待通知后苏醒,且再次得到锁后继续往下走
pWait++;//生产者等待一次,此次生产失败
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {//如果不要else就只能实现两条线程的同步,多加线程会出问题,或者如果不加else,也可以把notifyAll()改成notify()
//容器有空位
products[count] = product;
count++;
pTimes++;
//生产了产品通知消费者消费
this.notifyAll();
}
}
//取出产品
public synchronized Product pop(){
if (count<=0){
//没有产品,等待生产者生产
try {
this.wait();//消费者等待
cWait++;//消费者等待一次消费者消费失败,
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {//如果不要else就只能实现两条线程的同步,多加线程会出问题,或者如果不加else,也可以把notifyAll()改成notify()
//如果有产品
count--;
product = products[count];
cTimes++;
//消费一个产品,通知生产这生产
this.notifyAll();
}
return product;
}
public int getCount(){
return count;
}
}
2.信号灯法
package study04;
//生产者与消费者模型之信号灯法
public class TestPC02 {
public static void main(String[] args) {
TV tv = new TV();//容器
new Thread(new Performer(tv)).start();
new Thread(new Audience(tv)).start();
}
}
//消费者/观众
class Audience implements Runnable{
TV tv;
public Audience(TV tv){
this.tv=tv;
}
public void run(){
//消费者消费
for (int i = 0; i < 100; i++) {
tv.consume();
}
}
}
//生产者/演员
class Performer implements Runnable{
TV tv;
public Performer(TV tv){
this.tv=tv;
}
public void run(){
//生产者生产
for (int i = 0; i < 100; i++) {
if (i%2==0){
tv.produce(new Program("斗罗大陆"));
}else {
tv.produce(new Program("西行纪"));
}
}
}
}
//产品/节目
class Program{
String name;
public Program(String name){
this.name = name;
}
}
//容器/TV
class TV{
Program program ;
boolean flag = true;
//true 消费者等待,生产者生产
//false 生产者等待 ,消费者消费
public synchronized void produce(Program program){
if (flag==false){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.program = program;
System.out.println("生产者生产了"+this.program.name);
this.flag = !this.flag;
this.notifyAll();//通知消费者消费
}
public synchronized Program consume(){
if (flag==true){
try {
this.wait();//生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.flag = !this.flag;//消费者消费完毕
this.notifyAll();
System.out.println("消费者消费了"+this.program.name);
return this.program;
}
}
八,线程池
1.线程池的创建
创建方式一:
通过工具类Executors
package study05;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//线程池,创建方式1:
public class ThreadPool {
public static void main(String[] args) {
//创建一个线程次,容量为3,核心线程数3,最大线程数3,等待队列最多2147483647(默认)
//最大可容纳线程数=最大线程数+等待线程数
ExecutorService service = Executors.newFixedThreadPool(3);
//执行
for (int i = 0; i <100 ; i++) {
service.execute(()-> System.out.println(Thread.currentThread().getName()));
}
//关闭连接
service.shutdown();
}
}
#创建方式二:
package study05;
import java.util.concurrent.*;
public class ThreadPool02 {
public static void main(String[] args) {
//核心线程数,最大线程数,存活时间,时间单位,等待队列(队列长度),线程工厂,拒绝策略
//完整写法:
//ExecutorService service = new ThreadPoolExecutor(3,5,1L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//核心部分:
ExecutorService service = new ThreadPoolExecutor(3, 5, 1L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
for (int i = 0; i < 8; i++) {
service.execute(()-> System.out.println(Thread.currentThread().getName()));
}
service.shutdown();
}
}
九,扩展内容–Lambda表达式
关于Lambda的使用有兴趣的读者可以点击下面的链接阅读我的另一篇文章:
java Lambda的使用
创作不易,对您有所帮助的话可以点赞支持下,Thanks♪(・ω・)ノ