带你了解多线程

@[toc]java

多线程

1、程序、进程、线程

一、程序

  • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

二、进程

  • 是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。面试

    1. 进程做为资源分配的单位,系统在运行时会为每一个进程分配不一样的内存区域
    2. 程序是静态的,进程是动态的

三、线程

  • 进程可进一步细化为线程,是一个程序内部的一条执行路径算法

    1. 若一个进程同一时间 并行执行多个线程,就是支持多线程的
    2. 线程做为调度和执行的单位,每一个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
    3. 一个进程中的多个线程共享相同的内存单元/内存地址空间---->它们从同一堆中分配对象,能够访问相同的变量和对象。这就使得线程间通讯更简便、高效。但多个线程操做共享的系统资源可能就会带来安全的隐患。

四、并行与并发

  • 区别:设计模式

    并行:多个CPU同时执行多个任务。好比:多我的同时作不一样的事。安全

    并发:一个CPU(采用时间片)同时执行多个任务。好比:秒杀、多我的作同一件事。多线程

2、线程的建立和使用

一、Thread类

1.一、Thread类的特性

  • 每一个线程都是经过某个特定Thread对象的run()方法来完成操做的,常常把run()方法的主体称为线程体
  • 经过该Thread对象的start()方法来启动这个线程,而非直接调用run()

1.二、构造器

  • Thread():建立新的Thread对象
  • Thread(String threadname):建立线程并指定线程实例名
  • Thread(Runnable target) :指定建立线程的目标对象,它实现了Runnable接口中的run方法
  • Thread(Runnable target, String name) :建立新的Thread对象

二、建立线程的第一种方式(继承Thread类)

  • JDK1.5以前建立新执行线程有两种方法:并发

    继承Thread类的方式app

    实现Runnable接口的方式ide

  • 继承Thread类工具

    定义子类继承Thread类。

    子类中重写Thread类中的run方法。

    建立Thread子类对象,即建立了线程对象。

    调用线程对象start方法:启动线程,调用run方法。

  • mt子线程的建立和启动过程

三、建立线程和使用的注意点

  • 若是本身手动调用run()方法,那么就只是普通方法,没有启动多线程模式
  • run()方法由JVM调用,何时调用,执行的过程控制都有操做系统的CPU调度决定。
  • 想要启动多线程,必须调用start方法
  • 一个线程对象只能调用一次start()方法启动,若是重复调用了,则将抛出以上的异常IllegalThreadStateException

建立线程代码以下:

package com.shsxt.thread;

/** * 多线程的建立,方式一:继承于Thread类 */
//1. 建立一个继承于Thread类的子类
class MyThread extends Thread {
    //2. 重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3. 建立Thread类的子类的对象
        MyThread mt = new MyThread();
        //4.经过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        mt.start();
        //问题一:咱们不能经过直接调用run()的方式启动线程。
        //mt.run();

        //问题二:再启动一个线程,遍历100之内的偶数。不能够还让已经start()的线程去执行。会报IllegalThreadStateException
        //t1.start();

        //咱们须要从新建立一个线程的对象
        MyThread mt2 = new MyThread();
        mt2.start();

        //以下操做仍然是在main线程中执行的。
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i + "******main*******");
            }
        }

    }
}

复制代码
public class ThreadDemo {

    public static void main(String[] args) {

        //建立Thread类的匿名子类的方式
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100 ; i++) {
                    if (i%2==0){
                        System.out.println(Thread.currentThread().getName()+":"+i);
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i % 2 != 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);

                    }
                }
            }
        }.start();
    }
}
复制代码

四、Thread类有关方法

  • void start(): 启动线程,并执行对象的run()方法

  • run(): 线程在被调度时执行的操做

  • String getName(): 返回线程的名称

  • void setName(String name):设置该线程名称

  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,一般用于主线程和Runnable实现类

  • static void yield():线程让步

    暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程

  • join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b彻底执行完之后,线程a才结束阻塞状态。

  • static void sleep(long millis) :(指定时间:毫秒);让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

  • boolean isAlive():返回boolean,判断线程是否还活着

五、线程调度

  • 调度策略

    1. 时间片:
    2. 抢占式: 高优先级的线程抢占CPU
  • Java的调度方法

    1. 同优先级线程组成先进先出队列(先到先服务),使用时间片策略
    2. 对高优先级,使用优先调度的抢占式策略

