多线程详解

1. 多线程简介

(1).多任务

(2).多线程

(3).进程

一个进程可以有多个线程

(4).Process与Thread

程序:指令和数据的有序集合,静态的概念

进程:系统分配的单位

线程:cpu调度和执行的单位

注:许多多线程是模拟来的,多个cpu多核心才是真正的多线程

  1. 线程是独立的执行路径
  2. 程序运行没创建线程,后台也会有多个线程
  3. main()主线程,为系统的入口,执行整个函数
  4. 调度器安排调度
  5. 对同一资源进行操作,避免资源强夺,需要并发控制
  6. 线程会带来额外的开销
  7. 每个线程都在自己的工作内存内存交互,内存控制不当会造成数据不一致。

2.线程创建(Thread,Runnable,Callable)

Thread:继承Thread类

Runnable:实现Runnable接口

Callable:实现Callable接口

(1).Thread类

  • 自定义线程继承类Tread
  • 重写run()方法
  • 创建线程对象,调用start()方法启动线程
package com.thj.demo01;

// 创建线程方式一:继承Thread类,重写run()方法,调用start()方法开启线程
// 线程开启不一定立即执行,看cpu的调度
public class TestThread1 extends Thread{
    @Override
    public void run() {
        // run()方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码-----"+i);
        }

    }

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

        // 创建一个线程对象。
        TestThread1 testThread1 = new TestThread1();
        // 调用start方法开启线程
        testThread1.start(); // 同时交替执行


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

例子:多线程下载图片

package com.thj.demo01;

import org.apache.commons.io.FileUtils;

import javax.xml.namespace.QName;
import java.io.File;
import java.io.IOException;
import java.net.URL;

// 练习Thread,实现多线程同步下载图片
public class TestThread2 extends Thread{

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

    public TestThread2(String url,String name){
        this.url = url;
        this.name = name;
    }
    // 下载图片的执行体
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了文件名为"+name);
    }

    public static void main(String[] args) {
        TestThread2 t1 = new TestThread2("https://www.ivsky.com/tupian/xiniu_v69780/pic_1136868.html","1.jpg");
        TestThread2 t2 = new TestThread2("https://www.ivsky.com/tupian/xiniu_v69780/pic_1136870.html","2.jpg");
        TestThread2 t3 = new TestThread2("https://www.ivsky.com/tupian/xiniu_v69780/pic_1136869.html","3.jpg");

        t1.start();
        t2.start();
        t3.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方法出现问题");
        }

    }
}

核心:继承Tread类,重写run()方法

(2).Runnable接口(把自己作为Tread的参数)

定义类实现Runnable接口

实现run()方法,写线程执行体

创建线程对象,调用start()方法启动线程

package com.thj.demo01;

// 创建线程方式2:实现Runnable接口,重写Run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestTread3 implements Runnable {

    @Override
    public void run() {
        // run()方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在看代码-----" + i);
        }

    }

    public static void main(String[] args) {
        // 创建runnable接口的实现类对象
        TestTread3 testTread3 = new TestTread3();
        Thread thread = new Thread(testTread3);
        thread.start();

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

(3).小结

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

(4).例子

  1. 多线程同时操作一个对象
package com.thj.demo01;

// 多个线程同时操作一个对象
// 买火车票的例子



// 发现问题,多个线程操作同一个资源的情况下,线程不安全,数据紊乱。
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();

    }
}
  1. 模拟龟兔赛跑
package com.thj.demo01;

// 模拟龟兔赛跑
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();
    }
}

(5).扩充Callable接口(了解即可,短时间用不到)

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(t1);
  6. 获取结果:boolean r1 = result1.get();
  7. 关闭服务:ser.shutdownNow();
package com.thj.demo02;

// 线程实现方法三:实现Callable接口
/*
Callable的好处:
1.可以获取返回值
2.可以抛出异常
 */


import com.thj.demo01.TestThread2;
import org.apache.commons.io.FileUtils;

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

public class TestCallable implements Callable<Boolean> {

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

