Java基础——多线程

Java基础——多线程

一、线程简介

1、程序
为了完成特定任务、用某种语言编写的一组指定的集合、即指一段静态的代码
2、进程
程序的一次执行过程,或是正在运行的一个程序
3、线程
进程可进一步细化为线程,是一个程序内部的一条执行路径。

二、线程实现

1、继承Thread类

1)、如何实现

1.自定义线程类继承Thread类
2.重写run()方法,编写线程执行体
3.创建线程对象,调用start()方法启动线程

//1、自定义线程类继承Thread类
public class ExtendsThread extends Thread{
    //2、重写run()方法,编写线程执行体
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我是重写Thread类的run()方法" + i);
        }
    }

    public static void main(String[] args) {
        //3、创建线程对象,调用start()方法启动线程
        ExtendsThread extendsThread = new ExtendsThread();
        extendsThread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("我是主函数的for循环"+i);
        }
    }
}
/*
运行结果(没有复制运行结果,口头描述):两个for循环互相穿插内容
*/
2)、案例

利用多线程完成网络图片的下载

public class ExtendsThreadExer01 extends Thread{
    private String url;//下载地址
    private String name;//文件名称

    public ExtendsThreadExer01() {
    }

    public ExtendsThreadExer01(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) {
        String url1 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg9.51tietu.net%2Fpic%2F2019-091311%2Frrwzhatwryfrrwzhatwryf.jpg&refer=http%3A%2F%2Fimg9.51tietu.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668228&t=d433c5e8c068d9469833cbf5d2f4e468";
        String url2 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp02%2F1Z91921132U2R-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668315&t=2d695729676eea08db30f150fd302874";
        String url3 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.goupu.com.cn%2Fupload%2F201706%2F04%2F115016541.png&refer=http%3A%2F%2Fimg.goupu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668393&t=d35df7425c058bd7038df5e836ac1ef9";
        ExtendsThreadExer01 test1 = new ExtendsThreadExer01(url1,"01.jpg");
        ExtendsThreadExer01 test2 = new ExtendsThreadExer01(url2,"02.jpg");
        ExtendsThreadExer01 test3 = new ExtendsThreadExer01(url3,"03.jpg");

        //期望:01——02——03
        test1.start();
        test2.start();
        test3.start();
    }
}
//下载器
class WebDownLoader{
    //下载方法
    public void downLoader(String url, String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
/*
运行结果:下载的文件名为:03.jpg
		下载的文件名为:02.jpg
		下载的文件名为:01.jpg
*/

2、实现Runnable接口

1)、如何实现

1.自定义线程类,实现Runnable接口
2.实现run()方法,编写线程执行体
3.创建线程对象,通过Thread类对象代理,调用start()方法启动线程

//1、自定义线程类,实现Runnable接口
public class ImplementsRunnable implements Runnable{
    //2、实现run()方法,编写线程执行体
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("重写Runnable接口的for循环"+i);
        }
    }

    public static void main(String[] args) {
        //3、创建线程对象,通过Thread类对象代理,调用start()方法启动线程
        ImplementsRunnable implementsRunnable = new ImplementsRunnable();
        new Thread(implementsRunnable).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主函数的for循环"+i);
        }
    }
}
/*
运行结果(没有复制运行结果,口头描述):两个for循环互相穿插内容
*/
2)、小结
继承Thread类实现Runnable接口
子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力
启动线程:子类对象.start()启动线程:传入目标对象+Thread对象.start()
不建议使用:避免OOP单继承局限性推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
3)、案例——龟兔赛跑
public class RaceRunnable implements Runnable{
    private static String winner;
    @Override
    public void run() {
        for(int i = 0; i <= 15; i++) {

            //如果线程是兔子,让此线程sleep
            if(Thread.currentThread().getName().equals("兔子") && i%15==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean isOver =  isGameOver(i);
            if(isOver){
               break;
           }
            System.out.println(Thread.currentThread().getName() + "跑到了第"+ i + "公里");
        }
    }
    //判断比赛是否结束,谁是胜利者
    public boolean isGameOver(int kiloMetres){
        //判断是否有胜利者
        if(winner != null){
            return true;
        }else{
            if (kiloMetres >= 150) {
                winner = Thread.currentThread().getName();
                System.out.println("Winner is " + winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        RaceRunnable race = new RaceRunnable();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

3、实现Callable接口

1)、如何实现

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

//1、实现Callable接口,需要返回值类型
public class ImplementsCallable implements Callable<Boolean> {
    private String url;//下载地址
    private String name;//文件名称

    public ImplementsCallable() {
    }
    public ImplementsCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    
    //2、重写call方法,需要抛出异常
    @Override
    public Boolean call() throws Exception {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下载的文件名为:"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        String url1 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg9.51tietu.net%2Fpic%2F2019-091311%2Frrwzhatwryfrrwzhatwryf.jpg&refer=http%3A%2F%2Fimg9.51tietu.net&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668228&t=d433c5e8c068d9469833cbf5d2f4e468";
        String url2 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.jj20.com%2Fup%2Fallimg%2Ftp02%2F1Z91921132U2R-0-lp.jpg&refer=http%3A%2F%2Fimg.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668315&t=2d695729676eea08db30f150fd302874";
        String url3 = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimg.goupu.com.cn%2Fupload%2F201706%2F04%2F115016541.png&refer=http%3A%2F%2Fimg.goupu.com.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1659668393&t=d35df7425c058bd7038df5e836ac1ef9";
        //3、创建目标对象
        ImplementsCallable test1 = new ImplementsCallable(url1,"01.jpg");
        ImplementsCallable test2 = new ImplementsCallable(url2,"02.jpg");
        ImplementsCallable test3 = new ImplementsCallable(url3,"03.jpg");

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

        //5、提交执行结果:Future<Boolean> result1= ser.submit(t1);
        Future<Boolean> result1 = ser.submit(test1);
        Future<Boolean> result2 = ser.submit(test2);
        Future<Boolean> result3 = ser.submit(test3);

        //6、获取结果:boolean r1 = result1.get()
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();

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

        System.out.println(r1);
        System.out.println(r2);
        System.out.println(r3);
    }
}

4、Lambda表达式

1)、函数式接口

函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法、类方法,但仅能声明一个抽象方法。

2)、Lambda表达式
①、接口中的抽象方法不含参数
public class LambdaTest01 {
    //3、静态内部类
    static class AntiFans2 implements Ikun{
        @Override
        public void isIkun() {
            System.out.println("我是二号小黑子");
        }
    }

    public static void main(String[] args) {
        Ikun k = new AntiFans1();
        k.isIkun();

        k = new AntiFans2();
        k.isIkun();

        //4、局部内部类
        class AntiFans3 implements Ikun{
            @Override
            public void isIkun() {
                System.out.println("我是三号小黑子");
            }
        }
        k = new AntiFans3();
        k.isIkun();

        //5、匿名内部类
        k = new Ikun() {
            @Override
            public void isIkun() {
                System.out.println("我是四号小黑子");
            }
        };
        k.isIkun();

        //6、Lambda表达式
        k = ()->{
            System.out.println("我是五号小黑子");
        };
        k.isIkun();
    }
}
//2、实现类
class AntiFans1 implements Ikun{
    @Override
    public void isIkun() {
        System.out.println("我是一号小黑子");
    }
}
//1、定义一个函数式接口
interface Ikun{
    void isIkun();
}
/*
运行结果:我是一号小黑子
		我是二号小黑子
		我是三号小黑子
		我是四号小黑子
		我是五号小黑子
*/
②、接口中的抽象方法含有参数
public class LambdaTest02 {
    //3、静态内部类
    static class Fruit2 implements BuyFruit{
        @Override
        public void buyFruit(String name, double price) {
            int weight = 2;
            System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
            System.out.println("一共花了"+(price*weight)+"元");
            System.out.println("***************************************");
        }
    }
    public static void main(String[] args) {
        BuyFruit buy = new Fruit1();
        buy.buyFruit("西瓜",1.5);

        buy = new Fruit2();
        buy.buyFruit("苹果",2.68);

        //4、局部内部类
        class Fruit3 implements BuyFruit{
            @Override
            public void buyFruit(String name, double price) {
                int weight = 3;
                System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
                System.out.println("一共花了"+(price*weight)+"元");
                System.out.println("***************************************");
            }
        }
        buy = new Fruit3();
        buy.buyFruit("香蕉",1.98);

        //5、匿名内部类
        buy = new BuyFruit() {
            @Override
            public void buyFruit(String name, double price) {
                int weight = 4;
                System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
                System.out.println("一共花了"+(price*weight)+"元");
                System.out.println("***************************************");
            }
        };
        buy.buyFruit("榴莲",19.98);

        //6、Lambda表达式
        buy = (name,price)->{
            int weight = 5;
            System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
            System.out.println("一共花了"+(price*weight)+"元");
            System.out.println("***************************************");
        };
        buy.buyFruit("樱桃",25.68);

    }
}
//2、实现类
class Fruit1 implements BuyFruit{
    @Override
    public void buyFruit(String name, double price) {
        int weight = 1;
        System.out.println("买了"+weight+"斤"+name+",每一斤"+price+"元");
        System.out.println("一共花了"+(price*weight)+"元");
        System.out.println("***************************************");
    }
}
//1、实现一个函数式接口
interface BuyFruit{
    void buyFruit(String name,double price);
}
/*
运行结果:买了1斤西瓜,每一斤1.5元
		一共花了1.5元
		***************************************
		买了2斤苹果,每一斤2.68元
		一共花了5.36元
		***************************************
		买了3斤香蕉,每一斤1.98元
		一共花了5.9399999999999995元
		***************************************
		买了4斤榴莲,每一斤19.98元
		一共花了79.92元
		***************************************
		买了5斤樱桃,每一斤25.68元
		一共花了128.4元
		***************************************
*/
③、小结

1.前提是接口为函数式接口
2.Lambda表达式只能有一行代码的情况下才能简化成为一行,如果有多行,那么就用代码块包裹。
3.多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号。

三、线程状态

线程共五大状态,即创建状态、就绪状态、运行状态、阻塞状态、死亡状态。

1、停止线程

1.建议线程正常停止——>利用次数,不建议死循环
2.建议使用标志位——>设置一个标志位
3.不要使用stop或者destroy等过时或者JDK不建议使用的方法

public class StopTest implements Runnable{
    //设置一个标志位
    private boolean isFlag = true;
    @Override
    public void run() {
        int i = 0;
        while (isFlag){
            System.out.println(Thread.currentThread().getName()+"正在运行!"+i++);
        }
    }
    //设置一个停止函数
    public void stop(){
        this.isFlag = false;
    }
    public static void main(String[] args) {
        StopTest test = new StopTest();
        new Thread(test).start();
        for (int i = 0; i < 500; i++) {
            System.out.println("线程正在运行"+i);
            if(i==300){
                //调用stop方法切换标志位,让线程停止
                test.stop();
                System.out.println("线程停止了");
            }
        }
    }
}

2、线程休眠

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

1)、模拟倒计时
public class SleepTest {
    public static void main(String[] args) {
        tenDown();
    }
    //模拟倒计时
    public static void tenDown(){
        int down = 10;
        while(true){
            try {
                Thread.sleep(1000);
                System.out.println(down--);
                if(down<=0){
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
/*
运行结果:10
		9
		8
		7
		6
		5
		4
		3
		2
		1
*/
2)、模拟网络延时
public class SleepTest02 {
    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();
            }
        }
    }
}
/*
运行结果:09:30:27
		09:30:28
		09:30:29
		09:30:30
		09:30:31
		09:30:32
*/

3、线程礼让

1.方法:线程礼让
void yield()
2.礼让不一定成功!看cpu心情

public class YieldTest {
    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()+"结束线程");
    }
}
/*
运行结果一:	A开始线程
			B开始线程
			B结束线程
			A结束线程
运行结果二:	A开始线程
			A结束线程
			B开始线程
			B结束线程:
*/

4、线程插入

线程插入
void join()

public class JoinTest implements Runnable{
    public static void main(String[] args) {
        JoinTest joinTest = new JoinTest();
        Thread thread = new Thread(joinTest);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i == 300){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("main线程"+i);
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 700; i++) {
            System.out.println("我是尊贵的Vip用户,请让我插个队"+i);
        }
    }
}

