1、线程
线程(Thread):线程是进程中最小的调度的单元(单位),cpu控制的最小的执行单元。轻量级的进程。任何一个程序都至少有一个线程在用,多个线程共享内存。多线程切换消耗的资源少。
并发:在同一时间间隔内,同时有多个线程运行。
并行:在同一时刻,同时有多个线程运行。
2、如何创建线程
1、继承Thread类 :受限于java单继承
重写run方法,创建线程对象,启动线程(start())
2、实现Runnable接口:(常用) 继承类,实现多个接口,实现资源的共享;推荐使用
实现接口,重写run方法
3、实现Callable接口:可以获取线程的返回值
public class MyCall implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("线程在执行的任务");
return Thread.currentThread().getName();
}
public static void main(String[] args) {
// 创建Callable接口的实例
MyCall myCall=new MyCall();
// FutureTask 实例
FutureTask<String> task=new FutureTask<>(myCall);
Thread thread=new Thread(task);
thread.start();
}
}
3、Thread类API
方法 | 描述 |
---|---|
public static Thread currentThread() | 返回当前的线程 |
public final String getName() | 返回线程名称 |
public final void setPriority(int priority) | 设置线程优先级 |
public void start() | 开始执行线程 |
public static void sleep(long m) | 使目前的线程休眠m毫秒 |
public final void yield() | 暂停目前的线程,运行其他 线程 |
public void run() | 线程要执行的任务 |
- setPriority :调整线程优先级,Java线程有优先,优先级高的线程会获得较多的运行机会。
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
-
sleep():线程休眠,会让当前线程处于阻塞状态,指定时间过后,线程就绪状态
-
yield():暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
-
interrupt:中断线程,仅仅发送了一个中断的信号,当碰到wait(),sleep方法时,清除中断标记,抛出异常。
-
setDaemon:设置线程为后台(守护)线程。
sleep()和yield()的区别
sleep():线程休眠,时间到后会自动唤醒变成就绪状态 yield(): 线程礼让,暂停当前线程的执行,让出时间片,变成就绪状态,再和其他线程抢时间片
4、线程状态
- 新建:new Thread()
- 就绪:start()
- 运行:抢到时间片后
- 阻塞:调用sleep(),wait(),join()方法
- 死亡:正常结束或者异常退出
5、多线程数据安全问题
数据安全问题
- 条件1:多线程并发。
- 条件2:有共享数据。
- 条件3:共享数据有修改的行为。
示例:模拟多个线程实现取钱操作
- 创建Account类
- 创建自定义线程类实现取款操作
- 创建多个线程实现取款,观察数据的变化
Account类:
package com.bai.work.account;
public class Account {
private String no;
private int doller;
public Account() {
}
public Account(String no, int doller) {
this.no = no;
this.doller = doller;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public int getDoller() {
return doller;
}
public void setDoller(int doller) {
this.doller = doller;
}
public void getmoney(int money){
int before=this.doller;
int after=before-money;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
this.doller=after;
System.out.println(Thread.currentThread().getName()+"取了"+money+"-----余额:"+this.doller);
}
}
AccountThread类
package com.bai.work.account;
public class AccountThread extends Thread{
Account account;
public AccountThread(Account account) {
this.account = account;
}
@Override
public void run() {
account.getmoney(500);
}
}
Test测试类
package com.bai.work.account;
public class AccountTest {
public static void main(String[] args) {
Account account=new Account("66666",2000);
AccountThread a1=new AccountThread(account);
AccountThread a2=new AccountThread(account);
AccountThread a3=new AccountThread(account);
a1.start();
a2.start();
a3.start();
}
}
6、线程同步
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
线程同步的利弊
- 好处:解决了线程同步的数据安全问题
- 弊端:当线程很多的时候,每个线程都会去判断同步上面的这个锁,很耗费资源,降低效率
实现同步的方式:
- 基于synchornized(同步)实现
同步代码块
synchronized(this){ 方法体 }
普通同步方法:
修饰符 synchronized 返回值类型 方法名(形参列表){
方法体
}
静态同步方法:
修饰符 synchronized static 返回值类型 方法名(形参列表){
方法体
}
-
基于Lock实现
public void getMoney(double m) {
lock.lock();
double befor = this.money;
double after = befor - m;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
this.money = after;
System.out.println(Thread.currentThread().getName() + "取钱:" + m + "----余额:" + this.money);
lock.unlock();
}
-
synchronized与Lock的对比
-
相同点:用来实现同步
-
不同点:
- synchronized:关键字,同步代码块,同步实例方法,同步静态方法,自动解锁,jdk1.6在之后对synchronized进行了优化,性能得到了提升。
- Lock:接口,提供的有各种实现类,手动的加锁和解锁,使用try和finally,性能更好,灵活性更高
7、死锁
死锁:当多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,从而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况。
package com.bai.java2.test;
public class Test8 {
public static void main(String[] args) {
Object lipstick = new Object();//口红
Object mirror = new Object();//镜子
new Thread(new Runnable() {
@Override
public void run() {
synchronized (lipstick) {
System.out.println(Thread.currentThread().getName() + "拥有口红");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "还想拥有镜子");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (mirror) {
System.out.println(Thread.currentThread().getName() + "拥有镜子");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
synchronized (lipstick) {
System.out.println(Thread.currentThread().getName() + "还想拥有口红");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
}).start();
}
}
8、线程通信
方法 | 描述 |
---|---|
public final void wait() | 线程等待(阻塞) ,释放锁 ,用在同步方法中的。 |
public final void wait(long timeout) | 线程等待,并指定等待时间,以 毫秒为单位 |
public void notify() | 唤醒一个等待的线程 |
public void notifyAll() | 唤醒全部等待的线程 |
wait方法和notify方法不是通过线程对象调用,而是在加锁的对象上进行调用
Object o = new Object();
o.wait(); //让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止
o.notify(); //唤醒正在o对象上等待的线程。
o.notifyAll(); //这个方法是唤醒o对象上处于等待的所有线程
public static void main(String[] args) {
// 制水机:生成水
// 某同学:喝水
final Object obj = new Object();//锁对象
new Thread(new Runnable() {//制水机:生成水
@Override
public void run() {
synchronized (obj) {
System.out.println("制水机正在生成水,请稍后...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("储水罐已满");
try {
obj.wait();// 在该锁上活动的线程处于等待状态,释放锁
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("制水线程已经可以继续制水了");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
System.out.println("某某同学正在接水...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("储水罐的水已经空了");
obj.notify();//唤醒在该对象上等待的线程
System.out.println("唤醒等待的线程");
}
}
}).start();
}
9、线程池
线程池就是首先创建一些线程,他们的集合称之为线程池。
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后再需要执行新的任务时重用这些线程而不是新建线程。
创建线程池
public class Test1 {
public static void main(String[] args) {
/*
* 1:核心线程数
* 2:最大线程数:和核心线程数一致
* 3:闲置线程的存活时间:数字
* 4:时间单位
* 5:任务队列:阻塞队列
* 6、线程工厂:创建新线程的方式,使用默认工厂
* 7、拒绝策略:默认,抛出异常
*/
ThreadPoolExecutor pool=new ThreadPoolExecutor(
5,//核心线程数
5, //最大线程数:和核心线程数一致
60, //闲置线程的存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(20));//任务队列:阻塞队列
线程池的工作流程
- 核心线程数:5
- 存放要执行任务的等待队列:
- 最大线程数:20
- 拒绝策略:
- 提交任务,判断核心线程是否已满,如果未满,创建核心线程用来处理任务;如果已满进入下一步
- 判断阻塞队列是否已满,如果未满,将任务放入队列中,等待执行,如果已满,进入下一步
- 如果线程池中的最大线程未满,创建线程执行任务,如果已满,按照拒绝策略进行处理。
关闭线程方法:shutdown和shutdownNow的区别
- shutdown():仅仅是不再接受新的任务,以前的任务还会继续执行
- shutdownNow():立刻关闭线程池,如果线程池中还有缓存的任务没有执行,则取消执行,并返回这些任务