    public TestCallable(String url, String name){
        this.url = url;
        this.name = name;
    }
    // 下载图片的执行体
    @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://www.ivsky.com/tupian/xiniu_v69780/pic_1136868.html","1.jpg");
        TestCallable t2 = new TestCallable("https://www.ivsky.com/tupian/xiniu_v69780/pic_1136870.html","2.jpg");
        TestCallable t3 = new TestCallable("https://www.ivsky.com/tupian/xiniu_v69780/pic_1136869.html","3.jpg");

        // 创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交执行:
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.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);
        // 关闭服务:
        ser.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方法出现问题");
        }

    }
}

(6).概念补充:静态代理

// 静态代理模式总结:
// 真实对象和代理对象都要实现同一个接口
// 代理对象必须代理真实角色

// 好处:
    // 代理对象可以做很多真实对象做不了的事情
    // 真实对象专注做自己的事情

public class StaticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new Tou());
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    //
    void HappyMarry();
}

// 真实角色
class Tou 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 after() {
        System.out.println("结婚之后,收尾款");
    }

    private void before() {
        System.out.println("结婚之前,布置现场");
    }
}

Runnable接口实现多线程就是利用了静态代理

(7).概念补充:Lambda表达式

  • 函数式接口:
    • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
    • 对于函数式接口,我们可以通过lambda表达式来创建接口的对象。
  • 使得代码更加简洁,减少内部类的使用。
package com.thj.lambda;

public class TestLambda2 {

    public static void main(String[] args) {
        ILove love = null;

//        ILove love = (int a)->{System.out.println("i love you-->"+a);};
//
//        // 简化1.去掉参数类型
//        love = (a)->{System.out.println("i love you-->"+a);};
//
        // 简化2.简化括号
//        love = a->{
//            System.out.println("i love you-->"+a);
//        };
        // 简化3.去掉花括号
        love = a-> System.out.println("i love you-->"+a);
        // 总结:
        /*
        1.lambda表达式只能有一行代码的情况下,才能简化成一行,如有多行,用代码块
        2.前提是接口为函数式接口
        3.多个参数也可以去掉参数类型,要去都去,必须加上括号
         */
        love.love(520);
    }
}


interface ILove{
    void love(int a);
}

(8).线程状态

新生,就绪,阻塞,运行,死亡

(9).线程方法

  1. setPriority(int newPriority) 更改线程优先级

  2. static void sleep(long millis) 在指定的毫秒数让线程休眠

  3. void join() 等待该进程停止

  4. static void yield() 暂停当前正在执行的线程,并执行其它线程

  5. void interrupt() 中断线程,别用这个方式

    1. 不推荐使用JDK提供的stop(),destroy()方法【已废弃】
    2. 推荐线程自己停下来
    3. 推荐使用一个标志位进行线程终止。
  6. boolean isAlive() 测试线程是否处于活动状态

(10).线程停止

  1. 不推荐使用JDK提供的stop(),destroy()方法【已废弃】
  2. 推荐线程自己停下来
  3. 推荐使用一个标志位进行线程终止。
package com.thj.state;

// 测试stop
// 1.建议线程正常停止--》利用次数,不建议死循环
// 2.建议使用标志位----》设置一个标志位
// 3.不要使用stop或者destroy等过时的或者JDK不推荐的方法
public class TestStop implements Runnable{

    // 设置一个标识位
    private boolean flag = true;

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

    // 设置一个公开的方法。停止线程,转换标识位

    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();
            }
        }
    }
}

(11).线程休眠

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

(12).线程礼让

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


// 测试礼让线程
// 礼让不一定成功,看cpu心情
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()+"线程停止执行");
    }
}

好像没啥用啊。。。这方法

(13).Join

  • Jion合并线程,待此线程执行完毕后,在执行其它线程,其它线程阻塞
  • 可以想象成插队

少用啊,会造成线程阻塞啊

package com.thj.state;


// 测试Join方法,想象为插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; 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 < 1000; i++) {
            if(i == 2){
                thread.join();// 插队
            }
            System.out.println("main"+i);
        }
    }
}