5、线程状态观测

1.NEW:尚未启动的线程
2.RUNNABLE:在JAVA虚拟机中执行的线程
3.BLOCKED:被阻塞等待监视器锁定的线程
4.WAITING:正在等待另一个线程执行特定动作的线程
5.TIMED_ WAITING:正在等在另一个线程
6.TERNINATED:已退出的线程

public class StateTest {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           System.out.println("****************************");
        });
        //观察NEW状态
        Thread.State state = thread.getState();
        System.out.println(state);
        //观察启动RUNNABLE状态
        thread.start();
        state = thread.getState();
        System.out.println(state);
        while (state != Thread.State.TERMINATED){
            try {
                Thread.sleep(500);
                //更新线程状态
                System.out.println(state);
                //输出线程状态
                state = thread.getState();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //观察已退出TERNINATED的线程
        state = thread.getState();
        System.out.println(state);
    }
}
/*
运行结果:NEW
		RUNNABLE
		RUNNABLE
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		TIMED_WAITING
		****************************
		TIMED_WAITING
		TERMINATED
*/

6、线程优先级

1.线程的优先级范围从1-10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRORRITY = 5;
2.优先级的设定建议在start()调用前
3.方法
获取优先级 :gePriority()
设置优先级 :setPriority()
4.总结:优先级低只是意味着获得调度的概率低,并不是不会被调用。

