线程(Thread)

多线程:

多线程使Java语言的重要特性,大量应用于网络编程、服务器端程序的开发,最常见的UI界面底层原理、操作系统底层原理都大量使用了多线程。

程序:

“程序(Program)”是一个静态的概念,一般对应与操作系统中的一个可执行文件,比如:我们要启动网易音乐,则对应酷狗的可执行程序。当我们双击网易,则加载到内存中,开始执行该程序,于时产生了“进程”。

进程:

  1. 执行中的程序叫做进程(Process),是一个动态的概念。现代的操作系统都可以同时启动多个进程。比如同时启动多个应用程序。
  2. 进程的特点:
    1. 进程是程序的一次动态执行过程,占用特定的地址空间。
    2. 每个进程由3部分组成:Cpu、data、code。每个进程都是独立的,保有自己的Cpu时间,代码和数据,即便用同一份程序产生好几个进程,他们之间还是拥有自己的这三样东西,这样的缺点是:浪费内存,CPU的负担较重。
    3. 多任务(Multitasking)操作系统将CPU时间动态规划地分给某个进程,操作系统同时执行多个进程,你每个进程独立的运行。以进程的观点来看,它会以自己独占CPU的使用权。

线程:

  1. 一个进程可以产生多个线程。同多个进程可以共享操作系统里的某些资源一样,同一进程的多个线程也可以共享此进程的某些资源(code,data),所以线程又被称为轻量级进程(lightweight process)。
  2. 一个进程内部的一个执行单元,他是程序中的一个单一的顺序控制流程。
  3. 一个进程可以拥有多个并行的(concurrent)线程。
  4. 一个进程中的多个线程共享相同的内存单元/内存地址空间,可以访问相同的变量和对象,而且他们从同一堆中分配对象那个并进行通信、数据交换和同步操作。
  5. 由于线程间的通信是在同一地址空间上进行的,多以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快。
  6. 线程的启动、中断、消亡,消耗的资源非常少。

image.png
线程和进程的区别:

  1. 每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
  2. 线程可以看成是轻量级的进程,属于同一进程的线程共享代码和数据空间,每个线程又独立的运行栈和程序计数器(PC),线程切换到开销小。
  3. 线程和进程的最根本的区别在于:进程是资源分配的单位,线程是调度和执行的单位。
  4. 多进程:在操作系统中能同时运行多个任务(程序)。
  5. 多线程:在同一应用程序中有多个顺序流同时执行。
  6. 线程是进程的一部分,所以线程有的时候被称为轻量级进程。
  7. 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个线程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
  8. 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为了线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,除了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的配置与线程无关,线程只能共享它所属进程的资源。

进程与程序的区别:

程序是一组指令的集合,它是静态的实体,没有执行的含义。而进程是一个动态的实体,有自己的生命周期。以吧来说,一个进程肯定与一个程序相对应,并且只有一个,但是一个程序可以有多个进程,或者一个进程都没有。除此之外,进程还有并发性和交往性。简单来说:进程是程序的一部分,程序运行的时候会产生进程。

通过继承Thread类实现多线程

  1. 在Java中负责实现线程功能的类是java.lang.Thread类。
  2. 可以通过创建Thread的实例来创建新的线程。
  3. 每个吸纳成都是通过某个特定的Thread对象所对应的方法run()来完成其操作,方法run()称为线程体。
  4. 通过调用Thread类的==start()==方法来启动一个线程。
  1. public class TestThread extends Thread { // 自定义类继承Thread类
  2. // run()方法里是线程体
  3. @Override
  4. public void run() {
  5. for (int i = 0; i < 10; i++) {
  6. System.out.println(this.getName() + ":" + i); // getName()方法返回线程名称
  7. }
  8. }
  9. public static void main(String[] args) {
  10. TestThread thread1 = new TestThread(); // 创建线程对象
  11. thread1.start(); // 启动线程
  12. TestThread thread2 = new TestThread();
  13. thread2.start();
  14. }
  15. }

