多线程,看这一篇就够了

码上生花,ECharts 作品展示赛正式启动!>>> hot3.png

前言:学习了狂神说Java的多线程详解所总结归纳的知识点。

线程简介

多任务:边吃饭边玩手机、边开车边打电话、边上厕所边玩手机

现实中多这样同时做多件事情的例子了,看起来是多个任务再做,其实本质上,我们的大脑在同一时间只做了一件事情。

比如,有些人边吃饭边玩手机,实际上是上一秒吃了一口饭,下一秒在看手机的内容,只是这个时间交替的非常快,才会让你感觉到你是在边吃饭边玩手机,其他两个例子也是一个意思,边上厕所边玩手机其实也是相当于分工合作,多核工作。

现实生活中,很多道路原本是一条路,因为后来车太多了,道路容易堵塞,所以扩宽了道路,变成了多车道,这样就不会出现道路堵塞了,这也是多线程案例。

普通方法调用和多线程

多线程,看这一篇就够了_第1张图片

程序->进程->线程

在操作系统中运行的程序就是进程,比如你的电脑可以同时打开QQ、播放器、游戏、IDE等等

一个进程可以有多个线程,比如视频中可以同时听声音、看图像、看弹幕、看字幕,等等

  • 说起进程就不得不说下程序,程序是指令和数据的有序集合,本身没有任何运行的涵义,是一个静态的概念
  • 而进程是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。

注意:很多多线程是模拟出来的,真正的多线程是只有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

核心概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,GC线程
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的
  • 对同一个资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程的创建

三种创建方式

多线程,看这一篇就够了_第2张图片

  • 继承Thread类

多线程,看这一篇就够了_第3张图片

package com.janeroad.demo01;

/**
创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
 */
public class TestThread1 extends Thread{
    [@Override](https://my.oschina.net/u/1162528)
    public void run() {
    //run方法线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码---"+i);
        }
    }

    public static void main(String[] args) {
        //main线程 主线程

        //创建一个线程对象
        TestThread1 testThread1=new TestThread1();

        //调用start()方法开启线程
        testThread1.start();

        for (int i = 0; i < 2000; i++) {
            System.out.println("我在学习多线程--"+i);
        }
    }

}

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

案例:下载图片

需要添加一个commons-io-2.6.jar到项目目录里

package com.janeroad.demo01;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
* Created on 2020/5/31.
* 练习Thread,实现多线程同步下载图片
* [@author](https://my.oschina.net/arthor) LJN
*/
public class TestThread2 extends Thread{

//网络图片地址
private String url;
//保存的文件名
private String name;

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

//重写run()方法,下载图片的执行体
[@Override](https://my.oschina.net/u/1162528)
public void run() {
WebDownloader webDownloader=new WebDownloader();
webDownloader.downloader(url,name);
    System.out.println("下载了文件名为:"+name);
}

public static void main(String[] args) {
    TestThread2 testThread1=new TestThread2("https://images.gitbook.cn/b3a90d00-cdeb-11e9-932d-6123ff488b55","1.jpg");
    TestThread2 testThread2=new TestThread2("https://images.gitbook.cn/cb2aa8d0-cdeb-11e9-b572-5118f14310d8","2.jpg");
    TestThread2 testThread3=new TestThread2("https://images.gitbook.cn/e34947f0-cdeb-11e9-932d-6123ff488b55","3.jpg");

    testThread1.start();
    testThread2.start();
    testThread3.start();

}
}



//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
    try {
        FileUtils.copyURLToFile(new URL(url),new File(name));
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("IO异常,downloader方法出现问题");
    }
}

}
  • 实现Runnable接口

多线程,看这一篇就够了_第4张图片

package com.janeroad.demo01;