六、线程的优先级

  • 线程的优先级等级:

    MAX_PRIORITY :10 MIN _PRIORITY :1 NORM_PRIORITY :5

  • 涉及的方法

    getPriority() :返回线程优先值 setPriority(int newPriority) :改变线程的优先级

  • 注意点:

    线程建立时继承父线程的优先级

    低优先级只是得到调度的几率低,并不是必定是在高优先级线程以后才被调用

关于方法的一些使用,代码以下:

package com.shsxt.thread;

class HelloThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(getName() + ":" + getPriority() + ":" + i);
            }

            //yield()方法的使用(礼让线程)
// if (i % 20 == 0) {
// yield();
// }
        }
    }

    public HelloThread(String name) {
        //给子线程赋名字
        super(name);
    }
}

public class ThreadMethod {
    public static void main(String[] args) {
        //第一种方式:给子线程赋名字
        HelloThread h1 = new HelloThread("Thread:1");
        //第二种方式:给子线程赋名字
        //h1.setName("线程一");

        //给子线程设置优先级
        //h1.setPriority(Thread.MAX_PRIORITY);
        //启动子线程
        h1.start();

        //给主线程命名
        Thread.currentThread().setName("主线程");
        //给主线程设置优先级
        //Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }
            //调用join()方法,当i==20时主线程阻塞,子线程运行完后,主线程才运行
            if (i == 20){
                try {
                    h1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //判断线程是否还存活着
        System.out.println(h1.isAlive());
    }
}
复制代码

使用继承Thread类,写一个窗口卖票的练习

class Window extends Thread{
    //使用static关键字是防止new Window()每一个线程都有100张票
    //不使用static关键字的话,须要用到建立线程的第二种方式实现Runnable接口
    private static int tickets = 100;
    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(getName()+":卖票,票号为"+tickets);
                tickets--;
            }else {
                break;
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w = new Window();
        Window w1 = new Window();
        Window w2 = new Window();

        w.setName("窗口一");
        w1.setName("窗口二");
        w2.setName("窗口三");

        w.start();
        w1.start();
        w2.start();

    }
}
复制代码

七、建立线程的第二种方式(实现Runnable接口)

  • 实现Runnable接口

    1. 建立一个实现了Runnable接口的类
    2. 实现类去实现Runnable中的抽象方法:run()
    3. 建立实现类的对象
    4. 将此对象做为参数传递到Thread类的构造器中,建立Thread类的对象
    5. 经过Thread类的对象调用start()

线程建立,代码以下:

package com.shsxt.thread;

//1. 建立一个实现了Runnable接口的类
class MyThread1 implements Runnable {
    //二、实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}

public class ThreadTest1 {

    public static void main(String[] args) {
        //3. 建立实现类的对象
        MyThread1 myThread1 = new MyThread1();

        //4. 将此对象做为参数传递到Thread类的构造器中,建立Thread类的对象
        Thread t1 = new Thread(myThread1);
        t1.setName("线程1");
        //5. 经过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100之内的偶数
        Thread t2 = new Thread(myThread1);
        t2.setName("线程2");
        t2.start();

    }

}
复制代码

八、建立线程(继承Thread类和实现Runnable接口)的两种方式的异同

相同点:

两种方式都须要重写run(),将线程要执行的逻辑声明在run()中。

不一样点:

开发中:优先选择:实现Runnable接口的方式

缘由:一、实现了Runnable接口的方式解决了类的单继承性的局限性

二、实现Runnable接口的方式更适合来处理多个线程有共享数据的状况。

使用继承Thread类,写一个窗口卖票的练习:

package com.shsxt.thread;

/** * 建立三个窗口卖票,总票数为100张.使用实现Runnable接口的方式 * @author Rainbow * @date 2020/7/15 10:31 */
class Window1 implements Runnable {
    private int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + tickets);
                tickets--;
            } else {
                break;
            }
        }
    }
}

public class WindowTest1 {

    public static void main(String[] args) {
        Window1 w1 = new Window1();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);
        Thread t3 = new Thread(w1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

复制代码

3、线程的生命周期

  • JDK 中用Thread.State 类定义了 线程的几种状态

要想实现多线程,必须在主线程中建立新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中一般要经历以下的 五种状态:

  • 新建:当一个Thread类或其子类的对象被声明并建立时,新生的线程对象处于新建状态
  • 就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具有了运行的条件,只是没分配到CPU资源
  • 运行:当就绪的线程被调度并得到CPU资源时,便进入运行状态, run()方法定义了线程的操做和功能
  • 阻塞:在某种特殊状况下,被人为挂起或执行输入输出操做时,让出 CPU 并临时停止本身的执行,进入阻塞状态
  • 死亡:线程完成了它的所有工做或线程被提早强制性地停止或出现异常致使结束

一、线程生命周期图

4、线程的同步

首先举个例子看下:

package com.shsxt.day;

/** * @author Rainbow * @date 2020/7/15 9:15 */

class Window extends Thread {

    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(getName() + ":卖票,票号为" + tickets);
                tickets--;
            } else {
                break;
            }
        }
    }
}

