day21idea常用设置和多线程

一.idea

1.Module

  • 分为project和module:比如在project-Senior里创建module-section;
  • 大型项目的分布式部署


    分布式部署.png

二.多线程

1.概念:程序、进程、线程

  • 程序program:为了完成特定任务、用某种语言编写的一组指令的几何。即指一段静态的代码,静态对象。

  • 进程process:程序的一次执行过程,或是正在运行的一个程序,是动态的,是资源分配的单位(内存)。生命周期:产生、存在、消亡。

  • 线程thread:进程的进一步细化,程序内部的一条执行路径。
    1.若一个进程同时并行执行多个线程,就是支持多线程的
    2.线程作为调度和执行的单位,每个线程拥有独立的运行和程序计数器

    每个线程各自有一套vmStack和程序计数器,而方法区和堆是属于进程,而线程共享他们.png

    3.一个进程的多个线程共享相同内存-》从同一堆中分配对象,可以访问相同的变量和对象,使得多个线程间通信更简便高效,但有安全隐患

  • 单核和多核CPU的理解:
    单核CPU是一种假的多线程,一个时间单元只能执行一个线程。

  • 并行与并发:
    并行:多个cpu同时执行多个任务
    并发:一个cpu同时执行多个任务。(其实是快速切换任务,多个线程作同一个事),秒杀。

  • 多线程的优点:
    1.提高应用程序的响应
    2.提高计算机系统CPU利用率
    3.改善程序结构。

  • 何时需要多线程
    1.程序需要同时执行两个或多个任务
    2.程序需要实现一些需要等待的任务
    3.需要一些后台运行程序时候

2.线程的创建和使用

2.1线程的创建方式一
  • JVM允许程序运行多个线程,通过java.land.Thred类来实现。
  • Tread类特性:每个线程都是通过某个特定的Tread
  • 方式一:
    1.创建一个基础于Thread类的子类
    2.重写Thred类的run方法-》将此线程执行的操作声明在run中
    3.创建Thread类的子类对象
    4.通过此对象调用start方法
//1
class MyThread extends Thread{
    //2
    @Override
    public void run() {
        //线程要作的事:
        for (int i=0;i<100;i++)
        {
            if(i%2==0)
                System.out.println(i);
        }
    }
}

public class CreationI {
    public static void main(String[] args) {
        //3
        MyThread t1 = new MyThread();
        //4
        t1.start();

        //main线程
        for (int i=0;i<100;i++)
        {
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName() + ":"+"***"+i);
        }
    }
}
//此时main的主线程和t1同时执行
  • Thread类中的void start()用来1.让线程开始执行,2.JVM调用当前线程的run方法
  • 问题一:如果不用start而直接调用run方法呢?
    这样的话就不是多线程,就还在主线程上调用了重写的run方法。
System.out.println(Thread.currentThread().getName() + ":"+ i);
//会发现全是main线程
//main:0
//main:2
//。。。

如果改回start方法
//main:***1
//main:***3
//Thread-0:0
//Thread-0:2
  • 问题二:在启动一个线程,遍历100内偶数,也就是在调用一次t1.start():java.lang.IllegalThreadStateException,由下面的源码看来threadStatus已经不是0了。需要新建一个对象才可以
//source code
public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();


        MyThread t1 = new MyThread();
        t1.start();

        MyThread t2 = new MyThread();
        t2.start();
//出现thread 1,thread 2,main三个线程
  • 继承方式的练习:
/**
 * @author : liulinzhi
 * @date: 2020/06/29/20:49
 * @description:
 * 创建两个分线程,一个遍历100内偶数,另一个编译100以内奇数
 *
 */
class MyThread0 extends Thread{
    //2
    @Override
    public void run() {
        //线程要作的事:
        for (int i=0;i<100;i++)
        {
            if(i%2==0)
                System.out.println(Thread.currentThread().getName() + ":"+ i);
        }
    }
}