/**
* Created on 2020/5/31.
* 创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
* [@author](https://my.oschina.net/arthor) LJN
*/
public class TestThread3 implements Runnable{
[@Override](https://my.oschina.net/u/1162528)
public void run() {
    //run方法线程体
    for (int i = 0; i < 500; i++) {
        System.out.println("我在看代码---"+i);
    }
}

public static void main(String[] args) {
   //创建runnable接口的实现类对象
    TestThread3 testThread3=new TestThread3();
    //创建线程对象,通过线程对象来开启我们的线程,代理
//        Thread thread=new Thread(testThread3);
//        thread.start();


    new Thread(testThread3).start();


    for (int i = 0; i < 5000; i++) {
        System.out.println("我在学习多线程--"+i);
    }
}
}

多个线程同时操作同一个对象

package com.janeroad.demo01;

/**
* Created on 2020/5/31.
* 多个线程同时操作同一个对象
* 买火车票
* @author LJN
*/
public class TestThread4 implements Runnable{

//票数
private int ticketNums=10;

@Override
public void run() {
    while(true){
        if(ticketNums<=0){
            break;
        }


        //模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票");
    }
}


public static void main(String[] args) {
    TestThread4 ticket=new TestThread4();
    new Thread(ticket,"小明").start();
    new Thread(ticket,"老师").start();
    new Thread(ticket,"黄牛").start();
}
}
/**
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
* @Author: JaneRoad
* @Date: 2020/5/31 11:38
*/

龟兔赛跑案例

package com.janeroad.demo01;

/**
 * Created on 2020/5/31.
 *  模拟龟兔赛跑
 * @author LJN
 */
public class Race implements Runnable{
    //胜利者
    private  static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {

            //模拟兔子休息
            if(Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }


            //判断比赛是否结束
            boolean flag=gameOver(i);
            //如果比赛结束 就结束程序
            if(flag){
                break;
            }

            System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");

        }

    }
    //判断是否完成比赛
    private boolean gameOver(int steps){
        //判断是否有胜利者
        if(winner!=null)
        {
            return true;
        }
        {
            if(steps>=100){
                winner=Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race=new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}
  • 实现Callable接口

1.实现Callable接口

2.重写call方法,需要抛出异常

3.创建目标对象

4.创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);

5.提交执行:Future result1=ser.submit(t1);

6.获取结果:boolean r1=result1.get()

7.关闭服务:ser.shutdownNow();

package com.janeroad.demo02;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
* Created on 2020/5/31.
* callable的好处:1、可以定义返回值2、可以抛出异常
* @author LJN
*/
public class TestCallable implements Callable {

//网络图片地址
private String url;
//保存的文件名
private String name;

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

//重写call()方法,下载图片的执行体
@Override
public Boolean call()  {
    WebDownloader webDownloader=new WebDownloader();
    webDownloader.downloader(url,name);
    System.out.println("下载了文件名为:"+name);
    return true;
}


public static void main(String[] args) throws ExecutionException, InterruptedException {
    TestCallable t1=new TestCallable("https://images.gitbook.cn/b3a90d00-cdeb-11e9-932d-6123ff488b55","1.jpg");
    TestCallable t2=new TestCallable("https://images.gitbook.cn/cb2aa8d0-cdeb-11e9-b572-5118f14310d8","2.jpg");
    TestCallable t3=new TestCallable("https://images.gitbook.cn/e34947f0-cdeb-11e9-932d-6123ff488b55","3.jpg");
    //创建执行任务
    ExecutorService service= Executors.newFixedThreadPool(3);
    //提交执行
    Future r1=service.submit(t1);
    Future r2=service.submit(t2);
    Future r3=service.submit(t3);
    //获取结果
    boolean rs1=r1.get();
    boolean rs2=r2.get();
    boolean rs3=r3.get();

    System.out.println(rs1);
    System.out.println(rs2);
    System.out.println(rs3);
    //关闭服务
    service.shutdownNow();
}
}

//下载器
class WebDownloader{
//下载方法
public void downloader(String url,String name){
    try {
        FileUtils.copyURLToFile(new URL(url),new File(name));
    } catch (IOException e) {
        e.printStackTrace();
        System.out.println("IO异常,downloader方法出现问题");
    }
}

}

小结

继承Thread类

  • 子类继承THread类具备多线程能力
  • 启动线程:子类对象.start()
  • 不建议使用:避免单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:传入目标对象+Thread对象.start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

静态代理

演示:实现静态代理对比Thread

你:真实角色

婚庆公司:代理你,帮你处理结婚的事

结婚:实现结婚接口即可

package com.janeroad.demo03;

/**
 * Created on 2020/5/31.
 * 静态代理模式总结:
 * 真实对象和代理对象都要实现同一接口
 * 代理对象要代理真实角色
 *
 * 好处:代理对象可以做很多真实对象做不了的事情
 * 真实对象专注做自己的事情
 * @author LJN
 */
public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany=new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}

//真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("要结婚了");
    }
}