通过Runnable接口实现多线程

  1. 在应用开发中,我们应用更多的是通过 Runnable 接口实现多线程。这种方式克服了 Thread 实现线程类的缺点,即实现 Runnable接口的同时还可以继承某个类。
  2. 所以实现 Runnable 接口的方式要通用一些。
  1. public class TestThread2 implements Runnable { // 自定义类实现Runable接口
  2. @Override
  3. public void run() {
  4. for (int i = 0; i < 10; i++) {
  5. System.out.println(Thread.currentThread().getName() + ":" + i);
  6. }
  7. }
  8. public static void main(String[] args) {
  9. // 创建线程对象,把实现了Runnable接口的对象作为参数传入;
  10. Thread thread1 = new Thread(new TestThread2());
  11. thread1.start(); // 启动线程
  12. Thread thread2 = new Thread(new TestThread2());
  13. thread2.start(); // 启动线程
  14. }
  15. }

通过Callable实现多线程

public class CDownloader implements Callable<Boolean> {
    private String url;
    private String name;

    public CDownloader(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call() throws Exception{
        WebDownloader wd = new WebDownloader(); // WebDownloader为一个web下载的类
        wd.download(url, name);
        System.out.println(name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CDownloader cd1 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907409428&di=b08ec32df49d24e0153d046b5e37e86e&imgtype=0&src=http%3A%2F%2Fqimg.hxnews.com%2F2018%2F0611%2F1528676232318.jpg","火箭熊.jpg");
        CDownloader cd2 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907409428&di=315902600665b2854ac20e3a60a8ec2a&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F0105d15724c5886ac725381222c109.jpg%402o.jpg","蜘蛛侠.jpg");
        CDownloader cd3 = new CDownloader("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1588907837238&di=5519b9b715f0276bb29fc1e07497db54&imgtype=0&src=http%3A%2F%2Fimg.zcool.cn%2Fcommunity%2F01850455450dc50000019ae92d459c.jpg%401280w_1l_2o_100sh.jpg","蜘蛛侠02.jpg");

        // 创建执行服务
        ExecutorService service = Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> reslut1 = service.submit(cd1);
        Future<Boolean> reslut2 = service.submit(cd2);
        Future<Boolean> reslut3 = service.submit(cd3);
        // 获取结果
        boolean r1 = reslut1.get();
        boolean r2 = reslut2.get();
        boolean r3 = reslut3.get();
        System.out.println(r3);
        // 关闭服务
        service.shutdownNow();

    }
}

线程状态

image-20200524230332465.png

新生状态(New)

  1. 用new关键字建立一个线程对象后,该线程对象就处于新生状态。
  2. 处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态。

就绪状态(Runnable)

处于就绪状态的线程已经具备了运行条件,但是还没有被分配到CPU,处于“线程就绪队列”,等待系统为其分配CPU。就绪状态并不是执行状态,当系统选定一个等待执行的Thread对象后,她就会进入执行状态。一旦获得CPU,线程就进入运行状态并自动调用自己的run()方法。

线程进入就绪状态的4个原因:

  1. 新建线程: 调用start()方法,进入就绪状态。
  2. 阻塞线程: 阻塞解除,进入就绪状态。
  3. 运行进程: 调用yield(),直接进入就绪状态。
  4. 运行状态: JVM 将 CPU 资源从本线程切换到其他线程。

运行状态(Running)

在运行状态的线程执行自己run()方法中的代码,直到调用其他方法而终止或等待某资源而阻塞或完成任务而死亡。

如果再给定的时间片内没有执行结束,就会被系统给换下来回到就绪状态。也可能由于某些“导致阻塞的事件”而进入阻塞状态。

阻塞状态(Blocked)

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪)。有4中原因会导致阻塞。

