java学习第16天
多线程
进程和线程
-
进程Process
每一个程序运行就会创建一个进程。
进程是由操作系统管理
每一个进程独享一段内存空间,进程之间互不干扰。
-
线程Thread
线程是进程的组成单元
一个进程可以创建多个线程
多个线程之间共享同一个进程资源
例如:一个操作系统比做一家工厂,进程相当于车间,线程相当于一个车间里的多条生产线。
并行和并发
-
并行
多个线程是同时都在运行,称为并行
-
并发
并行的多个线程在同一时间点访问同一个资源(比如执行同一个方法,访问同一个属性),产生并发。
并发可能会导致数据不一致(数据混乱)
例如:火车票抢票;秒杀活动
多线程的好处:
- 加快程序运行的速度
- 防止程序的阻塞
多线程的坏处:
- 产生并发现象
同步和异步
-
同步(线程安全)
多个线程排队执行,称为同步
同一个时间点只有一个线程执行
不会导致并发
执行的效率不高(为了兼顾效率,一般只对会造成数据不一致的代码(线程)同步)
-
异步(非线程安全)
多个线程同时运行
可能会导致并发
执行效率比较高
例如:煮饭,如果只有一口锅,一个菜一个菜的炒,这种执行方式是同步
两口锅,一个炒锅,一个汤锅,一边炖汤,一边炒菜,这种叫异步
同步(线程安全):StringBuffer、ConcurrentHashMap
异步(非线程安全):StringBuilder,HashMap
创建和执行线程
方式一、继承Thread
-
继承Thread类
-
重写run方法
-
调用线程类的start()方法
start() 方法启动线程,并不一定会立即执行。什么时候执行这个线程,是由进程调度的。start()方法相当于告诉进程,我已经准备好执行了
千万不要调run()方法,如果调run(),方法就没有多线程的效果。
ThreadDemo1
package com.Hqyj.Thread;
public class ThreadDemo1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//sleep()让程序暂停一段时间,单位是毫秒
/*try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println("线程"+ getName() +"正在运行"+ i);
}
}
public ThreadDemo1(String name){
super(name);
}
}
- ThreadDemoTest
package com.Hqyj.Thread;
public class ThreadDemo1Test {
public static void main(String[] args) {
ThreadDemo1 thread1 = new ThreadDemo1("线程1");
ThreadDemo1 thread2 = new ThreadDemo1("线程2");
//两个线程同时执行(并行),所以打出来的结果线程1和线程2有交叉的现象
thread1.start();
thread2.start();
//不会等待上面的两个线程执行万之后在执行,而是用start方法启动线程之后,就不管了,for循环立即执行
for (int i = 0; i < 200; i++) {
System.out.println("主线程正在运行"+ i);
}
}
}
-
Thread类常用的方法
/** * Thread类的方法 */ thread1.getName();//返回线程的名称 thread1.setName("线程名称");//设置线程的名称 thread1.getPriority();//返回线程的优先级(设置线程被执行的几率) thread1.setPriority(1);//设置线程的优先级,优先级1-10
方式二、实现Runnable接口
- 实现Runnable接口,实现run方法
- 创建一个Thread对象,构造方法的参数是实现了Runnable接口类的对象
- 调用Thread对象的start方法启动
Thread2类
package com.Hqyj.Thread;
public class Thread2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
//Thread.currentThread() 获取到当前线程的对象
System.out.println("线程"+ Thread.currentThread().getName() +"正在运行"+i);
}
}
}
测试代码
package com.Hqyj.Thread;
public class Thread2Test {
public static void main(String[] args) {
//实现runnable接口的线程,要创建Thread对象,才能启动线程
Thread2 t1 = new Thread2();
Thread2 t2 = new Thread2();
new Thread(t1).start();//运行方式
new Thread(t2).start();
//main方法也是一个线程,线程名称叫:main
System.out.println(Thread.currentThread().getName());
}
}
方式三:实现Callable接口
- 实现Callable接口,实现call方法
- 创建FutureTask对象,构造方法是实现了Callable接口对象
- 创建Thread对象,构造方法的参数是FutureTask对象
- 调用start方法启动线程
Thread3 implement Callable
package com.Hqyj.Thread;
import java.util.concurrent.Callable;
public class Thread3 implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
Thread.sleep(2);
System.out.println("线程"+Thread.currentThread().getName()+"正在运行"+i);
}
return Thread.currentThread().getName();
}
}
测试代码
package com.Hqyj.Thread;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
Thread3 call1 = new Thread3();
//创建FutureTask对象,构造方法的参数是实现了Callable接口的对象
FutureTask<String> ft1 = new FutureTask<>(call1);
//创建Thread对象,构造方法的参数FutureTask对象
Thread t1 = new Thread(ft1);
//启动线程
t1.start();
Thread3 call2 = new Thread3();
FutureTask<String> ft2 = new FutureTask<>(call2);
Thread t2 = new Thread(ft2);
t2.start();
}
}
线程的生命周期
-
New 新生状态
新生状态,当线程对象被实例化后new Thread(),就进入到新生状态
-
Runnable 就绪状态
就绪状态,当线程对象执行了start()后进入到就绪状态
就绪状态的线程可以执行,但不会执行,等待线程调度触发它执行。
-
Running 运行状态
运行状态,当线程被激活执行,这时候线程就是运行状态,执行run()方法
不一定在一个执行周期能够执行完成run()方法,如果cpu的时间片消耗完毕或者线程的yield()方法被调用,线程会退回就绪状态,等待下一次运行
-
Blocked 阻塞状态
阻塞状态,暂停运行,正在运行的线程执行了sleep()方法或wait()方法就进入阻塞状态。
因为sleep()阻塞的线程,sleep()时间到了后,重新回到就绪状态
因为wait()方法阻塞的线程,就要等待notify()或notifyAll()被执行,才重新回到就绪状态。
-
Terminated 终止状态
终止状态,run()方法正常执行完毕,就进入到终止状态,线程的生命周期就结束。
线程不安全(并发)
假设有一个银行账号,有两个线程同时取钱,有可能出现余额为负的情况。
-
Account 账号类
账号属性
余额属性
相应的get、set方法
构造方法
package com.Hqyj.Test; public class Account { private String accountnum;//账号 private double blance;//余额 public Account() { } public Account(String accountnum, double blance) { this.accountnum = accountnum; this.blance = blance; } public String getAccountnum() { return accountnum; } public void setAccountnum(String accountnum) { this.accountnum = accountnum; } public double getBlance() { return blance; } public void setBlance(double blance) { this.blance = blance; } }
-
DrawThread 取钱类
继承Thread
有两个属性:账号和取款金额
创建构造方法
重写run()方法,取钱的逻辑方法run()方法里。
package com.Hqyj.Test; public class DrawThread extends Thread{ private Account account;//账号 private double drawAmount;//取款金额 public DrawThread(Account account,double drawAmount){ this.account = account; this.drawAmount = drawAmount; } @Override public void run() { //取款金额小于等于账号金额 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("余额不足,取款失败!"); } } }
-
测试类
package com.Hqyj.Test; public class DrawThreadTest { public static void main(String[] args) { Account account = new Account("88888888", 100000); DrawThread t1 = new DrawThread(account,100000); DrawThread t2 = new DrawThread(account,100000); t1.start(); t2.start(); } }
-
测试结果
可能会出现(并发产生时)余额为负的情况
Thread-0,ATM吐出100000.0元 Thread-1,ATM吐出100000.0元 Thread-0,余额是:0.0 Thread-1,余额是:-100000.0
解决线程不安全的问题
把并发代码(或方法)添加一个同步的关键字:synchronized
synchronized关键字还可以用于修饰方法,如果一个方法加了同步,这个方法就不会出现并发调用的情况。
private synchronized void draw(Account account,Double amount){
}
死锁
两个线程相互等待对方释放它锁定的资源,两个线程都没有办法继续执行,程序就一直处于等待状态,不会结束,也不能成功运行,这种现象就是死锁
例如:两个人吃牛排。只有一套刀叉,A拿到叉子,B拿到刀,两个人都没有办法吃到牛排,只能无限的等下去。
- 死锁形成的条件:
- 互斥使用。当一个资源被一个线程使用的时候,另一个线程不能使用。
- 不可抢占。资源的请求者不能强制从资源的占用者手中夺取资源。只能由资源的占用者主动释放。
- 请求和保持。当资源的请求者在请求其他资源的时候,已经占用的资源不会释放。
- 循环等待。A线程持有资源1,等待资源2,B线程持有资源2,等待资源1
会形成死锁的代码
锁定资源的顺序相反,就死锁
package com.Hqyj.Thread;
public class DeadLock {
public static void main(String[] args) {
Object knife = new Object();//刀
Object fork = new Object();//叉
Thread t1 = new Thread(() ->{
//t1线程名字
String name = Thread.currentThread().getName();
//对刀加锁(同步)
synchronized (knife){
System.out.println(name+"获取到了刀");
System.out.println(name+ "等待叉。。。");
//对叉加锁(同步)
synchronized (fork){
System.out.println(name+"获取到了叉");
}
}
});
t1.start();//启动第一个线程
Thread t2 = new Thread(() ->{
//t2线程名字
String name = Thread.currentThread().getName();
//对叉加锁(同步)
synchronized (fork){
System.out.println(name+"获取到了叉");
System.out.println(name+ "等待刀。。。");
//对刀加锁(同步)
synchronized (knife){
System.out.println(name+"获取到了刀");
}
}
});
t2.start();//启动第二个线程
}
}
结果:
Thread-1获取到了叉
Thread-0获取到了刀
Thread-1等待刀。。。
Thread-0等待叉。。。
避免死锁
- 以相同的顺序锁定资源
- 另外建立一个锁的对象,只锁一个对象,不锁多个对象