多线程
进程和线程
-
进程process
每个程序运行就会创建一个进程
进程是由操作系统管理
每个进程独享一段内存空间,进程之间互不干扰
-
线程Thread
线程是进程的组成单元
一个进程可以创建多个线程
多个线程之间共享同一个进程资源
例如:一个操作比作一家工厂,进程相当于车间,线程相当于一个车间里多条生产线
并行和并发
-
并行
多个线程同时都在运行
-
并发
并行的多个线程在同一时间点访问同一个资源(执行同一个方法或访问同一个属性),产生并发可能导致数据不一致(混乱)
例如:火车票抢票,秒杀,
-
多线程好处
加快程序运行速度
防止程序阻塞
-
多线程坏处
产生并发现象
同步和异步
-
同步(线程安全)
多个线程排队执行,称为同步
同一个时间点只有一个程序在执行
不会产生并发
执行效率不高(兼顾效率,只对会造成数据混乱的代码同步)
-
异步(线程不安全)
多个线程同时执行
可能会导致并发
执行效率比较高
例如:煮饭,如果只有一个锅,只能一个菜一个菜炒,这种方式叫同步
两个锅,一个汤锅,一个炒锅,一边炖汤,一边炒菜,这种方式叫异步
同步(线程安全)的类:String、Buffer、ConcurrenHashMap
异步(线程不安全)的类:StringBuilder、HashMap
创建和执行线程
方式一 :继承Thread类
-
继承Thread类
-
重写Thread类中的 run() 方法
-
调用线程类的 start() 方法
start() 方法是启动线程,并不一定立刻执行,什么时候执行这个线程是由进程调度的
start() 方法相当于告诉进程,我已经准备好了
-
不能去调用 run() 方法,如果调 run() 就没有线程效果
-
Thread1线程类
public class Thread1 extends Thread{ public Thread1(String name) { super(name); } @Override public void run() { for (int i = 0; i < 20; i++) { try { //Thread.sleep();让线程休眠 单位:毫秒 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getName() + ":" + i); } } }
-
Thread1Test测试类
public class Thread1Test { public static void main(String[] args) { //创建线程 Thread1 thread1 = new Thread1("线程1"); Thread1 thread2 = new Thread1("线程2"); //两个线程同时在执行(并行),打出来的结果 线程一 和 线程二 有交叉 thread1.start(); thread2.start(); //不会等待上面的两个执行完之后才会执行而是用start方法启动线程后,就不管了,for循环立刻开始 for (int i = 0; i < 20; i++) { System.out.println("main的主线程在执行" + i); } } }
-
Thread 类常用的方法
/* Thread 类的方法*/ thread1.getName();//返回线程的名称 thread1.setName("线程名称 :");//设置线程名称 thread1.getPriority();//返回线程的优先级(设置线程被执行的几率) thread1.setPriority(6);//改变线程的优先级 范围:1~10
方式二:实现Runnable接口
-
实现Runnable接口
-
创建一个Thread对象,构造方法的参数是实现了Runnable接口类的对象
-
调用Thread类的start()方法启动接口
-
Thread2类
public class Thread2 implements Runnable{ @Override public void run() { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i < 100; i++) { //Thread.currentThread().getName()获取当前运行线程的名字 System.out.println(Thread.currentThread().getName() + ":" + i); } } }
-
测试代码
public static void main(String[] args) { //实现runnable接口的线程要创建Thread对象 Thread2 t1 = new Thread2(); Thread2 t2 = new Thread2(); //Thread(t1).start();使用Thread类的start()方法启动线程 new Thread(t1).start(); new Thread(t2).start(); //main方法也是也是一个线程,线程名叫:main System.out.println(Thread.currentThread().getName()); }
方式三:实现Callable接口
-
实现Callable的接口,实现call的方法
-
创建FutureTask对象,构造方法是实现了Callable接口对象
-
创建Thread对象,勾构造方法的参数FutureTask对象
-
调用Thread类的start()方法启动线程
-
Thread3 implements Callable 类 实现接口
public class Thread3 implements Callable <String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
return Thread.currentThread().getName();
}
}
- 测试代码
public class Thread3Test {
public static void main(String[] args) {
Thread3 call1 = new Thread3();
//创建FutureTask对象,构造方法的参数是实现了Callable接口的对象
FutureTask<String> futureTask1 = new FutureTask<String>(call1);
//创建Thread对象,勾构造方法的参数FutureTask对象
Thread t1 = new Thread(futureTask1);
//启动线程
t1.start();
Thread3 call2 = new Thread3();
FutureTask<String> futureTask2 = new FutureTask<String>(call2);
Thread t2 = new Thread(futureTask1);
t2.start();
}
}
线程的生命周期
线程的生命周期包含5个阶段,包括:新生、就绪、运行、阻塞、终止
-
New 新生状态
当线程被实例化后new Thread( ), 就进入新生状态
-
Runnable 就绪状态
当线程对象执行了start( ) 后进入就绪状态
就绪状态的线程可以执行,但不会执行,等待线程调度触发它执行
这时候线程处于等待CPU分配资源阶段,谁先抢到CPU资源,谁开始执行
-
Runing 运行状态
当线程被激活执行,这时候就是运行状态,执行run() 方法
不一定在一个执行周期能执行完成run( ) 方法,如果cpu时间片消耗完毕或者线程的yield( )方法被调用,线程就会退回就绪状态,等待下一次运行
-
Blocked 阻塞状态
阻塞状态,暂停运行,正在运行的线程执行了sleep( )方法或wait()方法,就进入到阻塞状态
- 因为sleep( )阻塞的线程,sleep时间到了后,重写回到就绪状态
- 因为wait( )方法阻塞的线程,等待notify( )或者notifyAll( )被执行,才重新回到就绪状态
-
Terminated 终止状态
终止状态,run( ) 正常执行完毕,就进入到终止状态,线程生命周期就结束
线程不安全(并发)
有一个银行账号,两个线程同时取钱,有时候可能出现余额乱套
- Account账号类
账号属性
余额属性
生成get,set方法
生成两个参数的构造方法
public class Account {
private String accountNu;//账号
private double blance;//余额
public Account() {
}
public Account(String accountNu, double blance) {
this.accountNu = accountNu;
this.blance = blance;
}
public void setAccountNu(double accountNu) {
this.accountNu = accountNu;
}
public double getBlance() {
return blance;
}
public void setBlance(double blance) {
this.blance = blance;
}
}
-
DrawThrerad 取钱类
继承Thread
有两个属性,账号和取款金额
创建构造方法
重写run方法,取钱逻辑放在run方法里
public class DrawThrerad extends Thread {
private Account account; //账号
public double drawAmount; //取款金额
public DrawThrerad(Account account, double drawAmount) {
this.account = account;
this.drawAmount = drawAmount;
}
@Override
public void run() {
//先休眠,让线程并发
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//用synchronized关键字给这段代码加锁(让这段代码变为同步的代码),小括号里面的就是加锁对象
synchronized (account){
//取钱金额小于等于原来的余额
if(drawAmount <= account.getBlance()){
System.out.println(Thread.currentThread().getName() + "ATM吐出:" + drawAmount + "元");
//重新设置余额为原来的余额减去取款金额
account.setBlance(account.getBlance() - drawAmount);
System.out.println(Thread.currentThread().getName() + ", 余额是:" + account.getBlance());
}else{
System.out.println(Thread.currentThread().getName() + ", 余额不足,取钱失败");
}
}
}
}
- 测试类
public static void main(String[] args) {
Account account = new Account("88888888",1000);
DrawThrerad t1 = new DrawThrerad(account,800);
DrawThrerad t2 = new DrawThrerad(account,600);
t1.start();
t2.start();
}
- 测试结果
可能会出现(并发时产生)余额为负数的情况
解决线程不安全问题
把并发代码(或者是方法)添加一个同步的关键字:synchronized
synchronized关键字还可以用于修饰方法,如果一个方法加同步,这个方法不会出现并发调用情况
死锁
两个线程要相互的等到对方锁定的资源,两个线程都没有办法继续执行,程序就一直处于等待状态,不会结束,也不能成功运行,这种现象就是死锁
例如:两个人吃牛排,只有一套刀叉。A拿到叉子,B拿到刀,两个人都没有办法吃的到牛排,只能无限的等下去
- 死锁形成的条件
- **互斥使用调用:**当资源被一个线程使用时,另一个线程不能使用
- **不可抢占:**资源请求者不能强制从资源的占用者手中夺取资源,只能由资源的占用着主动释放
- **请求和保持:**当资源的请求者在请求资源的时候,已经占用的资源不会释放
- **循环等待:**A线程持有资源1要等待资源2,B线程持有资源2要等待资源1
会死锁的代码
锁定资源的顺序相反,造成循环等待
public class DeadLock {
public static void main(String[] args) {
Object knife = new Object(); //刀
Object fork = new Object(); //叉
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();//t1线程名字
//对刀加锁(同步)
synchronized (knife){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + ", 获取到了刀");
System.out.println(name + ", 等待叉子。。。");
//对叉子加锁
synchronized (fork){
System.out.println(name + ", 获得了叉");
}
}
}
});
t1.start();//启动第一个线程
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();//t2线程的名字
//对叉子加锁
synchronized (fork){
System.out.println(name + ", 获得了叉子");
System.out.println(name + ", 等待刀。。。");
//对刀加锁
synchronized (knife){
System.out.println(name + ", 获得了刀");
}
}
}
});
t2.start();//启动第二个线程
}
}
结果
避免死锁
- 以相同的舒徐去锁定资源
- 可以建立一个锁的对象,之只锁一个对象,不锁多个对象
叉子加锁
synchronized (fork){
System.out.println(name + ", 获得了叉子");
System.out.println(name + ", 等待刀。。。");
//对刀加锁
synchronized (knife){
System.out.println(name + ", 获得了刀");
}
}
}
});
t2.start();//启动第二个线程
}
}
结果
避免死锁
- 以相同的舒徐去锁定资源
- 可以建立一个锁的对象,之只锁一个对象,不锁多个对象