class MyThread1 extends Thread{
    //2
    @Override
    public void run() {
        //线程要作的事:
        for (int i=0;i<100;i++)
        {
            if(i%2!=0)
                System.out.println(Thread.currentThread().getName() + ":"+ i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread0 t1 = new MyThread0();
        //4
        t1.start();

        MyThread1 t2 = new MyThread1();
        t2.start();
    }
}
  • 方法二:匿名类:(new 父类(){子类内容})
public static void main(String[] args) {
        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();

在多态中,子类对象向上提升为父类类型后,对于数据域和静态的,编译和运行都是父类的,但是方法却是编译看父类有没有,没有就不通过编译,运行则是执行子类的方法

2.2Thread有关方法
package ThreadAPI;

/**
 * @author : liulinzhi
 * @date: 2020/06/30/8:58
 * @description:THread相关方法
 * 1.start():启动当前线程;调用当前线程的run方法
 * 2.run():通常需要重写Thread类中的方法,将创建的线程
 * 要执行的操作声明在该方法中
 * 3.currentThread:静态方法,返回当前代码执行的线程
 * 4.getName:获取当前线程的名字
 * 5.setName:设置线程名字
 * 6.yield():释放当前CPU的执行权
 * 7.join:在线程A中调用B的join方法,此时线程A进入
 * 阻塞状态,直到B执行完毕
 * 8.sleep(long milltime):当前线程睡眠指定毫秒
 * 9.isAlive():判断当前线程是否存货
 */
class MyThread extends Thread{
    @Override
    public void run(){
        for (int i=0;i<100;i++)
        {
            if(i%2==0){
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":"+ i);

            }

//            if(i%20==0)
//                this.yield();
        }
    }

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

}

public class ThreadAPI {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("线程:一");
        //构造方法改线程名

//        t1.setName("线程一");

        t1.start();

        Thread.currentThread().setName("主线程");
        for (int i=0;i<100;i++)
        {
            if(i%2==0)
                System.out.println(Thread.currentThread().getName() + ":"+ i);
            if(i==20) {
                try {
                    t1.join();//先执行t1线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(t1.isAlive());

    }
}

  • 线程优先级的设置
    线程调度.png

    线程优先级.png

    并不是优先级高的一定在低的之前,只是概率高
  • 例题:多窗口卖票
package exer;

/**
 * @author : liulinzhi
 * @date: 2020/06/30/9:45
 * @description:创建三个窗口卖票,总票数100张
 *
 */
class Window extends Thread{
    private static int ticket = 100;
    //公用的


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

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

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

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

    }
}

虽然static公用了票,但此时还是线程不安全的,涉及到同步问题。

2.3第二种创建多线程的方式:实现Runnable接口
  • 实现Runnable接口的run方法
/**
 * @author : liulinzhi
 * @date: 2020/06/30/10:37
 * @description:第二种多线程创建方式
 * 1.创建实现了Runnable接口的类
 * 2.实现类实现Runnable的run方法
 * 3.创建实现类的对象
 * 4.将此对象作为参数传到Thread类的构造器中,创建
 * Thread类对象,
 * 5.通过它调用start方法
 */
class MThread implements Runnable{
    @Override
    public void run(){
        for (int i=0;i<100;i++)
        {
            if(i%2==0){
                System.out.println(Thread.currentThread().getName() + ":" +Thread.currentThread().getPriority() +"+:"+ i);

            }
    }
}}

public class CreationII {
    public static void main(String[] args) {
        MThread t = new MThread();
        Thread t1 = new Thread(t);
        t1.start();
    }
}

因为Thread(Runnable target)传入实现了Runnable的类对象,造成Thread里的run方法变成了该类对象的run方法

  • Runnable接口实现多窗口卖票
package exer;

/**
 * @author : liulinzhi
 * @date: 2020/06/30/10:57
 * @description:
 */
class WindowII implements Runnable{
    private int ticket = 100;
    //公用的
    @Override
    public void run(){
        while(true)
        {
            if(ticket>0)
            {
                System.out.println(Thread.currentThread().getName()
                        + ":买票,票号尾:" + ticket);
                ticket--;
            }else
                break;
        }
    }
}

public class WindowTestII {
    public static void main(String[] args) {
        WindowII t = new WindowII();
        //同一个t,所谓三个线程公用ticket
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

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

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

    }
}

  • 两种方式的对比
    一般用Runnable方式1. 因为不能多继承有局限,所以实现接口更好;2.线程共享同一个对象的内容,比如上面的t,而继承的方式资源不共享,需要static
    联系:Thread类也实现了Runnable接口,接口实现类覆盖了Runnable的run方法。
3.线程的生命周期
  • Thread类下的State类定义了线程的状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED;


    线程的状态.png

    状态转换.png
4.线程的同步
4.1线程安全问题
  • 多个线程执行的不确定性引起执行结果的不稳定;
  • 如果多个线程共享数据,会造成不安全;例如买票的例子,出现重票、错票


    多窗口买票.png
  • 原因:当某个线程操作车票过程中,尚未完成时,其他线程参与进来,也操作车票
  • 如何解决:当一个线程在操作共享数据(ticket)其他线程不能参与,直到该线程操作完毕该共享数据,采允许其他线程操作。(即使该线程过程中出现阻塞
4.2 同步机制解决安全问题
  • java最初提供了两种方法:
    1.同步代码块:可以解决线程同步问题,操作同步代码时只能有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低。
    注意不能包含多了(可能出错,比如这里把while放进,就一直是同一个窗口了),也不能少了

    示意图.png
针对实现Runnable接口的类,定义Object对象作为类的属性,
充当同步监视器(锁):
synchronized(同步监视器){
    //需要被同步的代码    
}
说明:操作共享数据的代码,就是需要被同步的代码。
共享数据:多个线程共同操作的数据;
同步监视器:俗称:锁;任何一个类的对象都可以充当锁。
要求:多个线程必须共用同一把锁
解决了不安全问题!

Object o = new Object();
    //公用的
    @Override
    public void run(){
        while(true)
        {
            synchronized(o) {
            if(ticket>0)
            {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                System.out.println(Thread.currentThread().getName()
                        + ":买票,票号:" + ticket);
                ticket--;
            }else
                break;
        }
    }}




针对继承实现Thread的创建方法:
这是因为创建的是三个类对象,上面的
Obj对象各自不同,所以需要加static共享
才能作为唯一锁
private static Object o = new Object();
    //公用的
    @Override
    public void run(){
        while(true)
        {
            synchronized (o){
            if(ticket>0)
            {
                System.out.println(Thread.currentThread().getName()
                + ":买票,票号尾:" + ticket);
                ticket--;
            }else
                break;
        }}
    }

这样每次创建一个锁对象,太烦。Runnable接口方式可以直接用this作为锁,继承Thread类方式可以用当前类充当(类名.class)作为锁

2.同步方法解决安全问题:如果操作共享数据的代码完整的声明在一个方法中,我们不妨将这个方法声明同步的。

1.对Runnable接口:


    private synchronized void show() {
//同步监视器就是this
        if (ticket > 0) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            System.out.println(Thread.currentThread().getName()
                    + ":买票,票号:" + ticket);
            ticket--;
        }
    }


    //公用的
    @Override
    public void run() {
        while (true) {
//            synchronized(o) {
            show();
            if(ticket==0)
                break;
        }
    }
}

2.继承创建线程类型:
必须加static变成类相关,否则就是this作为锁。明显三个对象不同。
private static synchronized void show() {
        //不安全,锁的问题,this不同。必须static
        if (ticket > 0) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            System.out.println(Thread.currentThread().getName()
                    + ":买票,票号:" + ticket);
            ticket--;
        }
    }

