JAVA多线程

Java多线程

作者:Memory Wechat/QQ:574373426 学编程找星哥

1. 进程线程

1.1 什么是进程

正在进行中的程序,是系统进行资源分配的基本单位,目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分

进程是操作系统中运行的一个任务(一个应用程序运行在一个进程中)

进程是一块包含了某些资源的内存区域,操作系统利用进程把他们的工作划分为一些功能单元

进程中包含的一个或多个执行单元称为线程,进程还拥有一个私有的虚拟地址空间,该空间只能被他所包含的线程访问

线程只能属于一个进程并且它只能访问该进程所拥有的资源,当操作系统创建一个线程后,该进程会自动申请一个主线程

一个进程至少包含一个线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

1.2 什么是线程

一个线程是进程的一个顺序执行单元,也是CPU的基本调度单位

同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈

一个进程中可以包含多个线程

线程通常用于在一个程序中需要同步完成的多个任务情况,我们可以将每个任务定义为一个线程,使得他们统一 一起工作,同时执行,称为多线程

也可以使用单线程完成,但是多线程可以更快,比如下载文件

在这里插入图片描述

例如:

迅雷就是一个进程,当中多个下载任务就是多个线程

java虚拟机是一个进程,当中默认包含主线程(main),可以通过代码创建多个独立线程,与main并发执行

1.3 并发

多个线程“同时运行”只是我们感官上的一种表现,事实上线程是并发运行的

系统将时间划分为很多时间片段(时间片),尽可能均匀的分配给每一个线程,获取时间片的线程被CPU调用运行,其他线程全部等待,微观上走走停停,宏观上都在运行,这种现象叫并发,并发不是绝对意义上的同时运行。

以上为单核的情况,目前CPU都是多核的,能实现真正的并发

1.4 进程和线程的区别

  • 进程是操作系统资源分配的基本单位,而线程是cpu的基本调度单位
  • 一个程序运行后至少有一个进程
  • 一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义的
  • 进程间不能共享数据段地址,但同进程的线程之间是可以的

2. 线程的组成

2.1 线程的组成

任何一个线程都具有基本的组成部分

  • CPU时间片:操作系统(OS)会为每个线程分配执行时间
  • 运行数据
    • 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
    • 栈空间:存储线程需要使用的局部变量,每个线程都拥有独立的栈
  • 线程的逻辑代码

在这里插入图片描述

2.2 线程特点

  1. 线程抢占式执行 (100个任务分给5个人去执行 有的会分配有的不会分配效率低,可能一个员工执行100个任务)
    • 效率高
    • 可防止单一线程长时间独占CPU
  2. 单核CPU中,宏观上同时执行,微观上顺序执行

2.3 创建线程

创建线程的三种方式

  1. 继承Thread类,重写Run方法
  2. 实现Runnable接口
  3. 实现Callable接口 (jdk1.5以后新加的接口)

2.4 继承Thread类

2.4.1 创建和启动线程

案例1:主线程和子线程抢占时间片执行for循环

package com.gx;
/**
 * 线程类
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println("子线程......"+i);
        }
    }
}
package com.gx;

public class Test {
    public static void main(String[] args) {
        //1.创建线程对象
        //main方法是jvm的主线程,而我们创建的线程统称为子线程
        MyThread myThread = new MyThread();
        //2.启动线程
        myThread.start();
        //myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
        //3.主线程中创建一个for用来对比
        for (int i=0;i<50;i++){
            System.out.println("主线程======"+i);
        }
    }
}

案例2:多个个子线程同时执行的时候,不好区分到底是哪个子线程执行的,我们可以获取线程名

public class Test {
    public static void main(String[] args) {
        //1.创建线程对象
        //main方法是jvm的主线程,而我们创建的线程统称为子线程
        //创建2个子线程
        MyThread myThread = new MyThread();
        myThread.start();
        MyThread myThread1 = new MyThread();
        myThread1.start();
        //myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
        //3.主线程中创建一个for用来对比
        for (int i=0;i<50;i++){
            System.out.println("主线程======"+i);
        }
    }
}

在这里插入图片描述

2.4.2 获取和修改线程名称

获取线程ID获取线程名称备注
this.getId()this.getName()当前的方法是继承的Thread类里的方法,有局限性,只能在继承的方式中使用
Thread.currentThread().getId()Thread.currentThread().getName()
package com.gx;
/**
 * 线程类
 */
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            //第一种方式 this.getId() this.getName()
            //System.out.println("线程id:"+this.getId()+"线程名称:"+this.getName()+"子线程......"+i);
            //第二种方式 Thread.currentThread() 获取当前线程
            System.out.println(
                    "线程id:"+Thread.currentThread().getId()+
                            "线程名称:"+Thread.currentThread().getName()+
                            "子线程......"+i
            );
        }
    }
}
修改线程名称
线程对象.setName()
线程子类的构造方法赋值 public MyThread(String name){ super(name); }
package com.gx;