  1. 执行sleep(int millsecond) 方法,使当前线程休眠,进入阻塞状态。当指定的时间到了后,线程进入就绪状态。
  2. 执行wait()方法,使当前线程进入阻塞状态。当使用nofity()方法唤醒这个线程后,他进入就绪状态。
  3. 线程运行时,某个操作进入阻塞状态,比如执行IO流操作(read()/write()方法本身就是阻塞方法的方法)。只有当引起该操作阻塞的原因消失后,线程进入就绪状态。
  4. join() 线程联合:当某个线程等待另一个线程执行结束后,才能继续执行,使用join()方法。

死亡状态(Terminated)

  1. 死亡状态是线程声明周期中最后一个阶段。线程死亡的原因有两个。一个是正常运行的线程完成了它run()方法内的全部工作;另一个是线程被强制终止,如通过执行stop()或destroy()方法来终止一个线程(注:stop()/destroy()方法已经被JDK废弃,不推荐使用)。
  2. 当一个线程进入死亡状态以后,就不能再回到其他状态了。

线程终止的典型方式

终止线程我们一般不使用JDK提供的stop()/destory()方法(它们本身也被JDK废弃了)。通常我们的做法是提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。

public class TestThreadCiycle implements Runable {
    String name;
    boolean live = true; // 标记变量。表示线程是否可终止。

    public TestThreadCiycle(String name) {
        super();
        this.name = name;
    }

    @Override
    public void run() {
        int i = 0;
        // 当live的值为true时,继续线程体,false则结束循环,继而终止线程体。
        while(live) {
            System.out.println(name + (i++));
        }
    }

    public void terminate() {
        live = false;
    }

    public static void main(String[] args) {
        TestThreadCiycle ttc = new TestThreadCiycle("线程A:");
        Thread t1 = new Thread(ttc);// 新生状态
        t1.start(); //就绪状态
        for(int i = 0; i < 100) {
            System.out.println("主线程:" + i);
        }
        ttc.terminate();
        System.out.println("ttc stop!");
    }
}

暂停线程执行

暂停线程常用的方法有sleep()和yield()方法,这俩方法区别如下:

  1. sleep()方法:可以让正在运行的线程进入阻塞状态,直到休眠时间满了,进入就绪状态。
  2. yield()方法:可以让正在运行的线程直接进入就绪状态,让出CPU的使用权。
  3. 使用yield()可以引起线程的切换,但运行时没有明显延迟。
// sleep()
public class TestThreadState {
    public static void main(String[] args) {
        StateThread thread1 = new StateThread();
        thread1.start();
        StateThread thread2 = new StateThread();
        thraed2.start();
    }
}
// 使用继承的方式实现多线程
class StateThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName + ":" + i);
            try {
                Thread.sleep(2000); // 调用线程的sleep()方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
// yield()
public class TestThreadState {
    public static void main(String[] args) {
        Thread thread1 = new Thread(new StateThread);
        Thread thread2 = new Thread(new StateThread);
        thread1.start();
        thread2.start();
    }
}
// 通过接口Runnable实现多线程
class StateThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread.getName() + ":" + i);
            Thread.yield(); // 调用线程的yield()方法。
        }
    }
}

守护线程(Daemon)

守护线程顾名思义,就是保佑其他线程正常运行,不必管自己本身。专业一点:守护线程:是为用户线程服务的;JVM停止不用等待守护线程执行完毕。(没有守护线程的话,用户线程JVM需要等待所有的线程执行完毕,才会停止)

public class DaemonTest {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        // 代理
        Thread t = new Thread(god);
        t.setDaemon(true);
        // 将用户线程调整为守护线程,守护线程就是通过这儿设置的。这儿也就是将god线程设置为了守护线程。
        t.start();
        new Thread(you).start();

    }
}

class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 365*100; i++) {
            System.out.println("happy life...");
        }
        System.out.println("oooooooooooo...");
    }
}

class God implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 365*1000000; i++) {
            System.out.println("bless you...");
        }
        System.out.println("oooooooooooo...");
    }
}

线程的联合join()

线程A再运行期间,可以调用线程B的join()方法,让线程B和线程A联合。这样,线程A就必须等待线程B执行完毕后,才能继续执行。如下例:“爸爸线程”要抽烟,于是联合“儿子线程”去买烟,必须等待“儿子线程”买烟完毕,“爸爸线程”才能继续抽烟。