/** * @author Rainbow */
public class WindowTest {
    public static void main(String[] args) {
        Window w = new Window();
        Window w1 = new Window();
        Window w2 = new Window();

        w.setName("窗口一");
        w1.setName("窗口二");
        w2.setName("窗口三");

        w.start();
        w1.start();
        w2.start();

    }
}

复制代码

由此代码看出,发现有线程安全问题:理想状态下

极端状态:

  • 由上述代码能够看出出现了线程安全问题

  • 问题的缘由:

    当多条语句在操做同一个线程共享数据时,一个线程对多条语句只执行了一部分,尚未执行完,另外一个线程参与进来执行。致使共享数据的错误。

  • 解决办法:

    对多条操做共享数据的语句,只能让一个线程都执行完,在执行过程当中,其余线程不能够参与执行。即便有条线程发生了阻塞,也不能改变,也得等这条线程执行完毕,其余线程才能执行

一、Synchronized的使用方法

  • Java 对于多线程的安全问题提供了专业的解决方式 : 同步机制

    1. 同步代码块

      synchronized(同步监视器){

      ​ //须要被同步的代码

      }

    2. synchronized 还能够放在方法声明中,表示整个方法为同步方法

      public synchronized void show (){ …. }

  • 关于以上名词的说明:

    同步的代码:操做共享数据的代码 ----------->(同步的范围)同步的代码不能被同步代码块包含多了,也不能包含少了

    ***同步监视器(俗称:锁)***:任何一个类的对象,均可以充当锁。要求:多个线程必须共同拥有一把锁

  • 同步机制中的锁和注意事项:

    一、任意对象均可以做为同步锁。全部对象都自动含有单一的锁(监视器)

    二、同步方法的锁:静态方法(类名.class)、非静态方法(this)

    三、同步代码块:本身指定,不少时候也是指定为this或类名.class

    注意事项:

    一、必须确保使用同一个资源的 多个线程共用一把锁,这个很是重要,不然就没法保证共享资源的安全

    二、 一个线程类中的全部静态方法共用同一把锁(类名.class),全部非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

  • 使用同步方式的优缺点:

    优势:解决了线程的安全问题。

    缺点:操做同步代码时,只能有一个线程参与,其余线程等待。至关因而一个单线程的过程,效率低

二、使用同步代码块的方式解决实现Runnable接口的线程安全问题

  • 代码以下:
package com.shsxt.thread;

/** * 在实现Runnable接口建立多线程的方式中,咱们能够考虑使用this充当同步监视器。 * @author Rainbow * @date 2020/7/15 16:22 */
class Window1 implements Runnable {
    private int ticket = 100;
    //使用同步代码块的第一种解决方式
    Object obj = new Object();

    @Override
    public void run() {
        while (true) {
// synchronized (obj) {
           //使用同步代码块的第二种解决方式
            synchronized (this) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
// }

        }
    }
}

public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
复制代码

三、使用同步代码块解决继承Thread类的方式的线程安全问题

package com.shsxt.thread;

/** * 在继承Thread类建立多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。 * * @author Rainbow * @date 2020/7/15 16:33 */
class Window2 extends Thread {
    private static int ticket = 100;
    private static Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            //正确的方式:
// synchronized (obj) {
            synchronized (Window2.class) { //Class clazz = Window2.class,Window2.class只会加载一次
                //错误的方式:
// synchronized (this) {//此时的this表明着t1,t2,t3三个对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
// }
            }
// }
        }
    }
}

public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}

复制代码

四、使用同步方法解决实现Runnable接口的线程安全问题

package com.shsxt.thread;

/** * @author Rainbow * @date 2020/7/15 16:40 */
class Window3 implements Runnable {
    private int ticket = 100;
    boolean flag = true;

    @Override
    public void run() {

        while (flag) {
            show();
        }
    }