public class Test {
    public static void main(String[] args) {
        //1.创建线程对象
        //main方法是jvm的主线程,而我们创建的线程统称为子线程
        //创建2个子线程
        MyThread myThread = new MyThread("我的子线程1");
        //修改线程名称
        //myThread.setName("我的子线程1");
        myThread.start();
        MyThread myThread1 = new MyThread("我的子线程2");
        //myThread1.setName("我的子线程2");
        myThread1.start();
        //myThread.run(); 如果调用run方法则还是主线程去执行某一个类里的方法,就不是子线程执行
        //3.主线程中创建一个for用来对比
        for (int i=0;i<50;i++){
            System.out.println("主线程======"+i);
        }
    }
}
package com.gx;
/**
 * 线程类
 */
public class MyThread extends Thread{
    public MyThread(){

    }
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            //第一种方式 this.getId() this.getName()
            //System.out.println("线程id:"+this.getId()+"线程名称:"+this.getName()+"子线程......"+i);
            //第二种方式 Thread.currentThread() 获取当前线程
            System.out.println(
                    "线程id:"+Thread.currentThread().getId()+
                            "线程名称:"+Thread.currentThread().getName()+
                            "子线程......"+i
            );
        }
    }
}

2.4.3 案例

使用继承Thread类实现4个窗口各卖100张票

在这里插入图片描述

package com.gx;

/**
 * 卖票窗口类(线程类)
 */
public class TickeWin extends Thread {

    public TickeWin(){}
    public TickeWin(String name){
        super(name);
    }

    private int ticket =100; //票
    @Override
    public void run() {
        //卖票功能
        while (true){
            if (ticket<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
            ticket--;
        }
    }
}
package com.gx;

public class TestWin {
    public static void main(String[] args) {
        //创建四个窗口
        TickeWin w1 = new TickeWin("窗口1");
        TickeWin w2 = new TickeWin("窗口2");
        TickeWin w3 = new TickeWin("窗口3");
        TickeWin w4 = new TickeWin("窗口4");
        //启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

2.5 实现Runnable接口

package com.gx;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"......"+i);
        }
    }
}
package com.gx;

public class TestRunnable {
    public static void main(String[] args) {
        //1.创建MyRunnable对象,表示线程要执行的功能
        MyRunnable runnable = new MyRunnable();
        //2.创建线程对象
        Thread thread = new Thread(runnable,"我的线程1");
        //3.启动线程
        thread.start();

        //主线程
        for(int i=0;i<100;i++){
            System.out.println("main......"+i);
        }
    }
}

补充:采用匿名内部类

package com.gx;

public class TestRunnable {
    public static void main(String[] args) {
        //创建可运行对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<100;i++){
                    System.out.println(Thread.currentThread().getName()+"......"+i);
                }
            }
        };
        Thread thread = new Thread(runnable,"我的线程2");
        thread.start();

        for(int i=0;i<100;i++){
            System.out.println("main......"+i);
        }
    }
}

2.5.1 案例

#实现四个窗口共卖100张票

在这里插入图片描述

package com.gx.d2;

/**
 * 票类(共享资源)
 */
public class Ticket implements Runnable{
    private int ticket = 100;//100张票

    @Override
    public void run() {
        while (true){
            if (ticket<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
            ticket--;
        }
    }
}
package com.gx.d2;

public class TestTicket {