(14).线程状态观测

  • Thread.State
    • new 尚未启动的线程
    • runnable 在java虚拟机中执行的线程处于此状态
    • blocked 被阻塞等待监视器锁定的线程处于此状态
    • waiting 正在等待另一个线程执行特定动作的线程处于此状态
    • timed_waiting 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
    • terminated 已退出的状态
package com.thj.state;


// 观察测试线程的状态

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("///////");
        });

        // 观察状态
        System.out.println(thread.getState());// NEW

        // 观察启动后
        thread.start();
        System.out.println(thread.getState());

        while (thread.getState() != Thread.State.TERMINATED){
            // 只要线程不中止,就一直输出状态
            Thread.sleep(100);
            System.out.println(thread.getState());
        }
    }
}

(15).线程的优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行。
  • 线程的优先级用数字表示,范围1-10
    • Thread.MIN_PRIORITY = 1;
    • Thread.MAX_PRIORITY = 10;
    • Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或者获取优先级
    • getPriority().setPriority(int xxx)

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

有可能会性能倒置

package com.thj.state;

public class TestPriority {

    public static void main(String[] args) {
        // 主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();

        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);

        // 先设置优先级,再启动
        t1.start();

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

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

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

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

        t6.setPriority(7);
        t6.start();
    }
}


class MyPriority implements Runnable{

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

(16).守护(daemon)线程

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


// 测试守护线程
// 上帝守护你
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();


        Thread thread = new Thread(god);
        thread.setDaemon(true);     // 默认是false表示用户线程,正常的线程都是用户线程

        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!");
    }
}

3.线程同步

多个线程操作同一个资源

(1).并发

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

(2).线程同步(synchronized)

  • 现实中遇到并发问题,天然的解决方法是:排队
  • 程序中遇到并发问题需要用到线程同步。线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
  • 引入锁机制,当一个线程获得对象的排它锁,独占资源,其它线程必须等待,使用后释放锁即可。但是存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程被挂起
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致性能倒置,引起性能问题。

(3).队列和锁

队列加锁,解决安全性问题

(4).三大不安全问题

  1. 不安全的买票
package com.thj.syn;

// 不安全的买票
// 线程不安全,有负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"我").start();
        new Thread(station,"你们").start();
        new Thread(station,"黄牛").start();
    }
}



class BuyTicket implements Runnable{
    // 票
    private int ticketNums = 10;
    boolean flag = true;    // 外部停止方式
    @Override
    public void run() {
        // 买票
        while (flag){
            buy();
        }
    }

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

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


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

  1. 不安全的取钱
package com.thj.syn;


// 不安全的取钱
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100,"marry");

        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;
        }

        // sleep放大问题的发生性
        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);

    }
}

  1. 不安全的线程
package com.thj.syn;

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

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}


(5).同步方法

synchronized方法和synchronized块 修饰符

该方法控制对象的访问,每个对象都是一把锁,方法一旦执行,就会独占对象

缺点:把太大的方法指定为synchronized会让运行速度大大减缓

  1. 上锁对方法
package com.thj.syn;

// 不安全的买票
// 线程不安全,有负数
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();

        new Thread(station,"我").start();
        new Thread(station,"你们").start();
        new Thread(station,"黄牛").start();
    }
}



class BuyTicket implements Runnable{
    // 票
    private int ticketNums = 10;
    boolean flag = true;    // 外部停止方式
    @Override
    public void run() {
        // 买票
        while (flag){
            buy();
        }
    }

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

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


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


  1. 上锁对需要变化的对象

同步块:

synchronized(Obj){

}

Obj称为同步监视器,默认是this或者class,推荐用需要改变的变量作为监视器

package com.thj.syn;


// 不安全的取钱
public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(1000,"marry");

        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;
    }

    // 取钱
    // synchronized 默认锁的是this。
    @Override
    public void run() {
        // 锁定对象是变化的量,需要增删改的操作
        synchronized (account){
            // 判断有没有钱
            if(account.money-drawingMoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }

            // sleep放大问题的发生性
            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.thj.syn;

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

public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> {
                    synchronized (list) {
                        list.add(Thread.currentThread().getName());
                    }
                }).start();
            }
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(list.size());
    }
}