public class PriorityTest {
    public static void main(String[] args) {
        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);

        System.out.println(Thread.currentThread().getName() + "的优先级——>" + Thread.currentThread().getPriority());

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

        t3.setPriority(5);
        t3.start();

        t4.setPriority(8);
        t4.start();

        t5.setPriority(3);
        t5.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "的优先级——>" + Thread.currentThread().getPriority());
    }
}
/*
运行结果一:	main的优先级——>5
			Thread-0的优先级——>1
			Thread-1的优先级——>10
			Thread-4的优先级——>3
			Thread-3的优先级——>8
			Thread-2的优先级——>5
运行结果二:	main的优先级——>5
			Thread-1的优先级——>10
			Thread-3的优先级——>8
			Thread-0的优先级——>1
			Thread-2的优先级——>5
			Thread-4的优先级——>3
*/

7、守护(daemon)线程

1.线程分为用户线程守护线程
2.虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
3.方法
设置是否为守护线程,默认是false
void setDaemon(boolean on)

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

        Thread t1 = new Thread(god);
        t1.setDaemon(true);
        t1.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 < 100; i++) {
            System.out.println("这是我活着的第"+i+"年");
        }
        System.out.println("******************************************");
        System.out.println("生老病死,与世长辞!");
    }
}

四、线程同步

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