    public static void main(String[] args) {
        //1.创建票对象
        Ticket ticket = new Ticket();
        //2.创建线程对象
        Thread w1 = new Thread(ticket,"窗口1");
        Thread w2 = new Thread(ticket,"窗口2");
        Thread w3 = new Thread(ticket,"窗口3");
        Thread w4 = new Thread(ticket,"窗口4");
        //3.启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}
#此时多个窗口会卖出同一张票,后面的课程中解决

在这里插入图片描述

2.5.2 案例

#你和你女朋友共用一张银行卡,你向卡中存钱,你女朋友从卡里取钱
package com.gx.d2;
/**
 * 银行卡
 */
public class BankCard {
    private double money;//余额

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}
package com.gx.d2;
/**
 * 存钱
 */
public class AddMoney implements Runnable{
    
    private BankCard card;
    
    public AddMoney(BankCard card){
        this.card=card;
    }
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            card.setMoney(card.getMoney()+1000);
            System.out.println(Thread.currentThread().getName()+" 存了1000,余额是"+card.getMoney());
        }
        
    }
}
package com.gx.d2;

/**
 * 取钱
 */
public class SubMoney implements Runnable{
    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            if (card.getMoney()>=1000){
                card.setMoney(card.getMoney()-1000);
                System.out.println(Thread.currentThread().getName()+" 取了1000,余额是"+card.getMoney());
            }else {
                System.out.println("余额不足,请赶快存取");
                i--;
            }
        }
    }
}
package com.gx.d2;

public class TestBankCard {
    public static void main(String[] args) {
        //1.创建一张银行卡
        BankCard card = new BankCard();
        //2.创建存钱取钱
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        //3.创建两个线程
        Thread nan = new Thread(add,"男朋友");
        Thread nv = new Thread(sub,"女朋友");
        //4.启动线程
        nan.start();
        nv.start();
    }
}
#匿名内部类方式
package com.gx.d2;

public class TestBankCard2 {

    public static void main(String[] args) {
        BankCard card = new BankCard();
        //存钱
        Runnable add = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    card.setMoney(card.getMoney()+1000);
                    System.out.println(Thread.currentThread().getName()+" 存了1000,余额是"+card.getMoney());
                }
            }
        };
        //取钱
        Runnable sub = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    if (card.getMoney()>=1000){
                        card.setMoney(card.getMoney()-1000);
                        System.out.println(Thread.currentThread().getName()+" 取了1000,余额是"+card.getMoney());
                    }else {
                        System.out.println("余额不足,请赶快存取");
                        i--;
                    }
                }
            }
        };
        //创建线程对象并启动
        new Thread(add,"那朋友").start();
        new Thread(sub,"女朋友").start();
    }
}
#上述两个案例中存在线程安全同步问题,后期学了相关知识点,可以解决

3. 线程的状态

3.1 基本状态

在这里插入图片描述

3.2 线程常见方法

休眠

  • public static void sleep (long millis)
  • 当前线程主动休眠millis毫秒
package com.gx.d3;

public class SleepThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"......"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
     public static void main(String[] args) {
        SleepThread s1 = new SleepThread();
        s1.start();
        SleepThread s2 = new SleepThread();
        s2.start();
    }
}
package com.gx.d3;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TestSleep {
    public static void main(String[] args) {
        Thread  thread = new Thread(){
            @Override
            public void run() {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                while (true){
                    System.out.println(sdf.format(new Date()));
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
    }
}

放弃

  • public static void yield()

  • 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片

package com.gx.d3;

public class YieldThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"......"+i);
            //主动放弃cup
            Thread.yield();
        }
    }

    //测试
    public static void main(String[] args) {
        YieldThread y1 = new YieldThread();
        YieldThread y2 = new YieldThread();
        y1.start();
        y2.start();
    }
}
package com.gx.d3;

public class YildTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("t1开始跑步");
                for (int i=1;i<=400;i++){
                    System.out.println("t1:跑了"+i+"米");
                    if(i==200){
                        Thread.yield();
                    }
                }
                System.out.println("t1跑完了");
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                System.out.println("t2开始跑步");
                for(int i=1;i<=400;i++){
                    System.out.println("t2:跑了"+i+"米");
                }
                System.out.println("t2跑完了");
            }
        };
        t1.start();
        t2.start();
    }
}