//代理角色,帮助你结婚
class  WeddingCompany implements Marry{
    //代理谁->真实目标角色
    private Marry target;

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

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();//调用真实对象里的结婚方法
        after();
    }

    private void before() {
        System.out.println("结婚前布置现场");
    }
    private void after() {
        System.out.println("结婚后,收尾款");
    }
}

Lambda表达式

为什么要使用lambda表达式?

  • 避免匿名内部类定义过多
  • 可以让你的代码更简洁
  • 去掉一堆无意义代码,只留下核心的逻辑

函数式接口的定义

  • 任何接口如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{
	public abstract void run();
}
  • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象

代码推导:

package com.janeroad.lambda;

/**
 * Created on 2020/5/31.
 * lambda表达式与内部类
 * @author LJN
 */
public class TestLambda1 {
    public static void main(String[] args) {
        //方法一
        ILike iLike=new Like();
        iLike.TestLike();
        //方法二
        ILike iLike2=new Like2();
        iLike2.TestLike();

        //方法三:局部内部类
        class Like3 implements ILike{
            @Override
            public void TestLike() {
                System.out.println("I LIKE U 3");
            }
        }
        //方法三
        ILike iLike3=new Like3();
        iLike3.TestLike();

        //方法四:匿名内部类,没有类的名称,必须借助接口或者父类
        ILike iLike4=new ILike(){
            @Override
            public void TestLike() {
                System.out.println("I LIKE U 4");
            }
        };
        iLike4.TestLike();

        //方法五:用lambda简化
        ILike iLike5=()->{
            System.out.println("I LIKE U 5");
        };
        iLike5.TestLike();


    }


    //方法二:静态内部类
    static class Like2 implements ILike{
        @Override
        public void TestLike() {
            System.out.println("I LIKE U 2");
        }
    }


}



//定义函数接口
 interface ILike{
    void TestLike();
}
//方法一:外部实现接口
class Like implements ILike{
    @Override
    public void TestLike() {
        System.out.println("I LIKE U");
    }
}

线程状态

多线程,看这一篇就够了_第5张图片

多线程,看这一篇就够了_第6张图片

多线程,看这一篇就够了_第7张图片

停止线程

  • 不推荐使用JDK提供的stop()、destory(),因为已废弃
  • 推荐线程自己停下来
  • 或者使用一个标志位进行终止变量 当flag=false,则终止线程运行
package com.janeroad.demo04;

/**
 * Created on 2020/5/31.
 * 测试stop
 * 1、建议线程正常停止--->利用次数,不建议死循环
 * 2、建议使用标志位--->设置一个标志位
 * 3、不要使用stop或者destory等过时或者JDK不建议使用的方法
 * @author LJN
 */
public class TestStop implements Runnable{
    //1.设置一个标志位