    //公用的
    @Override
    public void run() {
        while (true) {
            show();
            if (ticket == 0)
                break;
        }
    }
}
  • 关于同步方法的总结:同步方法仍然设计到同步监视器,只是不需要显式声明。非静态的同步监视器是this,静态的同步方法,同步监视器是当前类本身
4.3 单例模式
复习:
单例设计模式:对某个类之只能存在一个对象实例,
且该类只提供一个取得其对象实例的方法。构造函数设置为private,
此时就只能在类内部定义对象。
由类内部的静态方法来返回该对象(静态方法只能访问类中静态成员变量,
所以变量也要定义为静态的)
 * 2.饿汉模式
 *public class DesignMode {
 *     public static void main(String[] args) {
 * //        Bank b = new Bank();私有后不能构建
 *         Bank bank1 = Bank.getInstance();
 *     }
 * }
 *
 * class Bank{
 *     private Bank(){}
 *     private static Bank instance = new Bank();
 *     public static Bank getInstance()
 *     {
 *         return instance;
 *     }
 *
 * }
 *
 * 3.懒汉模式
 *public class DesignMode {
 *     public static void main(String[] args) {
 * //        Bank b = new Bank();私有后不能构建
 *         Bank bank1 = Bank.getInstance();
 *     }
 * }
 *
 * class Bank{
 *     private Bank(){}
 *     //先声明类对象实例,不初始化
 *     //static变量随类加载,被所有对象共享
 *     private static Bank instance = null;
 *     public static Bank getInstance()
 *     {
 *         if(instance == null)
 *             instance = new Bank();//防止多次开堆空间
 *         return instance;
 *     }
 *
 * }
 *
 * 4.懒汉饿汉的区别:对比
 * 饿汉:对象生命周期过长,线程安全
 * 懒汉:延迟对象创建,目前写法线程不安全(
 * 如两个线程都同时调用getInstance())——》多线程