public class TestThreadState {
    public static void mian(String[] args) {
        System.out.println("爸爸和儿子买烟的故事!");
        Thread father = new Thread(new FatherThread());
        father.start();
    }
}
class FatherThread implements Runable {
    @Override
    public void run() {
        System.out.println("爸爸想抽烟,发现烟抽完了!?");
        System.out.println("爸爸让儿子去买包烟");
        Thread son = new Thread(new SonThread());
        son.start();
        System.out.println("爸爸等儿子买烟回来");
        try {
            son.join();
        } catch (INterruptedException e) {
            e.printStackTrace();
            System.out.println("爸爸出门找儿子跑哪去了");
            System.exit(1);
        }
        System.out.println("爸爸高兴的接过烟开始抽,并把零钱给了儿子");
    }
}
class SonThread implements {
    @Override
    public void run() {
        System.out.println("儿子出门买烟");
        System.out.println("买烟需要10分钟");
        try {
            for (int i = 1; i <= 10; i++) {
                System.out.println("第" + i + "分钟");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("儿子买烟回来了");
    }
}

运行结果为:
image.png

获取线程基本信息的方法

方法 功能
isAlive() 判断线程是否还活着,即线程是否还未终止
getPriority() 获得线程的优先级数值
setPriority() 设置线程的优先级数值
setName() 给线程一个名字
getName() 获得线程的名字
currentThread() 获得房前正在运行的线程对象,也就是取得自己本身
public class TestThread {
    public static void main(String[] args) throws Exception {
        Runnable r = new MyThread();
        Thread t = new Thread(r, "Name test"); // 定义线程对象,并传入参数;
        t.start(); // 启动线程
        System.out.println("Name is:" + t.getName()); // 输出线程名称
        Thread.currentThread().sleep(5000); // 线程暂停5s。
        System.out.println(t.isAlive()); // 判断线程还在运行吗?
        System.out.println("over!");
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
    }
}

线程的优先级

优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高的线程后调用优先级低的线程。

  1. 处于就绪状态的线程,会进入“就绪队列”等待JVM来挑选。
  2. 线程的优先级用数字表示,范围从1到10,一个线程缺省优先级是5.
  3. 使用下列方法获得或设置线程对象的优先级。
    int getPriority();
    void setPriority(int newPriority);
    
public class TestThread {
    public static void mian(String[] args) {
        Thread t1 = new Thread(new MyThread(), "t1");
        Thread t2 = new Thread(new MyThread(), "t2");
        t1.setPriority(1);
        t2.setPriority(10);
        t1.start();
        t2.start();
    }
}
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

线程同步

处理多线程问题时,多个线程访问一个对象,并且某些线程还想修改这个对象。这时候,我们就需要用到“线程同步”。线程同步起始就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

多线程操作同一个对象(未使用线程同步)

public class TestSync {
    public static void main(String[] args) {
        Account a1 = new Account(100, "高");
        Drawing draw1 = new Drawing(80, a1);// 定义取钱线程对象;
        Drawing draw2 = new Drawing(80, a1);// 定义取钱线程对象;
        draw1.start(); // 你取钱
        draw2.start(); // 你老婆取钱
    }
}
/*
 * 简单表示银行账户
 */
class Account {
    int money;
    String aname;

    public Account(int money, String aname) {
        super();
        this.money = money;
        this.aname = aname;
    }
}
/**
 * 模拟提款操作
 */
class Drawing extends Thread {
    int drawingNum; // 取多少钱
    Account account; // 要取钱的账户
    int expenseTotal; // 总共取的钱数

    public Drawing(int drawingNum, Account account) {
        super();
        this.drawingNum = drawingNum;
        this.account = account;
    }

    @Override
    public void run() {
        if (account.money - drawingNum < 0) {
            return;
        }
        try {
            Thread.sleep(1000); // 判断完后阻塞。其他线程开始运行。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        account.money -= drawingNum;
        expenseTotal += drawingNum;
        System.out.println(this.getName() + "--账户余额:" + account.money);
        System.out.println(this.getName() + "--总共取了:" + expenseTotal);
    }
}

未使用线程同步的结果
image.png

实现线程同步

  1. 由于同一线程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题。Java语言提供了专门机制解决这种冲突,有效避免了同一个数据对象被多个线程同时访问造成的这种问题。
  2. 由于我们可以通过private 关键字来保证数据对象只能被访问,所以我们只需针对方法提出一套机制,这套机制就是synchronized 关键字,它包括两种用法:synchronized方法synchronized块

synchronized方法
  1. 通过在方法声明中加入synchronized 关键字来声明,如: public synchronized void accessVal(int newVal);
  2. synchronized方法控制对“对象的类成员变量”的访问:每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占改锁,直到该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行的状态。

synchronized块
  1. synchronized方法的缺陷:若将一个大的方法声明为synchronized将会大大影响效率。
  2. Java为我们提供了更好的解决办法,那就是synchronized块。块可以让我们精确地控制到具体的“成员变量”,缩小同步的范围,提高效率。
  3. synchronized块语法为:synchronized(syncObject){允许空值的代码}
public class TestSync {
    public static void main(String[] args) {
        Account a1 = new Account(100, "高");
        Drawing draw1 = new Drawing(80, a1);
        Drawing draw2 = new Drawing(80, a1);
        draw1.start(); // 你取钱
        draw2.start(); // 你老婆取钱
    }
}
/*
 * 简单表示银行账户
 */
class Account {
    int money;
    String aname;
    public Account(int money, String aname) {
        super();
        this.money = money;
        this.aname = aname;
    }
}
/**
 * 模拟提款操作
 * 
 * @author Administrator
 *
 */
class Drawing extends Thread {
    int drawingNum; // 取多少钱
    Account account; // 要取钱的账户
    int expenseTotal; // 总共取的钱数

    public Drawing(int drawingNum, Account account) {
        super();
        this.drawingNum = drawingNum;
        this.account = account;
    }

    @Override
    public void run() {
        draw();
    }

    void draw() {
        synchronized (account) {
            // 这儿锁定的时account账户
            if (account.money - drawingNum < 0) {
                System.out.println(this.getName() + "取款,余额不足!");
                return;
            }
            try {
                Thread.sleep(1000); // 判断完后阻塞。其他线程开始运行。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingNum;
            expenseTotal += drawingNum;
        }
        System.out.println(this.getName() + "--账户余额:" + account.money);
        System.out.println(this.getName() + "--总共取了:" + expenseTotal);
    }
}
  1. “synchronized(account)” 意味着线程需要获得account对象的“锁”才有资格运行同步块中的代码。Account对象的“锁”也称为“互斥锁”,在同一时刻只能被一个线程使用。
  2. A线程拥有锁,则可以调用“同步块”中的代码;B线程没有锁,则进入account对象的“锁池队列”等待,直到A线程使用完毕释放了account对象的锁,B线程得到锁才可以开始调用“同步块”中的代码。

死锁及解决方案

死锁

  1. 死锁是指,各个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进程,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形。
  2. 比如三个人吃饭,没两个人中间放着一种餐具,要每个人手里都拿到两个餐具才能吃饭,但是同时开始吃饭的话,就会造成,每人拿着一个餐具,都不能吃饭的结局。
class Lipstick {
    // 口红类
}
class Mirror {
    // 镜子类
}
class Makeup extends Thread {
    // 化妆类继承了Thread类
    int flag;
    String girl;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    @Override
    public void run() {
        doMakeup();
    }
    void doMakeup() {
        if (flag == 0) {
            synchronized (lipstick) {
                // 需要拿到口红的锁
                System.out.println(girl + "拿着口红!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }

                synchronized (mirror) {
                    // 需要得到镜子的锁
                    System.out.println(girl + "拿着镜子!");
                }
            }
        } else {
            synchronized (mirror) {
                // 需要拿到口红的锁
                System.out.println(girl + "拿着镜子!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }

                synchronized (lipstick) {
                    // 需要得到镜子的锁
                    System.out.println(girl + "拿着口红!");
                }
            }
        }
    }
}
public class TestDeadLock {
    public static void main(String[] args) {
        Makeup m1 = new Makeup();//大丫的化妆线程;
        m1.girl = "大丫";
        m1.flag = 0;
        Makeup m2 = new Makeup();//小丫的化妆线程;
        m2.girl = "小丫";
        m2.flag = 1;
        m1.start();
        m2.start();
    }
}
// 这样互相持锁,造成死锁

死锁的解决方法

死锁是由于“同步块需要同时持有两个对象锁造成”的,要解决这个问题,思路很简单,就是同一个代码块,不要同时持有两个对象的锁。

class Lipstick {//口红类

}
class Mirror {//镜子类

}
class Makeup extends Thread {//化妆类继承了Thread类
    int flag;
    String girl;
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    @Override
    public void run() {
        // TODO Auto-generated method stub
        doMakeup();
    }

    void doMakeup() {
        if (flag == 0) {
            synchronized (lipstick) {
                System.out.println(girl + "拿着口红!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
            synchronized (mirror) {
                System.out.println(girl + "拿着镜子!");
            }
        } else {
            synchronized (mirror) {
                System.out.println(girl + "拿着镜子!");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            synchronized (lipstick) {
                System.out.println(girl + "拿着口红!");
            }
        }
    }
}

public class TestDeadLock {
    public static void main(String[] args) {
        Makeup m1 = new Makeup();// 大丫的化妆线程;
        m1.girl = "大丫";
        m1.flag = 0;
        Makeup m2 = new Makeup();// 小丫的化妆线程;
        m2.girl = "小丫";
        m2.flag = 1;
        m1.start();
        m2.start();
    }
}
// 这儿没有锁的重复,也就是锁里边没有锁,所以不会造成互相持锁,造成死锁的这种情况。

线程并发协作

在多线程环境下,我们经常需要多个线程变并发和协作。这个时候,我们就需要了解一个重要的多线程并发协作模式“生产者/协作者模式”。

什么是生产者?

生产者指的是负责生产数据的模块(这里的模块可能是:对象,方法,线程,进程)。

什么是消费者?

消费者的是负责处理无数据的模块(这里的模块可能是:方法,对象,进程,线程)。

什么是缓冲区?

消费者不能直接使用生产者的数据,他们之间有个“缓冲区”。生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。

image-20200526093958793.png

实现线程的并发协作

  1. 有了缓冲区以后,生产者线程只需要往缓冲区里面防止数据,而不需要管消费者的情况;同样,消费者只需要从缓冲区拿数据处理即可,也不用管生产者生产的情况。这样,就从逻辑上实现了“生产者线程”和“消费者线程”的分离。
  2. 解耦了生产者和消费者:生产者不需要和消费者直接打交道。
  3. 解决了忙闲不均,提高效率:生产者生产数据慢时,缓冲区仍有数据,不影响消费者消费;消费者处理数据慢时,生产者仍然可以继续往缓冲区里边放置数据。
  4. 这样就会并发协作,你放一个,我拿一个,没了我就等待;放满了,你就等会儿放。

管程法

public class TestProduce {
    public static void main(String[] args) {
        SyncStack sStack = new SyncStack(); // 定义缓冲区对象;
        Shengchan sc = new Shengchan(sStack); // 定义生产线程;
        Xiaofei xf = new Xiaofei(sStack); // 定义消费线程;
        sc.start();
        xf.start();
    }
}

class Mantou {// 馒头
    int id;

    Mantou(int id) {
        this.id = id;
    }
}

class SyncStack {// 缓冲区(相当于:馒头框)
    int index = 0;
    Mantou[] ms = new Mantou[10];

    public synchronized void push(Mantou m) {
        while(index == ms.length) {// 说明馒头筐满了
            try {
                this.wait();
                /*
                    wait后,线程会将持有的锁释放,进入阻塞状态;
                    这样其他需要锁的线程就可以获得锁;
                    这里的含义是执行此方法的线程暂停,进入阻塞状态,等消费者消费了馒头后再生产。
                */
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        /*
            唤醒再当前对象等待池的第一个线程。
            notifyAll叫醒所有在线程等待池种等待的所有线程。
            如果不唤醒的话,以后这两个线程都会进入等待线程,没有人唤醒。
        */
        ms[index] = m;
        index++;
    }

    public synchronized Mantou pop() {
        while(index == 0) {// 如果馒头是空的
            try {
                this.wait();
                /*
                    如果馒头筐是空的,就暂停此消费线程(因为没有东西再可以消费了),等生产线程生产完再来消费。
                */
            } catch (InterruptedExceptio e) {
                e.printStackTrace();
            }
        }
        this.notify();
        index--;
        return ms[index];
    }
}

class Shengchan extends Thread {// 生产者线程
    SyncStack ss = null;

    public Shengchan(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("生产馒头:" + i);
            Mantou m = new Mantou(i);
            ss.push(m);
        }
    }
}

class Xiaofei extends Thread {// 消费者线程
    SyncStack ss = null;

    public Xiaofei(SyncStack ss) {
        this.ss = ss;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            Mantou m = ss.pop();
            System.out.println("消费馒头:" + i);
        }
    }
}

信号灯法

public class CoTest02 {
    public static void main(String[] args) {
        Tv tv = new Tv();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
// 生产者 演员
class Player extends Thread{
    Tv tv;

    public Player(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i %2 ==0) {
                this.tv.play("奇葩说");
            }else {
                this.tv.play("太污了!");
            }
        }
    }
}
// 消费者 观众
class Watcher extends Thread{
    Tv tv;

    public Watcher(Tv tv) {
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
// 同一个资源 电视
class Tv{
    String voice;
    // 信号灯
    // T表示演员表演,观众等待
    // F表示观众观看,演员等待
    boolean flag = true;

    // 表演
    public synchronized void play(String voice){
        // 演员等待
        if (!flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("表演了:" + voice);
        this.voice = voice;
        this.notifyAll();
        this.flag = !this.flag;
    }

    // 观看
    public synchronized void watch(){
        // 观众等待
        if (flag) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("听到了:" + voice);
        this.notifyAll();
        this.flag = !this.flag;
    }
}

线程并发协作总结

线程并发协作(也叫线程通信),通常用于生产者/消费者模式。

  1. 生产者和消费者共享一个资源,并且没有生产者和消费者之间的相互依赖(你干你的,我干我的),互为条件。
  2. 对于生产者,没有生产产品之前,消费者要进入等待状态。而生产了产品之后,又需要马上通知消费者消费。
  3. 对于消费者,在消费了后,要通知生产者已经消费结束,需要继续生产新产品以共消费。
  4. 在生产者消费者问题中,仅有synchronized 是不够的。
    • synchronized 可阻止并发更新同一个共享资源,实现了同步;
    • synchronized 不能用来实现不同线程之间的消息传递(通信)。
  5. 线程之间通过以下方法来进行消息传递(这些方法都是java.lang.Object类的方法,这些方法都只能在同步方法或者同步代码块中使用,否则抛出异常): | 方法名 | 作用 | | —- | —- | | final void wait() | 表示线程一直等待,直到得到其他线程通知 | | void wait(long timeout) | 线程等待指示毫秒参数的时间 | | final void wait(long timeout,int nanos) | 线程等待指示毫秒、微妙的时间 | | final void notify() | 唤醒一个处于等待状态的线程 | | final void notifyAll() | 唤醒同一个对象上所有调用的wait()方法的线程,优先级别搞得线程优先运行。 |

任务定时调度

通过Timer和TimerTask,我们可以实现定时启动某个线程

java.util.Timer

这种实现方式中,Timer类作用是类似闹钟的功能,也就是定时或者每隔一定的时间触发一次线程。其实。Timer来本身就是一个线程,只是这个线程使用来实现其他线程的。

java.util.TimerTask

TimerTask类是一个抽象类,该类实现了Runnable接口。 所以该类具备多线程的能力。在这种实现方式中,通过继承TimerTask使该类获得多线程的能力,将需要多线程执行的代码书写在run()方法内部,通过Timer类启动线程执行。

public class TestTimer {
    public static void main(String[] args) {
        Timer t1 = new Timer(); // 定义计时器
        MyTask task1 = new MyTask(); // 定义任务;
        //t1.schedule(task1,3000); // 3s后执行;
        //t1.schedule(task1,5000,1000); // 5s以后每个1s执行一次!
        GregorianCalendar calendar1 = new GregorianCalendar(2099,1,1,1,20,30);
        t1.schedule(task1,calendar1.getTime()); // 指定时间定时执行。
    }
}

class MyTask extends TimerTask {//自定义线程类继承TimerTask类;
    @Override
    public void run() {
        for(int i = 0; i < 10; i++) {
            System.out.println("任务1:" + i);
        }
    }
}

在实际使用时,一个Timer可以启动任意多个TimerTask实现的线程,但是多个线程之间会存在阻塞。所以如果多个线程之间需要完全独立的话,最好还是一个Timer启动一个TimerTask实现。

Lambda

  1. Lambda表达式也可称为闭包,它是推动java8发布的最重要的新特性。
  2. Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法)。

语法

(parameters) -> expression
或
(parameters) -> {statements;}
  1. Lambda重要特征
    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
// 1. 不需要参数,返回值为5
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x - y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接收一个String对象,并在控制台打印,不返回任何值(看起来像是void)
(String s) -> System.out.println(s)
public class Java8Tester {
    public static void main(String[] args) {
        Java8Tester tester = new Java8Tester();

        // 类型声明
        MathOperation addition = (int a, int b) -> a + b;

        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;

        // 大括号中返回语句
        MathOperation multiplication = (int a, int b) -> {return a * b};

        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

          // 不用括号
        GreetingService greetService1 = message ->System.out.println("Hello " + message);

      // 用括号
        GreetingService greetService2 = (message) ->System.out.println("Hello " + message);

      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
    }
    private int operate(int a, int b, MathOperation mathOperation) {
        return mathOtheration.opteration(a, b);
    }
}
interface MathOperation {
    int operation(int a, int b);
}
interface GreetingService {
    void sayMessage(String message);
}
  1. Lambda表达式注意:
    • Lambda表达式主要用来定义行内执行的方法类型接口,例如:一个简单方法接口。
    • Lambda表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
  2. Lambda变量作用域
    • Lambda表达式只能引用标记了final的外层局部变量,这就是说不能在lambda内部修改定义在域外的局部变量,否则会编译报错。
    • 可以直接在 lambda 表达式中访问外层的局部变量
    • lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
    • 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

静态代理(Proxy)

代理模式,简单来说,就是一个人专门负责干想干的事儿,其他事情不用你处理,会有人帮你处理

比如:你要结婚,你只需要happy,婚庆公司会帮你做好其他事物的。这就是代理模式。静态代理就是这一套流程是固定的,不会发生变化的,所以是静态代理。

public class StaticProxy {
    public static void main(String[] args) {
        new WeddingCompany(new You()).happyMarry();
        //new Thread(线程对象).start();
    }
}

interface Marry{
    /**
     * 结婚的接口
     */
    void happyMarry();
}

class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("you and you girlfriend 。。。");
    }
}

/**
 * 代理角色
 */
class WeddingCompany implements Marry{
    /**
     * 真实角色
     */
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void happyMarry() {
        this.ready();
        this.target.happyMarry();
        this.after();
    }

    private void ready(){
        System.out.println("布置主卧!");
    }

    private void after(){
        System.out.println("闹洞房!");
    }
}