    private synchronized void show() {//同步监视器:this
        //至关于下面的方式
// synchronized (this) {
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        } else {
            flag = false;
        }
// }
    }
}

public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w = new Window3();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

复制代码

五、使用同步方法处理继承Thread类的方式中的线程安全问题

package com.shsxt.thread;


/** * 使用同步方法处理继承Thread类的方式中的线程安全问题 * @author Rainbow * @date 2020/7/15 16:59 */
class Window4 extends Thread {
    static boolean flag = true;
    private static int ticket = 100;


    @Override
    public void run() {

        while (flag) {
            show();
        }
    }

    private static synchronized void show() {//同步监视器:Window4.class
        //没有static关键字修饰,此时的同步监视器为:t1,t2,t3;此种解决方式是错误的
// private synchronized void show(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        } else {
            flag = false;
        }
// }

    }
}

public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();

    }
}
复制代码

六、单例设计模式之懒汉式( 线程安全)

package com.shsxt.thread;

/** * 单例线程安全的懒汉模式 * @author Rainbow * @date 2020/7/15 17:08 */
class Singleton {
    private static Singleton instance = null;

    private Singleton() {

    }

    public static Singleton getInstance() {

        //方式1、效率低
// synchronized (Singleton.class) {
// if (instance == null) {
// instance = new Singleton();
// }
// return instance;
// }

        //方式2、效率高
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance1 = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        Singleton instance3 = Singleton.getInstance();

        System.out.println(instance);
        System.out.println(instance1);
        System.out.println(instance2);
        System.out.println(instance3);

    }

}
复制代码

5、线程的死锁问题

  • 死锁问题的产生

    1. 不一样的线程分别占用对方须要的同步资源不放弃,都在等待对方放弃本身须要的同步资源,就造成了线程的死锁
    2. 出现死锁后,不会出现异常,不会出现提示,只是全部的线程都处于阻塞状态,没法继续
  • 解决方法

    用专门的算法、原则

    尽可能减小同步资源的定义

    尽可能避免嵌套同步

  • 案例

    package com.shnsxt.thread;
    
    /** * @author Rainbow * @date 2020/7/15 21:28 */
    public class ThreadTest {
        public static void main(String[] args) {
            StringBuffer s1 = new StringBuffer();
            StringBuffer s2 = new StringBuffer();
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        //增大产生死锁的概率
                        try {
                            sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        synchronized (s2){
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
    
                }
            }.start();
    
    
            new Thread(){
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
                    }
    
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
    
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }.start();
    
        }
    }
    复制代码

6、建立线程的第三种方式:Lock锁(JDK5.0提供)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——经过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源以前应先得到Lock对象
  • ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较经常使用的是ReentrantLock,能够显式加锁、释放锁。

一、Synchronized与Lock的异同?

  • 相同点:

    两者均可以解决线程安全问题

  • 不一样点:

    一、synchronized机制在执行完相应的同步代码之后,自动的释放同步监视器

    二、Lock须要手动的启动同步(lock()方法)紧跟try代码块同时结束同步也须要手动的实现(unlock()方法)且必须放在finally的首行

  • 优先使用顺序

    Lock ---> 同步代码块(已经进入了方法体,分配了相应资源)----> 同步方法(在方法体以外)

使用Lock锁的案例:

package com.shnsxt.thread;

import java.util.concurrent.locks.ReentrantLock;

/** * @author Rainbow * @date 2020/7/15 21:41 */
class Window implements Runnable {
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                //2.调用锁定方法lock()
                lock.lock();

                if (ticket > 0) {

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }

            } finally {
                //3.调用解锁方法:unlock(),且必须放在finally的首行
                lock.unlock();

            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
复制代码

7、对以上知识点的一个小练习

package com.shnsxt.thread;

/** * 银行有一个帐户。 * 有两个储户分别向同一个帐户存3000元,每次存1000,存3次。每次存完打印帐户余额。 * @author Rainbow * @date 2020/7/15 21:52 */

class Account{
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    //存钱
    public synchronized void deposit(double AMB) {//同步监视器:this;虽说使用继承Thread类慎用this,可是此处的this不表明Customer,而是Account
        if (AMB>0){
            balance+=AMB;

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ":存钱成功。余额为:" + balance);
        }
    }
}
//储户
class Customer extends Thread{
    private Account account;

    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3 ; i++) {
            account.deposit(1000);
        }
    }
}
public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(0);
        Customer customer = new Customer(account);
        Customer customer1 = new Customer(account);

        customer.setName("甲");
        customer1.setName("已");

        customer.start();
        customer1.start();

    }
}