1、同步代码块

1.如何定义
synchronized(同步监视器){
//需要被同步的代码
}
2.操作共享数据的代码,即为需要被同步的代码。
3.共享数据:多个线程共同操作的变量
4.同步监视器,俗称锁。任何一个类的对象,都可以充当锁。要求:多个线程必须要共用同一把锁。
5.实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器
6.在继承Thread类创建多线程的方式中,考虑使用当前类充当同步监视器。慎用this充当同步监视器。

public class BankSyn {
    public static void main(String[] args) {
        //账户
        Account account = new Account("结婚基金",500);

        Drawing tony = new Drawing(account, 100, "托尼");
        Drawing tonyGirlFriend = new Drawing(account, 500, "托尼的女朋友");

        tony.start();
        tonyGirlFriend.start();
    }
}
class Account{
    //卡主姓名
    private String name;
    //余额
    private int monny;

    public Account(String name, int monny) {
        this.name = name;
        this.monny = monny;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getMonny() {
        return monny;
    }

    public void setMonny(int monny) {
        this.monny = monny;
    }
}

//银行:模拟取款
class Drawing extends Thread{
    //账户
    private Account account = null;
    //取了多少钱
    private int drawingMoney = 0;
    //现在手里多少钱
    private int nowMoney = 0;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    //取钱
    @Override
    public void run() {
        synchronized (account){
            if(account.getMonny() - drawingMoney < 0){
                System.out.println(Thread.currentThread().getName()+"账户余额不足,请确认账户余额!");
                return;
            }

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

            //卡内余额 = 余额 - 你取的钱
            account.setMonny(account.getMonny() - drawingMoney);
            //你手里的钱
            nowMoney = nowMoney + drawingMoney;

            System.out.println(account.getName() + "账户余额为:" + account.getMonny());
            //Thread.currentThread().getName() = this.getName()
            System.out.println(this.getName()+"手里的钱为:" + nowMoney);
        }
    }
}
/*
运行结果:结婚基金账户余额为:400
		托尼手里的钱为:100
		托尼的女朋友账户余额不足,请确认账户余额!
*/

2、同步方法

1.同步方法仍然设计到同步监视器,只是不需要我们显式的声明。
2.非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类本身

public class BuyTicketSyn {
    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 ticketNum = 10;
    private boolean isFlag = true;

