13_并发编程基础
2020-12-11 本文已影响0人
真是个点子王
线程的创建方式
- 1.直接定义一个类继承线程类Thread,重写run()方法,创建线程对象,并调用线程对象的start()方法启动线程;
- 2.定义一个线程任务类实现Runnable接口,重写run()方法,创建线程任务对象,把线程任务对象包装成线程对象,调用线程对象的start()方法启动线程;
- 3.实现Callable接口
方式一:继承Thread类
- 1.定义一个线程类继承Thread类
- 2.重写run()方法
- 3.创建一个新的线程对象
- 4.调用线程对象的start()方法启动线程
- 这种方法虽然编码简单,但是只能实现单继承。
public class ThreadDemo {
// 启动后的ThreadDemo当成一个进程。
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
// 3.创建一个线程对象
Thread t = new MyThread();
// 4.调用线程对象的start()方法启动线程,最终还是执行run()方法!
t.start();
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("子线程输出:"+i);
}
}
}
Thread类的API
1.public void setName(String name):给当前线程取名字。
2.public void getName():获取当前线程的名字。
-- 线程存在默认名称,子线程的默认名称是:Thread-索引。
-- 主线程的默认名称就是:main
3.public static Thread currentThread()
-- 获取当前线程对象,这个代码在哪个线程中,就得到哪个线程对象。
4.public static void sleep(long time)
-- 让当前线程休眠多少毫秒再继续执行
5.public Thread(String name)
-- 创建线程对象并取名字。
- 示例1:线程的命名
public class ThreadDemo {
// 启动后的ThreadDemo当成一个进程。
// main方法是由主线程执行的,理解成main方法就是一个主线程
public static void main(String[] args) {
// 创建一个线程对象
Thread t1 = new MyThread();
t1.setName("1号线程");
t1.start();
//System.out.println(t1.getName()); // 获取线程名称
Thread t2 = new MyThread();
t2.setName("2号线程");
t2.start();
//System.out.println(t2.getName()); // 获取线程名称
// 主线程的名称如何获取呢?
// 这个代码在哪个线程中,就得到哪个线程对象。
Thread m = Thread.currentThread();
m.setName("最强线程main");
//System.out.println(m.getName()); // 获取线程名称
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(m.getName()+"==>"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
方式二:实现Runnable接口
- 1.创建一个线程任务类实现Runnable接口
- 2.重写run()方法
- 3.创建一个线程任务对象
- 4.把线程任务对象包装成线程对象
- 5.调用线程对象的start()方法启动线程
Thread的构造器
-- public Thread(){}
-- public Thread(String name){}
-- public Thread(Runnable target){}
-- public Thread(Runnable target,String name){}
- 示例:
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new MyRunnable();
// 4.把线程任务对象包装成线程对象.且可以指定线程名称
// Thread t = new Thread(target);
Thread t = new Thread(target,"1号线程");
// 5.调用线程对象的start()方法启动线程
t.start();
Thread t2 = new Thread(target);
// 调用线程对象的start()方法启动线程
t2.start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
- 匿名内部类的简化写法
public class ThreadDemo02 {
public static void main(String[] args) {
// 创建一个线程任务对象(注意:线程任务对象不是线程对象,只是执行线程的任务的)
Runnable target = new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
};
// 把线程任务对象包装成线程对象.且可以指定线程名称
Thread t = new Thread(target);
// 调用线程对象的start()方法启动线程
t.start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}).start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
特点
- 线程任务只实现了Runnable接口,可以继续继承其他的类,而且可以实现其他接口;
- 同一个线程任务可以被包装成多个线程对象;
- 适合多个线程去共享一个资源;
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立;
- 线程池可以放入实现Runnable或Callable线程任务对象;
- 不能直接得到线程执行的结果。
方式三:实现Callable接口
- 1.定义一个线程任务类型实现Callable接口,声明线程的执行结果;
- 2.重写线程任务类的call方法,这个方法可以直接返回执行结果;
- 3.创建一个Callable的线程任务对象;
- 4.把Callable的线程任务对象包装成一个未来任务对象;
- 5.把未来任务对象包装成一个线程对象;
- 6.调用线程的start()方法启动线程。
- 示例:
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个Callable的线程任务对象
Callable call = new MyCallable();
// 4.把Callable任务对象包装成一个未来任务对象
// -- public FutureTask(Callable<V> callable)
// 未来任务对象是啥,有啥用?
// -- 未来任务对象其实就是一个Runnable对象:这样就可以被包装成线程对象!
// -- 未来任务对象可以在线程执行完毕之后去得到线程执行的结果。
FutureTask<String> task = new FutureTask<>(call);
// 5.把未来任务对象包装成线程对象
Thread t = new Thread(task);
// 6.启动线程对象
t.start();
for(int i = 1 ; i <= 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " + i);
}
// 在最后去获取线程执行的结果,如果线程没有结果,让出CPU等线程执行完再来取结果
try {
String rs = task.get(); // 获取call方法返回的结果(正常/异常结果)
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1.创建一个线程任务类实现Callable接口,申明线程返回的结果类型
class MyCallable implements Callable<String>{
// 2.重写线程任务类的call方法!
@Override
public String call() throws Exception {
// 需求:计算1-10的和返回
int sum = 0 ;
for(int i = 1 ; i <= 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " + i);
sum+=i;
}
return Thread.currentThread().getName()+"执行的结果是:"+sum;
}
}
特点
- 线程任务只实现了Callable接口,可以继续继承其他的类,而且可以实现其他接口;
- 同一个线程任务可以被包装成多个线程对象;
- 适合多个线程去共享一个资源;
- 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立;
- 线程池可以放入实现Runnable或Callable线程任务对象;
- 可以直接得到线程执行的结果。
线程安全问题
- 多个线程同时操作同一个共享资源的时候可能会出现线程安全问题。
//Account.java
public class Account {
private String cardId;
private double money;
public void draw(double money) {
System.out.println("现在取钱的用户:"+Thread.currentThread().getName());
//显示谁来取钱
if( this.money >= money){
System.out.println("余额充足,"+Thread.currentThread().getName() + "取走:" + money);
this.money -= money;
System.out.println(Thread.currentThread().getName()+"取款后的余额为:"+this.money);
}else{
System.out.println("余额不足:"+Thread.currentThread().getName());
}
}
public Account() {
}
public Account(String cardId, double money) {
this.cardId = cardId;
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"cardId='" + cardId + '\'' +
", money=" + money +
'}';
}
public String getCardId() {
return cardId;
}
public void setCardId(String cardId) {
this.cardId = cardId;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
//DrawThread.java
public class DrawThread extends Thread{
private Account account;
public DrawThread(Account account,String name){
super(name);
this.account = account;
}
@Override
public void run(){
account.draw(1000000);
}
}
//ThreadSafe.java
public class ThreadSafe {
public static void main(String[] args) {
Account account = new Account("ICBC-110",1000000);
//创建一个账户
Thread t1 = new DrawThread(account,"小红");
// 小红进程
Thread t2 = new DrawThread(account,"小明");
// 小明进程
t1.start();
t2.start();
}
}
线程同步
方法一:同步代码块
- 作用:把出现线程安全问题的核心代码给上锁,每次只能一个线程进入执行完毕以后自动解锁,其他线程才可以进来执行。
- 格式:
synchronized(锁对象){
// 访问共享资源的核心代码
}
锁对象:理论上可以是任意的“唯一”对象即可。
原则上:锁对象建议使用共享资源。
-- 在实例方法中建议用this作为锁对象。此时this正好是共享资源!必须代码高度面向对象
-- 在静态方法中建议用类名.class字节码作为锁对象。
public void drawMoney(double money) {
String name = Thread.currentThread().getName();
synchronized (this){
if(this.money >= money){
System.out.println(name+"来取钱,吐出:"+money);
this.money -= money;
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}
}
方法二:同步方法
- 作用:把出现线程安全问题的核心方法给锁起来,每次只能一个线程进入访问,其他线程必须在方法外面等待。
- 用法:直接给方法加上一个修饰符 synchronized。
- synchronized修饰方法默认是以当前对象作为锁的对象。
public synchronized void drawMoney(double money) {
String name = Thread.currentThread().getName();
if(this.money >= money){
System.out.println(name+"来取钱,吐出:"+money);
this.money -= money;
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}
方法三:Lock显式锁
- java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大
public class Account {
private double money ; // 余额
private String cardId ;
// 创建一把锁对象:因为账户对象对于小明小红是唯一的,所以这里的锁对象对于小明小红也是唯一的
private final Lock lock = new ReentrantLock();
public void drawMoney(double money) {
// 1.先拿到是谁来取钱:取当前线程对象的名称
String name = Thread.currentThread().getName();
lock.lock(); // 上锁~!
try{
if(this.money >= money){
// 3.余额足够
System.out.println(name+"来取钱,吐出:"+money);
// 4.更新余额
this.money -= money;
// 5.输出结果
System.out.println(name+"来取钱"+money+"成功,取钱后剩余:"+this.money);
}else{
// 6.余额不足
System.out.println(name+"来取钱,余额不足,剩余"+this.money);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock(); // 解锁~!
}
}
线程通信
- 核心方法
public void wait(): 让当前线程进入到等待状态 此方法必须锁对象调用.
public void notify() : 唤醒当前锁对象上等待状态的某个线程 此方法必须锁对象调用
public void notifyAll() : 唤醒当前锁对象上等待状态的全部线程 此方法必须锁对象调用
模拟案例
- 小明和小红有一个共同账户:共享资源
- 他们有3个爸爸(亲爸,岳父,干爹)给他们存钱。
- 模型:小明和小红去取钱,如果有钱就取出,然后等待自己,唤醒他们3个爸爸们来存钱他们的爸爸们来存钱,如果发现有钱就不存,没钱就存钱,然后等待自己,唤醒孩子们来取钱.做整存整取:10000元。
// ThreadCommunication.java
public class ThreadCommunication {
public static void main(String[] args) {
Account account = new Account(0, "ICBC-111");
Thread xiaohong = new DrawThread(account, "小红");
Thread xiaoming = new DrawThread(account, "小明");
Thread qindie = new SaveThread(account, "亲爹");
Thread gandie = new SaveThread(account, "干爹");
Thread yuefu = new SaveThread(account, "岳父");
xiaohong.start();
xiaoming.start();
qindie.start();
gandie.start();
yuefu.start();
}
}
//Account.java
public class Account {
private int money;
private String cardID;
public synchronized void drawMoney(int money) {
String name = Thread.currentThread().getName();
try {
if(this.money >= 10000){
this.money -= money;
System.out.println(name +":取钱成功,余额:"+this.money);
this.notifyAll();
this.wait();
}else{
System.out.println(name +":余额不足");
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void saveMoney(int money) {
String name = Thread.currentThread().getName();
try {
if(this.money >= 10000){
System.out.println(name +":余额充足,余额:"+ this.money);
this.notifyAll();
this.wait();
}else{
this.money += money;
System.out.println(name +":存钱成功,余额:"+ this.money);
this.notifyAll();
this.wait();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public Account() {
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
public String getCardID() {
return cardID;
}
public void setCardID(String cardID) {
this.cardID = cardID;
}
public Account(int money, String cardID) {
this.money = money;
this.cardID = cardID;
}
}
//DrawThread.java
public class DrawThread extends Thread {
Account account;
DrawThread(Account account,String name){
super(name);
this.account = account;
}
@Override
public void run(){
while (true){
try {
Thread.sleep(3000);
account.drawMoney(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//SaveThread.java
public class SaveThread extends Thread {
private Account account;
SaveThread(Account account,String name){
super(name);
this.account = account;
}
@Override
public void run(){
while (true){
try {
Thread.sleep(3000);
account.saveMoney(10000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
线程状态
- 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中
java.lang.Thread.State
这个枚举中给出了六种线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典教法) |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
Timed Waiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |

线程池
- 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程都需要时间,线程也是属于宝贵的系统资源。
- 线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。
- 优势:
- 1.降低资源消耗:减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可以执行多个任务;
- 2.提高响应速度:不需要频繁的创建线程,如果有线程可以直接用,不会出现系统僵死;
- 3.提高线程的可管理性(线程池可以约束系统最多只能有多少个线程,不会因为线程过多而死机)
创建线程池示例
- Runnable示例
public class ThreadPoolsDemo02 {
public static void main(String[] args) {
// a.创建一个线程池,指定线程的固定数量是3.
// new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
ExecutorService pools = Executors.newFixedThreadPool(3);
// b.创建线程的任务对象。
Runnable target = new MyRunnable();
// c.把线程任务放入到线程池中去执行。
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 提交任务,此时会创建一个新线程,自动启动线程执行!
pools.submit(target); // 不会再创建新线程,会复用之前的线程来处理这个任务
pools.shutdown(); // 等待任务执行完毕以后才会关闭线程池
//pools.shutdownNow(); // 立即关闭线程池的代码,无论任务是否执行完毕!
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0 ; i < 5 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => "+i);
}
}
}
- Callable示例
public class TestCallableThreadPool {
public static void main(String[] args) {
// 创建一个线程池,固定线程数为3
ExecutorService pools = Executors.newFixedThreadPool(3);
Future<String> t1 = pools.submit(new MyCallable(50));
Future<String> t2 = pools.submit(new MyCallable(100));
Future<String> t3 = pools.submit(new MyCallable(200));
Future<String> t4 = pools.submit(new MyCallable(300));
try {
System.out.println(t1.get());
System.out.println(t2.get());
System.out.println(t3.get());
System.out.println(t4.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
pools.shutdown();
}
}
}
class MyCallable implements Callable<String>{
private int n;
MyCallable(int n){
this.n = n;
}
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1 ; i <= n ;i++){
sum += i;
}
return Thread.currentThread().getName() + " - Sum = " + sum;
}
}