加入

  • public final void join()

  • 允许其他线程加入到当前线程中,造成当前线程阻塞,常用于等待当前线程结束

package com.gx.d3;

public class JoinThread extends Thread{

    @Override
    public void run() {
        for(int i=0;i<30;i++){
            System.out.println(Thread.currentThread().getName()+"......"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        JoinThread j1 = new JoinThread();
        j1.start();
        //j1.join();//加入当前线程(main),并阻塞当前线程,直到加入线程执行完毕
        for (int i=0;i<20;i++){
            System.out.println(Thread.currentThread().getName()+"============"+i);
            Thread.sleep(100);
        }
    }

}
package com.gx.d3;

public class JoinTest {
    public static void main(String[] args) {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i <= 10; i++) {
                    System.out.println("t1:正在下载图片:" + i * 10 + "%");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("t1:图片下载完毕");
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                System.out.println("t2:等待t1图片下载完毕");
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2:显示图片");
            }
        };
        t1.start();
        t2.start();
    }
}

线程优先级

  • 线程对象.setPriority()
  • 线程优先级为 1-10,1最低,10最高,默认是5,优先级越高,表示获取CPU机会越多
  • 线程提供了3个常量来表示最低(Thread.MIN_PRIORITY),最高(Thread.MAX_PRIORITY),默认(Thread.NORM_PRIORITY)
package com.gx.d4;

/**
 * 线程优先级
 */
public class PriorityThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"=========="+i);
        }
    }
    public static void main(String[] args) {
        PriorityThread p1 = new PriorityThread();
        p1.setName("p1");
        PriorityThread p2 = new PriorityThread();
        p2.setName("p2");
        PriorityThread p3 = new PriorityThread();
        p3.setName("p3");
        //设置优先级
        p1.setPriority(1);
        p3.setPriority(10);
        //启动 如果没有设置优先级 大概率p3再最后执行完
        p1.start();
        p2.start();
        p3.start();
    }
}

守护线程

  • 线程对象.setDaemon(true) 设置为守护线程
  • 线程有两类,用户线程(前台线程),守护线程(后台线程)
  • 如果程序中所有前台线程都执行完毕了,后台线程会自动结束
  • 垃圾回收器线程属于守护线程
public class DeamonThread extends Thread{
    @Override
    public void run() {

        for (int i=0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"---------"+i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        //创建线程(默认为前台线程)
        DeamonThread d1 = new DeamonThread();
        //设置为守护线程  当主线程接收后 守护线程也会结束
        d1.setDaemon(true);
        d1.start();
        //主线程也是前台线程
        for (int i=0;i<10;i++){
            System.out.println("主线程:----------"+i);
        }
    }
}

3.3 线程的状态(等待)

在这里插入图片描述

4. 线程安全

多线程安全问题

  • 当多线程并发访问临界资源,如果破坏原子操作,可能会造成数据不一致
  • 临界资源:共享资源,一次仅允许一个线程使用,才可保证其正确性
  • 原子操作:不可封额的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省

在这里插入图片描述

public class ThreadSafe {
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //创建数组 共享资源
        String[] str = new String[5];
        //创建两个操作
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                str[index]="hello";
                index++;
            }
        };

        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                str[index]="world";
                index++;
            }
        };

        //创建两个线程对象
        Thread a = new Thread(r1,"A");
        Thread b = new Thread(r2,"B");
        a.start();
        b.start();
        a.join();//加入线程
        b.join();//加入线程

        //多运行几次发现问题  只放了一个值
        System.out.println(Arrays.toString(str));

    }
}

思考:在程序应用中,如何保证线程的安全性

4.1 同步方式(1)

同步代码块 synchronized 厕所上锁,其他等待

同步代码块中传的参数只需要传一个唯一的引用当锁即可,也就是任何物体都可以作为一把锁,保证唯一