    private boolean flag=true;


    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("run...Thread"+i++);
        }
    }

    //2.设置一个公开的方法停止线程,转换标志位

    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 < 1000; i++) {
            System.out.println("main"+i);
            if(i==900){
                //调用stop,切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

模拟网络延时:

package com.janeroad.demo04;

/**
* Created on 2020/5/31.
* 模拟网络延时 放大问题的发生性
* @author LJN
*/
public class TestSleep implements Runnable{


//票数
private int ticketNums=10;

@Override
public void run() {
    while(true){
        if(ticketNums<=0){
            break;
        }


        //模拟延时
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


System.out.println(Thread.currentThread().getName()+"-->拿到了第"+ticketNums--+"张票");
    }
}


public static void main(String[] args) {
    TestSleep ticket=new TestSleep();
    new Thread(ticket,"小明").start();
    new Thread(ticket,"老师").start();
    new Thread(ticket,"黄牛").start();
}
}

模拟倒计时1:

package com.janeroad.demo04;

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

/**
 * Created on 2020/5/31.
 * 模拟倒计时1
 * @author LJN
 */
public class TestSleep2 {
    public static void main(String[] args) {
    try{
        tenDown();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    }




    //模拟倒计时

    public static void tenDown() throws InterruptedException {
        int num=10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num<=0){
                break;
            }
        }
    }
}

模拟倒计时2

package com.janeroad.demo04;

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

/**
 * Created on 2020/5/31.
 * 模拟倒计时2
 * @author LJN
 */
public class TestSleep2 {
    public static void main(String[] args) {
        //打印当前系统时间
        Date startTime =new Date(System.currentTimeMillis());


        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                startTime=new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


    //模拟倒计时

    public static void tenDown() throws InterruptedException {
        int num=10;
        while(true){
            Thread.sleep(1000);
            System.out.println(num--);
            if(num<=0){
                break;
            }
        }
    }
}

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度,礼让不一定成功!看CPU心情
package com.janeroad.demo04;

/**
 * Created on 2020/6/1.
 * 测试礼让线程,看CPU心情
 * @author LJN
 */
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield=new MyYield();
        new Thread(myYield,"a").start();
        new Thread(myYield,"b").start();
    }

}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始执行");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程停止执行");
    }
}

Join

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
package com.janeroad.demo04;

/**
 * Created on 2020/6/1.
 * 测试join方法 想象为插队
 * @author LJN
 */
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);
        }

    }
}

线程状态观测

多线程,看这一篇就够了_第8张图片

package com.janeroad.demo04;

/**
 * Created on 2020/6/1.
 *  观察测试线程的状态
 * @author LJN
 */
public class TestState {

    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("///");
        });



        //观察状态
        Thread.State state=thread.getState();
        System.out.println(state);
        //NEW



        //启动后
        thread.start();
        state=thread.getState();
        System.out.println(state);
        //Run

        while (state!=Thread.State.TERMINATED){
            //只要线程不终止就输出状态
        Thread.sleep(100);
        state=thread.getState();
        //更新线程状态
        System.out.println(state);
            //输出状态
        }

    }
}

线程优先级

多线程,看这一篇就够了_第9张图片

package com.janeroad.demo04;

/**
 * Created on 2020/6/1.
 * 测试线程优先级
 * @author LJN
 */
public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MyPriority mp=new MyPriority();


        Thread t0=new Thread(mp);
        Thread t1=new Thread(mp);
        Thread t2=new Thread(mp);
        Thread t3=new Thread(mp);
        Thread t4=new Thread(mp);
        Thread t5=new Thread(mp);

        //设置优先级 在启动
        t0.setPriority(6);
        t0.start();

        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

        t5.setPriority(8);
        t5.start();



    }



}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

注意:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待..
package com.janeroad.demo04;

/**
 * Created on 2020/6/1.
 *  测试守护线程
 * @author LJN
 */
public class TestDaemon {

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


        Thread thread=new Thread(god);
        //默认false 表示是用户线程,正常的线程都是用户线程
        thread.setDaemon(true);
        thread.start();//上帝守护线程启动


        //用户线程启动。。
        new Thread(you).start();


    }





}
class God implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("上帝保护着你");
        }
    }
}
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一生都开心的活着");
        }
        System.out.println("----goodbye!world!-----");
    }
}