    @Override
    public void run() {
        while(isFlag){
            buy();
        }
    }

    private synchronized void buy(){
        if(ticketNum <= 0){
            isFlag = false;
            return;
        }
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "抢到了第" +ticketNum-- + "张票 ");
    }
}
/*
运行结果:孤身一人抢到了第10张票 
		孤身一人抢到了第9张票 
		孤身一人抢到了第8张票 
		孤身一人抢到了第7张票 
		孤身一人抢到了第6张票 
		孤身一人抢到了第5张票 
		黄牛党抢到了第4张票 
		黄牛党抢到了第3张票 
		旅游团抢到了第2张票 
		旅游团抢到了第1张票 
*/

3、JUC安全类型的集合

public class JUCTest {
    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(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
/*
运行结果:10000
*/

4、死锁问题

死锁:多个线程互相抱着对方需要的资源,然后形成僵持.

1)、死锁
public class DeadLock {
    public static void main(String[] args) {
        MakeUp makeUp1 = new MakeUp("张三", 0);
        MakeUp makeUp2 = new MakeUp("李四", 1);
        new Thread(makeUp1).start();
        new Thread(makeUp2).start();
    }
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}

class MakeUp implements Runnable{
    //用static来保证只有一份资源
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    String name;
    int choice;

    public MakeUp(String name, int choice) {
        this.name = name;
        this.choice = choice;
    }

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

    private void makeUp() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){
                System.out.println(Thread.currentThread().getName() + "拿到了口红");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(Thread.currentThread().getName() + "拿到了镜子");
                }
            }
        }else{
            synchronized (mirror){
                System.out.println(Thread.currentThread().getName() + "拿到了镜子");
                Thread.sleep(1000);
                synchronized (lipstick){
                    System.out.println(Thread.currentThread().getName() + "拿到了口红");
                }
            }
        }
    }
}
/*
运行结果:Thread-0拿到了口红
		Thread-1拿到了镜子
		(发生死锁,程序一直卡住)
*/
2)、解决死锁

把makeUp()方法进行了改进

    private void makeUp() throws InterruptedException {
        if(choice == 0){
            synchronized (lipstick){
                System.out.println(Thread.currentThread().getName() + "拿到了口红");
                Thread.sleep(1000);
            }
            synchronized (mirror){
                System.out.println(Thread.currentThread().getName() + "拿到了镜子");
            }
        }else{
            synchronized (mirror){
                System.out.println(Thread.currentThread().getName() + "拿到了镜子");
                Thread.sleep(1000);
            }
            synchronized (lipstick){
                System.out.println(Thread.currentThread().getName() + "拿到了口红");
            }
        }
    }
    /*
    运行结果:Thread-1拿到了镜子
			Thread-0拿到了口红
			Thread-1拿到了口红
			Thread-0拿到了镜子
    */

5、Lock(锁)