复制代码
package com.shnsxt.thread;

/** * @author Rainbow * @date 2020/7/16 9:37 */

class Blank {
    private String accountId;
    private double balance;

    public Blank(String accountId, double balance) {
        this.accountId = accountId;
        this.balance = balance;
    }

    public double getBalance() {
        return balance;
    }

    public String getAccountId() {
        return accountId;
    }

    public void setAccountId(String accountId) {
        this.accountId = accountId;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Blank{" +
                "accountId='" + accountId + '\'' +
                ", balance=" + balance +
                '}';
    }
}

class DrawThread extends Thread {
    private Blank blank;
    //取款额度
    private double money;

    public DrawThread(String name, Blank blank, double money) {
        super(name);
        this.blank = blank;
        this.money = money;
    }

    @Override
    public void run() {
        synchronized (blank) {
            if (blank.getBalance() > money) {
                System.out.println(Thread.currentThread().getName() + ":取款成功," + "取现的金额为" + money);

                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                blank.setBalance(blank.getBalance() - money);

            } else {
                System.out.println("取现额度超过帐户余额,取款失败");
            }
        }
        System.out.println(blank.getAccountId() + "帐户的余额为:" + blank.getBalance());
    }
}

public class DrawThreadTest {

    public static void main(String[] args) {
        Blank blank = new Blank("中国银行", 100000.00);

        DrawThread d1 = new DrawThread("张三", blank, 4000);
        DrawThread d2 = new DrawThread("李四", blank, 5000);
        DrawThread d3 = new DrawThread("王五", blank, 8000);

        d1.start();
        d2.start();
        d3.start();
    }

}
复制代码

8、线程的通讯

  • 首先先给个案例,先理解下:使用两个线程印 打印 1-100 。线程1, 线程2 交替打印
package com.shnsxt.thread;

/** * @author Rainbow * @date 2020/7/15 22:07 */

class Number implements Runnable {
    private int number = 1;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (number <= 100) {

                    //唤醒被wait的一个线程。
                    obj.notify();

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;

                    try {
                        //使得调用以下wait()方法的线程进入阻塞状态
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}

public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);

        t1.setName("线程1");
        t2.setName("线程2");

        t1.start();
        t2.start();
    }
}

复制代码
  • 图解:

一、关于上述代码中涉及到三个方法:wait() 与 notify() 和 notifyAll()

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(锁)。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程,若是有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒全部被wait的线程。

二、关于这三个方法的注意点:

  • wait()notify()notifyAll()三个方法必须使用在同步代码块或同步方法中
  • wait()notify()notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器(锁)。不然,会出现IllegalMonitorStateException异常
  • wait()notify()notifyAll()三个方法是定义在java.lang.Object类中。

三、sleep()和wait()方法的异同(面试题)

  • 相同点:一旦执行方法,均可以使得当前的线程进入阻塞状态。

  • 不一样点:

    一、两个方法声明的位置不一样:Thread类中声明sleep() , Object类中声明wait()

    二、调用的要求(范围)不一样:sleep()能够在任何须要的场景下调用,wait()必须使用在同步代码块同步方法中

    三、关因而否释放同步监视器(锁):若是两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁wait()会释放锁

9、关于线程通讯问题总结小练习(消费者与生产者)

package com.shnsxt.thread;

/** * 经典例题:生产者/消费者的问题 * * @author Rainbow * @date 2020/7/16 9:07 */
class Clerk {
    private int productCount = 0;

    /** * 生产产品 */
    public synchronized void produceProduct() {
        if (productCount < 20) {
            productCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "产品");

            //线程唤醒
            this.notify();

        } else {

            //线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /** * 消费产品 */
    public synchronized void consumerProduct() {
        if (productCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "产品");
            productCount--;

            //线程唤醒
            this.notify();

        } else {
            //线程等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/** * 生产者 */
class Producer extends Thread {
    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 开始生产产品......");
        while (true) {

            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //生产产品
            clerk.produceProduct();
        }

    }
}

/** * 消费者 */
class Consumer extends Thread {
    private Clerk clerk;

    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(getName() + ": 开始消费产品......");

        while (true) {

            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //消费产品
            clerk.consumerProduct();
        }
    }
}

public class ProducetTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        //生产者
        Producer p1 = new Producer(clerk);
        p1.setName("生产者");
        //消费者
        Consumer c1 = new Consumer(clerk);
        c1.setName("消费者1");
        Consumer c2 = new Consumer(clerk);
        c2.setName("消费者2");