改造饿汉模式
class Bank{
    private Bank(){}

    private static Bank instance = null;

    public static synchronized Bank getInstance() {//Bank.class作为锁
//        synchronized (Bank.class) {
            if (instance == null) {
                instance = new Bank();

            }

            return instance;
        }
//    }
}
效率高的方式二:
if(instance ==null)
        {
            synchronized (Bank.class){
            if (instance == null) {
                instance = new Bank();
            }
        }}
        return instance;
        }
5.线程的通信
5.1死锁问题:
  • 概念:不同的线程分别占用对方的资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁。出现死锁后,不会出现异常和提示,只是所有线程处于阻塞状态,无法继续。
package Lock;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/9:28
 * @description:死锁
 */
public class Lock {
    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 {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2)
                    {
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        //匿名对象(实现Runnable接口的匿名类+new)
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2)
                {
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1)
                    {
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

5.2 JDK5提供Lock
  • 提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁用Lock对象充当;
  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象。
  • ReenTrantLock类实现了Lock,可以显式加锁、释放锁。
3.解决同步问题方式三:
TeentrantLock
package Lock.JDK5Lock;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/9:53
 * @description:解决线程安全问题方式三:
 *1.实例化ReentrantLock对象;
 * 2.调用锁  lock
 * 3.解锁 unlock
 *
 * 对比:
 * synchronized和lock异动:
 * 同:二者都可以解决线程安全问题
 * 不同:前者自动释放锁(执行完同步代码块),后者手动释放
 * 建议优先用Lock->同步代码块(已经进入方法体,分配了资源)
 * ->同步方法(在方法体外)
 *
 * 如何解决线程安全问题?有几种?
 */
class Window implements Runnable {
    private int ticket = 100;

    //   turn:FIFO
    private ReentrantLock lock = new ReentrantLock(true);

    @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解锁
                lock.unlock();
            }
        }
    }
}

public class JDK5Lock {
    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();
    }
}

5.3 练习

练习一:


image.png
package exer;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/10:33
 * @description:
 */
class BankDemo implements Runnable
{
    private int store = 3000;
    private ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        while(true)
        {   try{
            lock.lock();

            if (store != 0)
                System.out.println(Thread.currentThread().getName()
            +  "now bank have "+ store +" store 1000;" );
            else
                break;
            store -= 1000;
        }finally{
            lock.unlock();
        }
        }
    }
}