小明,小红他们来到工作的地方。获取锁的地方就是路边的一棵树:注意现在路边只有这一棵树。小红抢先一步将树上的锁拿走了。然后小红工作去了(她的工作 不用和树有关系,她可以砍树,也可以去路边扫地 )。小明,小芳等其他人,因为没地方获取锁(只有一棵树 并且树的锁 已经被小红拿走了)。所以只能在原地等待 ,小红工作做完了 把锁还给树,小明,小芳才有机会进行工作。

  • 每个对象都有一个互斥锁标记(任何对象都可以用来做一个锁),用来分配给线程
  • 只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
  • 线程退出同步代码块时,会释放相应的互斥锁标记
synchronized(临界资源对象){ //对临界资源对象加锁
	//代码(原子操作)
}
public class ThreadSafe {
    private static int index=0;
    public static void main(String[] args) throws InterruptedException {
        //创建数组 共享资源
        String[] str = new String[5];
        //创建两个操作
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                synchronized (str){
                    str[index]="hello";
                    index++;
                }
            }
        };

        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                synchronized (str){
                    str[index]="world";
                    index++;
                }
            }
        };

        //创建两个线程对象
        Thread a = new Thread(r1,"A");
        Thread b = new Thread(r2,"B");
        a.start();
        b.start();
        a.join();//加入线程
        b.join();//加入线程

        
        System.out.println(Arrays.toString(str));

    }
}

案例:卖票

/**
 * 票类(共享资源)
 */
public class Ticket implements Runnable{
    private int ticket = 100;//100张票
    //创建锁
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            //这里必须传一个引用类型的值,此时ticket不可以
            //1.可以自己创建一个对象当锁  obj此时内存中只有一份
            //2.也可以使用this this为当前对象也就是Ticket对象,内存中也只有一份
            //3.也可以讲ticket声明为Integer类型
            //4.这里只需要传一个唯一的引用当锁即可,也就是任何物体都可以作为一把锁,保证唯一
            synchronized (obj){
                if (ticket<=0){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+" 卖了第"+ticket+"张票");
                ticket--;
            }
        }
    }

    public static void main(String[] args) {
        //1.创建票对象
        Ticket ticket = new Ticket();
        //2.创建线程对象
        Thread w1 = new Thread(ticket,"窗口1");
        Thread w2 = new Thread(ticket,"窗口2");
        Thread w3 = new Thread(ticket,"窗口3");
        Thread w4 = new Thread(ticket,"窗口4");
        //3.启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

案例:银行取钱存钱

public class BankCard {
    private double money;

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    public static void main(String[] args) {
        //创建银行卡
        BankCard card = new BankCard();
        //创建两个操作
        Runnable add = new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    synchronized (card){
                        card.setMoney(card.getMoney()+1000);
                        System.out.println(
                                Thread.currentThread().getName()+"存了1000,余额为"+card.getMoney());
                    }
                }
            }
        };

        Runnable sub = new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<10;i++){
                    synchronized (card){
                        if (card.getMoney()>=1000){
                            card.setMoney(card.getMoney()-1000);
                            System.out.println(
                                    Thread.currentThread().getName()+"取了1000,余额为"+card.getMoney());
                        }else {
                            System.out.println("余额不足");
                            i--;
                        }
                    }
                }
            }
        };
        //创建两个线程对象
        Thread t1 = new Thread(add,"男");
        Thread t2 = new Thread(sub,"女");
        t1.start();
        t2.start();
    }
}

4.2 线程的状态(阻塞)

在这里插入图片描述

打开Thread源码查看 State

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

4.3 同步方式(2)

同步方法

  • 只有拥有对象互斥锁标记的线程,才能进入该对象加索的同步方法中
  • 线程退出同步方法时,会释放相应 的互斥锁标记
synchronized  返回值类型  方法名称(形参列表){ //对当前对象this加锁
	
	//代码(原子操作)

}

案例:卖票

/**
 * 票类(共享资源)
 */
public class Ticket implements Runnable{
    private int ticket = 100;//100张票
    @Override
    public void run() {
        while (true){
            if(!sale()){
                break;
            }
        }
    }
    //将卖票的功能提取到方法中
    public synchronized boolean sale(){ //锁为this(ticket) 如果为静态方法,锁就为当前的类Ticket.class
        if (ticket<=0){
            return false;
        }
        System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
        ticket--;
        return true;
    }

