多线程

基本概念

  • 进程(Process)、线程(Thread)、多线程
    1. 程序是指令和数据的集合,是一个静态的概念
    2. 进程是程序的一次的执行过程,是一个动态的概念。是系统资源分配的单位
    3. 通常在一个进程包含若干个线程,但至少有一个线程,如main。是CPU调度和执行的单位
    4. 很多多线程是模拟出来的,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
  • 在一个进程中,如果开辟了多个线程,线程的运行由CPU安排调度,先后顺序是不能人为干预的
  • 对同一份资源进行操作时,会存在资源抢夺问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

创建线程三种方式

  • 继承Thread类
//创建线程方式一:继承Threa类
public class TestThread1 extends Thread {
    @Override
    public void run() {
        //重写run()方法
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码---" + i);
        }
    }

    public static void main(String[] args) {
        //main方法主线程体
        TestThread1 testThread1 = new TestThread1();
        //调用start()方法开启线程
        testThread1.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学多线程---" + i);
        }
    }
}

注意:线程开启不一定立即执行,由CPU调度执行

  • 实现Runnable()接口,
//创建线程方式二:实现Runnable()接口
public class TestThread2 implements Runnable{
    @Override
    public void run() {
        //重写run()方法
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码---" + i);
        }
    }

    public static void main(String[] args) {
        //创建Runnable()接口实现类
        TestThread2 testThread2 = new TestThread2();
        //创建线程对象,通过线程对象开启我们的线程,代理
        new Thread(testThread2).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("我在学多线程---" + i);
        }
    }
}
  • 实现callable()接口

静态代理模式

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色
  • 好处
    1. 代理对象可以做很多真实对象做不了的事
    2. 真实对象专注做自己事

Lambda表达式

  • 函数式接口
    1. 任何接口,如果只包含唯一一个抽象方法,那么他就是函数式接口
    2. 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
  • Runnable接口就是一个函数式接口
  • Lambda表达式实现
public class LambdaTest {

    public static void main(String[] args) {
        //实现Lambda表达式
        ILove i = (x) -> {
            System.out.println("I love you!" + x);
        };
        i.print(20);
    }
}

//定义一个函数式接口
interface ILove {
    void print(int x);
}

线程状态

  • 线程停止
    1. 建议线程正常停止-->利用次数,不建议死循环
    2. 建议使用标志位-->设置一个标志位
    3. 不要使用stop或destroy等过时或者JDK不建议使用的方法
    4. 线程间共享变量需要使用volatile关键字标记,确保每个线程都能读取到更新后的变量值
public class TestStop implements Runnable {
    //1、设置一个标识位
    private volatile boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        //2、线程中使用该标识符
        while (flag) {
            System.out.println("run...Thread" + i++);
        }
    }

    //3、设置一个公开的方法停止线程,转换标识位
    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        
        for (int i = 0; i < 200; i++) {
            System.out.println("main" + i);
            if (i == 150) {
                //调用stop()方法停止线程
                testStop.stop();
                System.out.println("线程停止了");
            }
        }
    }
}
  • 线程休眠

    1. Thread.sleep(时间) 指定当前线程阻塞的毫秒数
    2. sleep可以模拟网络延时、倒计时等
    3. 每个对象都有一个锁,sleep不会释放锁
  • 线程礼让

    1. Thread.yield()
    2. 将线程从运行状态转为就绪状态
    3. CPU重新重新调度,但不一定成功
  • 线程强制执行

    1. join合并线程,将此线程执行完毕后,在执行其他线程,其他线程阻塞
    2. 可以想象成插队
public class TestJoin implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程VIP来了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //启动我们的线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            if (i == 200)
                //插队
                thread.join();
            System.out.println("main" + i);
        }
    }
}
  • 守护线程
  1. 线程分为用户线程和守护线程
  2. 虚拟机必须确保用户线程执行完毕
  3. 虚拟机不用等待守护线程执行完毕