1.创建ReentrantLock对象
final ReentrantLock lock = new ReentrantLock()
2.加锁
void lock()
3.解锁
void unlock()

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

        new Thread(ticket,"李四").start();
        new Thread(ticket,"张三").start();
        new Thread(ticket,"王五").start();
    }
}
class Ticket implements Runnable{
    private static int ticketNum = 10;

    //定义Lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            //加锁
            lock.lock();
            while (true){
                if(ticketNum > 0){
                    System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNum-- + "张票");
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }finally {
            //解锁
            lock.unlock();
        }
    }
}

6、Lock(锁)与synchronized的对比

1.Lock是显式锁(手动开启和关闭),synchronized是隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块锁和方法锁
3.使用Lock锁,性能更好,并且具有更好的扩展性(提供更多的子类)
4.优先使用顺序:
Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)

五、线程通信问题

1、常用方法

1、线程一直等待,直到其他线程通知,与sleep不同,会释放锁。
void wait()
2、指定等待的毫秒数
void wait(long timeout)
3、唤醒一个处于等待状态的线程
void notify()
4、唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度
void notifyAll()

2、生产者消费者模型

1)、管程法

生产者消费者模型——>利用缓冲区解决:管程法

public class PCTest {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();

        new Thread(new Productors(container)).start();
        new Thread(new Consumers(container)).start();
    }
}
//生产者
class Productors implements Runnable{
    SynContainer container;

    public Productors(SynContainer container) {
        this.container = container;
    }
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Products(i));
            System.out.println("生产了"+i+"个产品");
        }
    }
}
//消费者
class Consumers implements Runnable{
    SynContainer container;

    public Consumers(SynContainer container) {
        this.container = container;
    }
    //消费
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第"+ container.pop().getId() +"个产品");
        }
    }
}
//产品
class Products{
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public Products(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContainer{
    //容器
    Products[] products = new Products[10];
    int count = 0;

    //生产者生产
    public synchronized void push(Products product){
        //如果容器满了,需要等待消费者消费
        if(count == products.length){
            //通知消费者消费,生产等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果没有满,我们就需要放产品
        products[count] = product;
        count++;
        //通知消费者消费
        this.notifyAll();
        this.notify();
    }

    //消费者消费
    public synchronized Products pop(){
        //判断能否消费
        if(count == 0){
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        count--;
        Products product = products[count];
        //消费完了,通知生产者生产
        this.notifyAll();
        return product;
    }
}
2)、信号灯法

信号灯法,标志位解决

public class PCTest02 {
    public static void main(String[] args) {
        TV tv = new TV();

        new Thread(new Player(tv)).start();
        new Thread(new Watcher(tv)).start();
    }
}
//生产者-->演员
class Player implements Runnable{
    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.show("快乐大本营");
            }else{
                this.tv.show("王牌对王牌");
            }
        }
    }
}
//消费者-->观众
class Watcher implements Runnable{
    TV tv;

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

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}
//产品-->观众
class TV {
    //节目
    String program = null;
    //标志位
    boolean isFlag = true;

    //表演:T
    public synchronized void show(String program){
        if(!isFlag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了"+program + "这档节目");
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.program = program;
        this.isFlag = !this.isFlag;
    }

    //观看:F
    public synchronized void watch(){
        if(isFlag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看了"+ this.program);
        //通知演员表演
        this.notifyAll();//通知唤醒
        this.isFlag = !this.isFlag;
    }
}

3、线程池

1.线程池相关API:ExecutorService和Executors
2.ExecutorService:真正的线程池接口。常用子类ThreadPoolExecutor。
1)执行任务/命令,没有返回值,一般执行Runnable
void execute(Runnable command)
2)关闭线程池
void shutdown()
3)设置线程池的大小
ExecutorService newFixedThreadPool(int nThreads)
3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

public class ThreadPoolTest {
    public static void main(String[] args) {
        //1、创建服务,创建线程池
        //newFixedThreadPool,参数为线程池大小
        ExecutorService service = Executors.newFixedThreadPool(10);

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

        //3、关闭连接池
        service.shutdown();
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}
/*
运行结果:pool-1-thread-1
		pool-1-thread-4
		pool-1-thread-3
		pool-1-thread-2
		pool-1-thread-5
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值