    public static void main(String[] args) {
        //1.创建票对象
        Ticket ticket = new Ticket();
        //2.创建线程对象
        Thread w1 = new Thread(ticket,"窗口1");
        Thread w2 = new Thread(ticket,"窗口2");
        Thread w3 = new Thread(ticket,"窗口3");
        Thread w4 = new Thread(ticket,"窗口4");
        //3.启动线程
        w1.start();
        w2.start();
        w3.start();
        w4.start();
    }
}

4.4 同步规则

注意

  • 只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
  • 如调用不包含同步代码块的方法,或普通方法时,则不需要锁标记,可以直接调用

已知JDK中线程安全的类

  • StringBuffer
  • Vector
  • Hashtable
  • 以上类中的公开方法,均为synchronized修饰的同步方法

4.5 经典问题

死锁

  • 所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。

  • 当第一个线程拥有A对象锁标记,并等待B对象锁标记。同时第二个线程拥有B对象锁标记,并且等待A对象锁标记时,产生死锁

  • 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁的标记,由此可能造成死锁

案例:两根筷子,男女抢

/**
 * 创建两个锁对象
 */
public class MyLock {
    //两个锁(两根筷子)
    public static Object a = new Object();
    public static Object b = new Object();

}
public class Boy extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.a){
            System.out.println("男孩拿到了a");
            synchronized (MyLock.b){
                System.out.println("男孩拿到了b");
                System.out.println("男孩可以吃东西了");
            }
        }
    }
}
public class Girl extends Thread{
    @Override
    public void run() {
        synchronized (MyLock.b){
            System.out.println("女孩拿到了b");
            synchronized (MyLock.a){
                System.out.println("女孩拿到了a");
                System.out.println("女孩可以吃东西了");
            }
        }
    }
}
public class TestDeadLock {

    public static void main(String[] args) throws InterruptedException {
        Boy boy = new Boy();
        Girl girl = new Girl();
        //此时启动  形成死锁
        //boy.start();
        //girl.start();
        boy.start();
        //主线程休眠 先让boy执行 再让girl执行,这里指的是main线程休眠
        Thread.sleep(100);
        girl.start();
    }
}

4.6 线程通信

等待

  • public final void wait()
  • public final void wait(long timeout)
  • 必须在对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放其他拥有的所有锁标记,同时此线程阻塞在o的等待队列中。释放锁,进入等待队列

通知

  • public final void notify()
  • public final void notifyAll()

案例:存钱取钱

实现 存一次 取一次 依次交替进行

public class BankCard {
    //余额
    private double money;
    //开关 标记
    private boolean falg=false;//true表示有钱可以取钱,false没钱可以存钱
    //存钱
    public synchronized void save(double m){
        if (falg){ //有钱
            try {
                this.wait();//进入等待队列,同时释放锁和cup
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money+m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
        //修改标记
        falg=true;
        //唤醒取钱线程
        this.notify();
    }
    //取钱
    public synchronized void take(double m){
        if (!falg){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money-m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
        //修改标记
        falg=false;
        //唤醒存钱线程
        this.notify();
    }
}
public class AddMoney implements Runnable{
    private BankCard card;

    public AddMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            card.save(1000);
        }
    }
}
public class SubMoney implements Runnable{
    private BankCard card;