(6).死锁

  • 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。
  • 产生死锁的四个必要条件
    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞是,对已获得的资源保持不放。
    • 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺。
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源问题。
package com.thj.syn;


// 死锁:多个线程互相抱着对方需要的资源,然后形成僵持
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;

    public 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(2000);

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

只要能避免这四个条件的一个或者多个,就能避免死锁问题

(7).Lock(锁)

  1. 从JDK5.0开始,可以通过显式定义同步锁对象来实现同步。同步锁使用Lock对象来充当
  2. 就像synchronized似的
  3. ReentrantLock类(可重入锁),像synchronized

如果有异常的话,把解锁写入finally块

package com.thj.gaoji;


import java.util.concurrent.locks.ReentrantLock;

// 测试Lock锁
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{

    int tiketnums = 10;

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

    @Override
    public void run() {
        while (true){

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

        }
    }
}

synchronized和Lock的区别

  • Lock是显示锁(手动开启和关闭锁,不要忘记关锁)synchronized是隐式锁,出来作用域自动释放
  • Lock锁只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应的资源) > 同步方法 (在方法体之外)
package com.thj.gaoji;


import java.util.concurrent.locks.ReentrantLock;

// 测试Lock锁
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{

    int tiketnums = 10;

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

    @Override
    public void run() {
        while (true){

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

        }
    }
}


4.线程协作

(1).生产者消费者模式(问题)

线程同步问题,生产者和消费者共享一个资源,并且生产者和消费者互为依赖

java提供了一些线程通信的方法:

  1. wait() 表示线程一直等待,直到其它线程通知,但它会释放锁
  2. wait(long timeout) 指定等待的毫秒数
  3. notify() 唤醒一个处于等待状态的线程
  4. notifyALL() 唤醒同一个对象是所有调用wait()方法的线程,优先级别高的线程优先调度。

注意:均是Object类的方法,都只能在同步方法或者代码块中使用,否则会抛出异常

关于生成者消费者问题:

  1. 缓冲区
  2. 标志位

(2).管程法

package com.thj.gaoji;

// 测试生产者消费者模型--》利用缓冲区解决

// 生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Producer(container).start();
        new Consumer(container).start();
    }
}

// 生产者
class Producer extends Thread{
    SynContainer container = new SynContainer();

    public Producer(SynContainer container){
        this.container = container;
    }
    // 生产

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Chicken(i));
            System.out.println("生产了"+i+"只鸡");
        }
    }
}

// 消费者
class Consumer extends Thread{
    SynContainer container = new SynContainer();

    public Consumer(SynContainer container){
        this.container = container;
    }

    // 消费

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了--》"+container.pop().id+"只鸡");
        }
    }
}

// 产品
class Chicken{
    int id; // 产品编号

    public Chicken(int id) {
        this.id = id;
    }
}

// 缓冲区
class SynContainer{

    // 需要一个容器大小
    Chicken[] chickens = new Chicken[10];
    int count;

    // 生产者放入产品
    public synchronized void push(Chicken chicken){
        // 如果容器满了,就需要等待消费者消费
        if(count == chickens.length){
            // 通知消费者消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 如果没有满,我们就需要丢入产品
        chickens[count] =chicken;
        count++;

        // 可以通知消费者消费了
        this.notify();
    }


    // 消费者消费产品
    public synchronized Chicken pop(){
        // 判断能否消费
        if(count == 0){
            // 等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 如果可以消费
        count--;
        Chicken chicken = chickens[count];

        // 通知生产者生成
        this.notify();
        return chicken;
    }
}

(3).信号灯法

使用标志位

package com.thj.gaoji;

// 测试生产者消费者问题2:信号灯法,标志位解决

public class TestPC2 {
    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; // 表演的节目
    boolean flag = true;

    // 表演
    public synchronized void play(String voice){

        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了"+voice);
        // 通知观众观看
        this.notifyAll(); // 通知观看
        this.voice = voice;

        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;
    }
}

(4).使用线程池

package com.thj.gaoji;

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

// 测试线程池
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());
    }
}

完结,暂时

posted on 2022-04-06 16:31  新火拭茶  阅读(58)  评论(0)    收藏  举报