public class TestDaemon {
    public static void main(String[] args) {
        You you = new You();
        God god = new God();
        Thread thread = new Thread(god);
        //默认false是用户线程,设置true为守护线程
        thread.setDaemon(true);
        thread.start();
        new Thread(you).start();
    }
}
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 3000; i++) {
            System.out.println("开心活着");
        }
        System.out.println("Game over!");
    }
}
class God implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("上帝保佑你!");
        }
    }
}

同步线程

  • 并发:同一个对象被多个线程同时操作,可能会引起资源冲突
  • 线程同步是一种等待机制,多个需要同时访问一个对象的线程进入这个对象的等待池,形成队列,等待前面线程访问完毕,下一个线程再使用
  • 锁:synchronized关键字对一个对象进行加锁,当一个线程获得对象的锁,独占资源,其他线程需等待
  • 使用锁会使性能降低,所以方法里需要修改的内容才需要锁
  • 使用锁的方法
    1. 同步方法
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station, "邓憨憨").start();
        new Thread(station, "夜十七").start();
        new Thread(station, "黄卡丽").start();
    }
}

class BuyTicket implements Runnable {
    private int tickets = 10;
    boolean flag = true;//外部停止线程方法

    @Override
    public void run() {
        //买票
        while (flag) {
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized 同步方法 锁的是this.tickets
    private synchronized void buy() throws InterruptedException {
        if (tickets <= 0) {
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "拿到了票" + tickets--);
    }
}
 2. 同步块
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"结婚基金");
        Drawing you = new Drawing(account,50,"you");
        Drawing me = new Drawing(account,100,"me");
        you.start();
        me.start();
    }
}
//账户类
class Account {
    int money;
    String name;

    public Account(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
//取钱类
class Drawing extends Thread {
    Account account;
    int drawingMoney;//取出多少钱
    int nowMoney;    //手中的钱

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        //同步方法块,默认锁this
        synchronized (account) {
            if (account.money - drawingMoney < 0) {
                System.out.println("账户余额不足");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingMoney;
            nowMoney += drawingMoney;
            System.out.println(this.getName() + "手里的钱" + nowMoney);
            System.out.println(account.name + "账户余额:" + account.money);
        }
    }
}
  • 死锁
    多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方资源,都停止执行的情形。即某一个同步块同时拥有“两个以上对象的锁”时。
public class DeadLock {
    public static void main(String[] args) {
        MakeUp m1 = new MakeUp(0, "邓憨憨");
        MakeUp m2 = new MakeUp(1, "黄卡丽");
        m1.start();
        m2.start();
    }
}

//口红
class Lipstick {
}

//镜子
class Mirror {
}

class MakeUp extends Thread {
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();
    int choice;
    String name;

    MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(name + "获得口红的锁");
                Thread.sleep(1000);
                synchronized (mirror) {
                    System.out.println(name + "获得镜子的锁");
                }
            }

        } else {
            synchronized (mirror) {
                System.out.println(name + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick) {
                    System.out.println(name + "获得口红的锁");
                }
            }
        }
    }
}
  • lock锁:是显式锁
public class TestLock {
    public static void main(String[] args) {
        BuyTickets station = new BuyTickets();
        new Thread(station, "邓憨憨").start();
        new Thread(station, "夜十七").start();
        new Thread(station, "黄卡丽").start();
    }
}

class BuyTickets implements Runnable {
    private int tickets = 10;
    boolean flag = true;//外部停止线程方法
    //定义锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        //买票
        while (flag) {
            try {
                //加锁
                lock.lock();
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }

    private void buy() throws InterruptedException {
        if (tickets <= 0) {
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "拿到了票" + tickets--);
    }
}

线程池

  • 线程池内部维护一组线程,可以高效执行大量小任务
  • Executors提供了静态方法创建不同类型的ExecutorService
  • 必须调用shutdown()关闭ExecutorService
import java.util.concurrent.*;

public class TestPool {
    public static void main(String[] args) {
        //创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(4);

        //执行
        for (int i = 0; i < 6; i++) {
            service.submit(new MyThreads());
        }
        //关闭链接
        service.shutdown();
    }
}

class MyThreads implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}