        p1.start();
        c1.start();
        c2.start();
    }

}
复制代码

10、建立线程的第四种方式之一:实现Callable接口(JDK5.0新增)

  • 与使用Runnable相比, Callable功能更强大些

    一、 相比run()方法,能够有返回值

    二、 方法能够抛出异常

    三、支持泛型的返回值

    四、须要借助FutureTask类,好比获取返回结果

  • Future接口

    一、 能够对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

    二、FutrueTask是Futrue接口的惟一的实现类

    三、 FutureTask 同时实现了Runnable, Future接口。它既能够做为Runnable被线程执行,又能够做为Future获得Callable的返回值

代码理解,以下所示:

package com.shnsxt.thread;

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

/** * 建立线程的方式三:实现Callable接口。 --- JDK 5.0新增 * 1. call()能够有返回值的。 * 2. call()能够抛出异常,被外面的操做捕获,获取异常的信息 * 3. Callable是支持泛型的 * * @author Rainbow * @date 2020/7/16 10:31 */
//1.建立一个实现Callable的实现类
class NumThread implements Callable<Integer> {

    //2.实现call方法,将此线程须要执行的操做声明在call()方法中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                sum += i;
            }
        }
        return sum;
    }
}

public class ThreadCallable {
    public static void main(String[] args) {
        //3.建立Callable接口实现类的对象
        NumThread numThread = new NumThread();

        //4.将此Callable接口实现类的对象做为传递到FutureTask构造器中,建立FutureTask的对象
        FutureTask<Integer> futureTask = new FutureTask(numThread);

        //5.将FutureTask的对象做为参数传递到Thread类的构造器中,建立Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Integer sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
复制代码

11、建立线程的第四种方式之二:使用线程池(JDK5.0新增)

  • 为何要用线程池?

    常常建立和销毁、使用量特别大的资源,好比并发状况下的线程,对性能影响很大。

  • 使用线程池的好处

    一、提升响应速度(减小了建立新线程的时间) 二、下降资源消耗(重复利用线程池中线程,不须要每次都建立)

    便于线程的管理:

    一、corePoolSize:核心池的大小

    二、maximumPoolSize:最大线程数

    三、keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • 线程池相关的API

    JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors

    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    1. void execute(Runnable command) :执行任务/命令,没有返回值,通常用来执行Runnable
    2. <T> Future <T> submit(Callable<T> task):执行任务,有返回值,通常又来执行Callable
    3. void shutdown() :关闭链接池

    Executors:工具类、线程池的工厂类,用于建立并返回不一样类型的线程池

    1. Executors.newCachedThreadPool():建立一个可根据须要建立新线程的线程池,主要的问题是线程数最大数Integer.MAX_VALUE,可能会建立数量很是多的线程
    2. Executors.newFixedThreadPool(n): 建立一个可重用固定线程数的线程池,主要问题是堆积的请求处理队列可能会损耗很是大的内存
    3. Executors.newSingleThreadExecutor() :建立一个只有一个线程的线程池,主要问题是堆积的请求处理队列可能会损耗很是大的内存
    4. Executors.newScheduledThreadPool(n):建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。主要的问题是线程数最大数Integer.MAX_VALUE,可能会建立数量很是多的线程

代码理解,以下所示:

package com.shnsxt.thread;

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


/** * 建立线程的方式四之二:使用线程池 * 好处: * 1.提升响应速度(减小了建立新线程的时间) * 2.下降资源消耗(重复利用线程池中线程,不须要每次都建立) * 3.便于线程管理 * corePoolSize:核心池的大小 * maximumPoolSize:最大线程数 * keepAliveTime:线程没有任务时最多保持多长时间后会终止 * * @author Rainbow * @date 2020/7/16 11:08 */
class NumberThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {

        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//这里可能会出现上面写出的问题
        ThreadPoolExecutor pool = (ThreadPoolExecutor) service;
        //设置线程池的属性
// pool.setCorePoolSize(15);
// pool.setKeepAliveTime(60,MINUTES);

        //2.执行指定的线程的操做。须要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

// service.submit(Callable callable);//适合使用于Callable

        //3.关闭链接池
        service.shutdown();
    }
}
复制代码

若是,此文章对你有所帮助,请帮忙点个赞呗! 谢谢!!!

励志图
相关文章
相关标签/搜索