    public SubMoney(BankCard card) {
        this.card = card;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            card.take(1000);
        }
    }
}
public class TestBankCard {
    public static void main(String[] args) {
        //创建银行卡
        BankCard card = new BankCard();
        //创建操作
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        //创建线程对象
        Thread t1 = new Thread(add,"男");
        Thread t2 = new Thread(sub,"女");
        t1.start();
        t2.start();
    }
}
#这时有2个线程对象,此时实现了存一次取一次
#多个线程存取时会出现问题,出现负数或2000,还有可能出现死锁
public class TestBankCard {
    public static void main(String[] args) {
        //创建银行卡
        BankCard card = new BankCard();
        //创建操作
        AddMoney add = new AddMoney(card);
        SubMoney sub = new SubMoney(card);
        //创建线程对象
        Thread t1 = new Thread(add,"男");
        Thread t2 = new Thread(sub,"女");
        Thread t3 = new Thread(add,"公");
        Thread t4 = new Thread(sub,"母");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
#1.女抢到cpu,不能取,进入等待队列,释放锁和cpu
#2.母抢到cpu,不能取,进入等待队列,释放锁和cpu
#3.此时队列有2个
#4.男抢到cpu,存钱成功,修改标记true,并唤醒(随机唤醒队列中的一个)女
#5.女抢到cpu,取钱成功,修改标记为false,并唤醒母
#6.母抢到cpu,取钱成功,修改标记为false,并唤醒(此时队列没有) 为什么能取呢,因为刚才是走到if里面的wait()处进入队列的,此时唤醒没有再去判断标记
#此时将if换成while可以解决负数的问题,但是还有死锁问题
#死锁问题,根本原因随机唤醒一个,造成的问题
#女抢到cpu,不能取,进队列,释放锁和cpu               女
#母抢到cpu,不能取,进队列,释放锁和cpu               女 母
#男抢到cpu,存成功,修改标记唤醒女                    母
#公抢到cpu,不能存,进队列,释放锁和cpu               母 公
#男抢到cpu,不能存,进队列,释放锁和cpu               母 公 男
#女抢到cpu,取成功,唤醒母                           公 男
#女抢到cpu,不能取,进队列,释放锁和cpu                公 男 女
#母抢到cpu,不能取,进队列,释放锁和cpu                公 男 女 母
public class BankCard {
    //余额
    private double money;
    //开关 标记
    private boolean falg=false;//true表示有钱可以取钱,false没钱可以存钱
    //存钱
    public synchronized void save(double m){
        while (falg){ //有钱
            try {
                this.wait();//进入等待队列,同时释放锁和cup
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money+m;
        System.out.println(Thread.currentThread().getName()+"存了"+m+"余额是"+money);
        //修改标记
        falg=true;
        //唤醒取钱线程
        this.notify();
    }
    //取钱
    public synchronized void take(double m){
        while (!falg){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        money=money-m;
        System.out.println(Thread.currentThread().getName()+"取了"+m+"余额是"+money);
        //修改标记
        falg=false;
        //唤醒存钱线程
        this.notify();
    }
}
#此时将notify()改为notifyAll()唤醒全部,即可解决问题

4.7 生产者和消费者

若干个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区中取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个满的缓冲区中放入产品

package com.gx.d6;

/**
 * 面包
 */
public class Bread {
    private int id;
    private String productName;

    public Bread() {
    }

    public Bread(int id, String productName) {
        this.id = id;
        this.productName = productName;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getProductName() {
        return productName;
    }

    public void setProductName(String productName) {
        this.productName = productName;
    }

    @Override
    public String toString() {
        return "Bread{" +
                "id=" + id +
                ", productName='" + productName + '\'' +
                '}';
    }
}
package com.gx.d6;

/**
 * 面包容器
 */
public class BreadCon {
    //存放面包的数组
    private Bread[] cons = new Bread[6];
    //存放面包的位置
    private int index = 0;

    //存放面包
    public synchronized void input(Bread b){//锁this
        //判断有没有满
        while (index>=cons.length){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        cons[index]=b;
        System.out.println(Thread.currentThread().getName()+"生产了"+b.getId()+"");
        index++;
        //唤醒消费者
        this.notifyAll();
    }
    //消费面包
    public synchronized void output(){//锁this
        while (index<=0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        index--;
        Bread b = cons[index];
        System.out.println(Thread.currentThread().getName()+"消费了"+b.getId());
        cons[index]=null;
        //唤醒生产者
        this.notifyAll();
    }
}
package com.gx.d6;

/**
 * 生产
 */
public class Prodcut implements Runnable{
    private BreadCon con;

    public Prodcut(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for (int i=0;i<30;i++){
            con.input(new Bread(i,Thread.currentThread().getName()));
        }
    }
}
package com.gx.d6;

/**
 * 消费
 */
public class Consume implements Runnable{
    private BreadCon con;

    public Consume(BreadCon con) {
        this.con = con;
    }

    @Override
    public void run() {
        for (int i=0;i<30;i++){
            con.output();
        }
    }
}
package com.gx.d6;

public class Test {
    public static void main(String[] args) {
        //容器
        BreadCon con = new BreadCon();
        //生产和消费
        Prodcut prodcut = new Prodcut(con);
        Consume consume = new Consume(con);
        //创建线程对象
        Thread chenchen = new Thread(prodcut,"晨晨生产者");
        Thread bingbing = new Thread(consume,"冰冰消费者");
        Thread mingming = new Thread(prodcut,"明明生产者");
        Thread lili = new Thread(consume,"丽丽消费者");
        //启动线程
        chenchen.start();
        bingbing.start();
    }
}

4.8 总结

在这里插入图片描述

5. 线程池

5.1 线程池概念

在这里插入图片描述

5.2 线程池原理

将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程

在这里插入图片描述
在这里插入图片描述

5.3 创建线程池

常用的线程池接口和类(所在包java.util.concurrent)

  • Executor:线程池的顶级接口 (源码查看作者)
  • ExecutorService:线程池接口,可以通过submit(Runnable task)提交任务代码
  • Executors工厂类:通过此类可以获得一个线程池
  • 通过newFixedsThreadPool(int nThreads) 获取固定数量的线程池。参数:执行线程池中线程的数量
  • 通过newCachedThreadPool() 获得动态数量的线程池,如果不够则创建新的,没有上线
package com.gx.d7;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 线程池的创建
 * Executor:线程池的根接口  executor()
 * Executor:包含管理线程池的一些方法 submit,shutdown等
 *   ThreadPoolExecutor
 *     ScheduledThreadPoolExecutor
 * Executors:创建线程池的工具类
 *      (1)创建固定线程个数的线程池*
 *      (2)创建缓存线程池,由任务的多少决定*
 *      (3)创建单线程
 *      (4)创建调度线程池 调度:周期,定时执行
 */
public class Demo1 {
    public static void main(String[] args) {
        //1.1 创建固定个数的线程池
        //ExecutorService es = Executors.newFixedThreadPool(4);
        //1.2 创建缓存线程池
        ExecutorService es = Executors.newCachedThreadPool();
        //1.3 创建单线程池
        //Executors.newSingleThreadExecutor();
        //1.4 创建调度线程
        //Executors.newScheduledThreadPool();
        //2 创建任务
        Runnable runnable = new Runnable() {
            private int ticket=100;
            @Override
            public void run() {
                while (true){
                    if (ticket<=0){
                        break;
                    }
                    System.out.println(
                            Thread.currentThread().getName()+"卖了第"+ticket+"张票"
                            );
                    ticket--;
                }
            }
        };
        //3. 提交任务
        for (int i=0;i<4;i++){
            es.submit(runnable);
        }
        //4. 关闭线程池
        es.shutdown();//等待任务执行完毕
        //es.shutdownNow()//立即关闭
    }
}

5.4 Callable接口

JDK5加入,与Runnable接口类似,实现之后代表一个线程任务

Callable具有泛型返回值,可以声明异常

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Callable接口的使用
 * Callable和Runnable接口的区别
 * (1)Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
 * (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //功能需求:使用Callable实现1-100和
        //1.创建Callable对象
        Callable<Integer> callable = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum = 0;
                for (int i=0;i<100;i++){
                    sum+=i;
                    Thread.sleep(100);
                }
                return sum;
            }
        };
        //2.把Callable对象转成可执行任务
        FutureTask<Integer> task = new FutureTask<>(callable);
        //3.创建线程
        Thread thread = new Thread(task);
        //4.启动线程
        thread.start();
        //5.获取结果(等待call执行完毕,才会返回)
        Integer sum = task.get();
        System.out.println("结果是:"+sum);
    }
}
#结合线程池使用
import java.util.concurrent.*;

public class Demo02 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1.创建线程池
        ExecutorService es = Executors.newFixedThreadPool(1);
        //2.提交任务
        Future<Integer> future = es.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println(Thread.currentThread().getName()+"开始计算");
                int sum =0;
                for (int i=1;i<=100;i++){
                    sum+=i;
                    Thread.sleep(10);
                }
                return sum;
            }
        });
        //3.获取任务结果
        System.out.println(future.get());
        //4.关闭线程
        es.shutdown();
    }
}

6. 线程安全的集合

待更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值