public class BankTestI {
    public static void main(String[] args) {
        BankDemo b = new BankDemo();

        Thread t1 = new Thread(b);
        Thread t2 = new Thread(b);

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

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

  • 练习2:线程的通信


    image.png
package exer;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/10:44
 * @description:两个线程交替打印
 * wait():当前线程进入阻塞,释放同步监视器
 * notify():一旦执行该方法,唤醒被wait的一个线程,如果
 * 多个线程被wait,唤醒优先级最高的
 * notifyAll():唤醒所有wait线程
 *
 * 注意:线程同行:
 * 1.只能用在同步代码块或同步方法用。
 * 2.这三个方法的调用者必须是同步监视器(锁),否则报错
 * 3.三个方法定义在java.lang.Object类,
 * 因为任何一个类对象都可以做锁,故定义在Object类
 * 才能任意对象调用这三个方法
 *
 */
class Number implements Runnable
{
    private int number = 1;

    @Override
    public void run() {
        while(true)
        {
            synchronized (this) {
                notify();//唤醒一个线程,看优先级
                if (number <=100) {
                    System.out.println(Thread.currentThread().getName()
                            + " number: " + number);
                    number++;

                    try {
                        wait();//调用该方法的线程阻塞,释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else
                    break;
            }
        }
        }
    }

public class CommunicationTest {
    public static void main(String[] args) {
        Number b = new Number();

        Thread t1 = new Thread(b);
        Thread t2 = new Thread(b);

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

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

6.sleep()和wait()异同
  • 相同:一旦执行,就阻塞当前进程
  • 不同
    1)两个方法声明位置不同,Tread类声明sleep,而Object类声明wait
    2)调用要求不同:sleep可以任意场景调用;而wait必须在同步代码块中调用
    3)wait阻塞的同时释放了同步监视器(锁)
7.生产者和消费者
  • 以前都是对同一个类的共享数据用同一个同步监视器,这里对生产者消费者两个类同步也要用同一个同步监视器clerk对象;


    经典问题.png
package exer;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/11:13
 * @description:生产者消费者问题 1.是否是多线程:是,生产者线程,消费者线程
 * 2.线程安全问题:因为有共享数据,所以有
 * 3.如何解决线程安全问题:同步机制,三种方法
 * 4.线程间通信,wait notify
 */
class Clerk {
    private int productCount = 0;

    public synchronized void produceProduct() {
        if (productCount < 20) {
            System.out.println("生产者" + Thread.currentThread().getName()
                    + "开始生产第" + productCount +"个产品");
            productCount++;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumeProduct() {
        if (productCount > 0) {
            System.out.println("消费者" + Thread.currentThread().getName()
                    + "开始消费第" + productCount +"个产品");
            productCount--;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

class Producer extends Thread {
    private Clerk clerk;

    @Override
    public void run() {
        System.out.println("生产者" + Thread.currentThread().getName() + "开始生产产品");
        while (true) {
            clerk.produceProduct();
        }
    }

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

}

class Concumer extends Thread {
    private Clerk clerk;

    @Override
    public void run() {
        System.out.println("消费者" + Thread.currentThread().getName() + "开始生产产品");
        while (true) {
            clerk.consumeProduct();
        }
    }

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

public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();//锁唯一

        Producer p1 = new Producer(clerk);
        p1.setName("生产者");

        Concumer c1 = new Concumer(clerk);
        c1.setName("消费者");

        p1.start();
        c1.start();


    }
}

8.创建多线程方式三:Callable接口(JDK5)
  • 比起Runnable接口更强大:
    1)相比run方法,可以有返回值;
    2)方法可以抛出异常;
    3)支持泛型的返回值;
    4)需要借助Future Task类,比如获取返回结果,该类实现了Runnable和Callable接口,也是Future的唯一实现类
package CreationI;

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

/**
 * @author : liulinzhi
 * @date: 2020/07/01/12:06
 * @description:Callable接口创建线程
 * 1.创建Callable接口实现类
 * 2.实现call方法,线程要做的操作,可以有返回值
 * 3.创建Callable接口实现类的对象
 * 4.将该对象作为参数传入FutureTask构造器创建对象
 * 5.Thread构造器传入FT对象,调用start方法
 * 6.可以使用FT对象.get得到call()返回值
 *
 *
 * 理解Callable接口方式创建多线程比Runnable接口创建更强大?
 * 1.前者可以有返回值
 * 2.前者可以抛出异常,可以catch
 * 3.前者支持泛型
 * 4.
 */
class NumThread implements Callable {
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                sum += i;
            }
        }
        return sum;
    }
}