线程同步

  • 并发:同一个对象被多个线程同时操作

多线程,看这一篇就够了_第10张图片

  • 现实生活中,我们会遇到同一个资源,多个人都想使用的问题,比如:食堂排队打饭
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用

线程同步形成条件:队列+锁

多线程,看这一篇就够了_第11张图片

不安全的例子

package com.janeroad.demo05;

/**
 * Created on 2020/6/1.
 * 不安全的买票
 * @author LJN
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket=new BuyTicket();
        new Thread(buyTicket,"苦逼的我").start();
        new Thread(buyTicket,"牛逼的你").start();
        new Thread(buyTicket,"可恶的黄牛").start();
    }
}

class BuyTicket implements Runnable{
    //票
    private int ticketNums=10;
    //外部停止方式
    boolean flag=true;


    @Override
    public void run() {
        //买票
            while (flag){
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }




    private void buy() throws InterruptedException {
        if(ticketNums<=0){
            //判断是否有票
            flag=false;
            return;
        }
        //模拟延时
        Thread.sleep(100);

        //买票
        System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

    }


}
package com.janeroad.demo05;

/**
 * Created on 2020/6/1.
 *  不安全的取钱
 * @author LJN
 */
public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"结婚基金");
        Drawing you=new Drawing(account,50,"你");
        Drawing girlFriend=new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.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() {
        //判断有没有钱
        if(account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }


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

        //卡内余额=余额-取的钱
        account.money=account.money-drawingMoney;
        //你手里的钱
        nowMoney=nowMoney+drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        //
        System.out.println(this.getName()+"手里的钱:"+nowMoney);

    }
}
package com.janeroad.demo05;

import java.util.ArrayList;
import java.util.List;

/**
 * Created on 2020/6/1.
 * 线程不安全的集合
 * @author LJN
 */
public class UnsafeList {
    public static void main(String[] args) {
        List list=new ArrayList();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        System.out.println(list.size());
    }

}

同步方法

多线程,看这一篇就够了_第12张图片

同步方法弊端

多线程,看这一篇就够了_第13张图片

同步块

多线程,看这一篇就够了_第14张图片

之前的买票案例方法前加synchronize修饰,可实现同步

package com.janeroad.demo05;

/**
* Created on 2020/6/1.
* 安全的买票
* @author LJN
*/
public class UnsafeBuyTicket {
public static void main(String[] args) {
    BuyTicket buyTicket=new BuyTicket();
    new Thread(buyTicket,"苦逼的我").start();
    new Thread(buyTicket,"牛逼的你").start();
    new Thread(buyTicket,"可恶的黄牛").start();
}
}

class BuyTicket implements Runnable{
//票
private int ticketNums=10;
//外部停止方式
boolean flag=true;


@Override
public void run() {
    //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}

//synchronize 同步方法,锁的是this
private synchronize void buy() throws InterruptedException {
    if(ticketNums<=0){
        //判断是否有票
        flag=false;
        return;
    }
    //模拟延时
    Thread.sleep(100);

    //买票
    System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);

}


}

之前的取钱案例,加上同步代码块,可实现同步

package com.janeroad.demo05;

/**
 * Created on 2020/6/1.
 *  不安全的取钱
 * @author LJN
 */
public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"结婚基金");
        Drawing you=new Drawing(account,50,"你");
        Drawing girlFriend=new Drawing(account,100,"girlFriend");

        you.start();
        girlFriend.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;

    }

    //取钱
	//synchronize 默认锁的是this
    @Override
    public void run() {
    	//同步代码块
        //锁的对象就是变化的量
    	synchronize(account){
    	    //判断有没有钱
        if(account.money-drawingMoney<0){
            System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
            return;
        }


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

        //卡内余额=余额-取的钱
        account.money=account.money-drawingMoney;
        //你手里的钱
        nowMoney=nowMoney+drawingMoney;
        System.out.println(account.name+"余额为:"+account.money);
        //
        System.out.println(this.getName()+"手里的钱:"+nowMoney);

    	}
    
    }
}