public class CreationIII {
    public static void main(String[] args) {
        NumThread n = new NumThread();

        FutureTask f = new FutureTask(n);//又封装一层

        new Thread(f).start();
        //因为FurueTask实现了Runnable接口

        try {
            Object sum = f.get();//返回值就是FutureTask
            //构造器参数Callable实现类重写的call方法
            System.out.println(sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

9.创建多线程方式四:线程池(JDK5)
  • 提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中。可以频繁避免创建销毁、实现重复利用。
  • 好处:提高响应速度(减少了创建新线程的时间)、降低资源消耗(重复利用线程池的线程)、便于线程管理
  • corePoolSize:核心池的大小;maximumPoolSize:最大线程数;keepAliveTime:线程没任务时最多保持多长时间后会终止。


    image.png
package CreationI;

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

/**
 * @author : liulinzhi
 * @date: 2020/07/01/14:25
 * @description:线程池创建线程
 *
 */
class Number implements Runnable{
    @Override
    public void run(){
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                sum += i;
            }
        }
    }
}

public class CreationIV {
    public static void main(String[] args) {
        //1.提供指定线程池数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //设置线程池的属性,在实现类ThreadPoolExtcutor里
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//        service1.setCorePoolSize();
//        service1.setKeepAliveTime();
//        service1.setMaximumPoolSize();

        //2.执行指定线程的操作,需要提供实现Runnbable或Callable
        //接口的实现类对象
        service.execute(new Number());//适合Runnable
//        service.submit();//适合Callable



        //3.关闭线程池
        service.shutdown();
    }
}

三.多线程高级juc

1.volatile关键字和内存可见性
  • 提出问题:flag不同步。JVM对两个线程创建两个独立的缓存,线程1写回主存后,main线程还是false,没有同主存中读过来——内存可见性问题:多个线程操作共享数据时,彼此不可见
    image.png
package JUC;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/14:54
 * @description:volatile关键字
 */
class Number implements Runnable {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        System.out.println("flag=" + isFlag());

    }
}

public class VolatileDemo {
    public static void main(String[] args) {
        Number n = new Number();

        new Thread(n).start();

        //这里卡在flag=true,为什么分线程flag是
        //true而主线程一直循环?
        /*
        Jvm对每个线程都分配一个独立的缓存用于提高效率;
        
         */
        while (true) {
            if (n.isFlag()) {
                System.out.println("---------");
                break;
            }
        }
    }
}

  • volatile关键字修饰共享数据:该关键字保证多个线程操作该共享数据时候,内存中数据是可见的,时刻刷新缓存数据到主存,可以近似看成直接对主存进行操作,比锁效率高
  • 相较于synchronized是一种较为轻量级的同步策略;
  • 注意:
    1)volatile不具备互斥性
    2)不能保证变量的”原子性“
2.变量的原子性和CAS算法
  • 下面图中线程一的V=A,故把B更新主存,num=1,此时如果线程2的V读的早于这一更新操作,那么V=0,而这时更新后A=1,不一致,就不做任何操作
    image.png

    image.png
package JUC;

import java.util.TreeMap;

/**
 * @author : liulinzhi
 * @date: 2020/07/01/15:18
 * @description:原子性问题
 * 1.i++的原子性问题:读-改-写三部操作
 * j = i++本质上在底层 int temp = i; i = i+1; j =temp;
 *
 * 解决方式:原子变量:java.util.concurrent.atomic包
 * 提供了大量常用的原子变量:
 * 1.有volatile特性,里面都用这个修饰,保证的内存可见性
 * 2.CAS(compare and swap)算法保证数据的原子性:
 * 他是硬件对并发操作共享数据的支持,底层的支持:
 * 包含了三个操作数:
 * 内存值V
 * 预估值A
 * 更新值B
 * 当前仅当V==A时,V=B,否则不做任何操作
 */
class Atomic implements Runnable{
    private int num = 0;

    public int getNum(){
        return num++;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()
        + "  :" + getNum());
    }
}

public class AtomicDemo {
    public static void main(String[] args) {
        Atomic a1 = new Atomic();

        for (int i = 10; i>0; i--)
            new Thread(a1).start();
    }
}

  • CAS不成功的时候,线程不会放弃更新权,会再次进行CAS,比同步锁效率高。
private AtomicInteger num = new AtomicInteger(0);

    public int getNum(){
        return num.getAndIncrement();
    }

  • volatile和synchronized:
1、Volatile轻量级的,只能修饰变量。
synchronize重量级的,还可以修饰方法
2、Volatile只保证数据的可见性,不能用来同步,
因为多线程访问Volatile变量不会阻塞
3、synchronize不仅保证可见性,而且保证原子性,因为自由获得了锁的线程才能到达临界区,
从而保证了临界区中的所有语句被执行,多个线程抢夺synchronize锁的时候,会出现阻塞。
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,490评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,581评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,830评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,957评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,974评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,754评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,464评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,357评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,847评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,995评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,137评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,819评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,482评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,023评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,149评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,409评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,086评论 2 355