不安全集合案例改进

package com.janeroad.demo05;

import java.util.ArrayList;
import java.util.List;

/**
 * Created on 2020/6/1.
 * 线程不安全的集合
 * @author LJN
 */
public class UnsafeList {
    public static void main(String[] args) {
        List list=new ArrayList();
        for (int i=0;i<10000;i++){
            new Thread(()->{
                //加入同步代码块
              synchronize(list){
              	  list.add(Thread.currentThread().getName());
              }
            }).start();
        }
        System.out.println(list.size());
    }

}

JUC

package com.janeroad.demo06;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Created on 2020/6/1.
 *  测试JUC安全类型集合
 * @author LJN
 */
public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList list =new CopyOnWriteArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

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

        System.out.println(list.size());
    }
}

死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题
package com.janeroad.demo07;

/**
 * Created on 2020/6/1.
 * 死锁:多个线程互相抱着对方需要的资源,然后形成僵持
 * @author LJN
 */
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"灰姑凉");
        Makeup g2=new Makeup(1,"白雪公主");

        g1.start();
        g2.start();
    }
}


//口红
class Lipstick{

}

//镜子
class Mirror{

}


class Makeup extends Thread{


    //需要的资源只有一份,用static来保证只有一份
    static Lipstick lipStick=new Lipstick();
    static Mirror mirror=new Mirror();

    int choice;//选择
    String girlName;//使用化妆品的人

    Makeup(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }

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

    //化妆,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipStick){//获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);

                synchronized (mirror){//一秒钟后想获得镜子
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }
        }else{
            synchronized (mirror){//获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);

                synchronized (lipStick){//一秒钟后想获得镜子
                    System.out.println(this.girlName+"获得镜子的锁");
                }
            }
        }
    }

}

执行结果:程序卡死

改进:

private void makeup() throws InterruptedException {
    if(choice==0){
        synchronized (lipStick){//获得口红的锁
            System.out.println(this.girlName+"获得口红的锁");
            Thread.sleep(1000);
        }
        synchronized (mirror){//一秒钟后想获得镜子
            System.out.println(this.girlName+"获得镜子的锁");
        }
    }else{
        synchronized (mirror){//获得口红的锁
            System.out.println(this.girlName+"获得口红的锁");
            Thread.sleep(1000);
        }
        synchronized (lipStick){//一秒钟后想获得镜子
                System.out.println(this.girlName+"获得镜子的锁");
        }
    }
}

死锁避免方法

多线程,看这一篇就够了_第15张图片

Lock锁

多线程,看这一篇就够了_第16张图片

多线程,看这一篇就够了_第17张图片

package com.janeroad.demo07;

import java.util.concurrent.locks.ReentrantLock;

/**
* Created on 2020/6/4.
*
* @author LJN
*/
public class TestLock {

public static void main(String[] args) {
    TestLock2 testLock2=new TestLock2();
    new Thread(testLock2).start();
    new Thread(testLock2).start();
    new Thread(testLock2).start();

}

}

class TestLock2 implements Runnable{

//票
private int ticketNums=10;

//定义lock锁
private final ReentrantLock lock=new ReentrantLock();

@Override
public void run() {
    //买票
    while (true){

        try {
            lock.lock();//加锁
            if(ticketNums>0){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("还剩"+ticketNums--);
            }else{
                break;
            }
        }finally {
            //解锁
            lock.unlock();
        }

    }
}

}

synchronized与Lock的对比

多线程,看这一篇就够了_第18张图片

使用线程池

多线程,看这一篇就够了_第19张图片

多线程,看这一篇就够了_第20张图片

package com.janeroad.demo08;

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

/**
 * Created on 2020/6/4.
 * 测试线程池
 * @author LJN
 */
public class TestPool {

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

        //执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //2.关闭连接
        service.shutdown();
    }

}



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

    }
}

你可能感兴趣的:(多线程,看这一篇就够了)