Java多线程

Java多线程,第1张

Java多线程 Java多线程

参考视频:

BV1V4411p7EF

并发相关概念已经学过,可以直接参考 *** 作系统相关书籍,这里不再记录。

文章目录

Java多线程1.线程创建

1.1 创建Thread1.2 多线程网图下载1.3 实现Runnable接口1.4 实现Callable接口(次要)例子:购买火车票例子:龟兔赛跑1.5 静态代理模式Lambda表达式 2.线程 *** 作

2.0 Java线程基本内容2.1 线程停止2.2 线程休眠2.3 线程礼让2.4 线程强制执行2.5 线程优先级2.6 守护(daemon)线程 3 线程同步

三大不安全案例

1.抢票系统2.银行取钱3.线程安全 同步方法

1.抢票系统2.银行取钱3.线程安全 4.锁

4.1 死锁4.2 Lock锁(JDK) 5.线程通信

生产者、消费者问题解决方式

1.管程法2.信号灯法 6.线程池

1.线程创建

父类:Thread (java.lang.Thread)
接口:Runnable、Callable

1.1 创建Thread
package Multics;

public class TestThread extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for(int i=0;i<20;i++){
            System.out.println("吃饭");
        }
    }

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

        //创建一个线程对象
        TestThread testThread=new TestThread();

        //调用start()方法开启线程。调用run()方法不会实现并发
        testThread.start();

        for(int i=0;i<40;i++){
            System.out.println("看电视");
        }
    }
}

注:每次执行结果可能不一样

1.2 多线程网图下载

可用工具包:https://commons.apache.org/proper/commons-io/download_io.cgi

package Multics;

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

public class TestThread extends Thread{
    private String url;
    private String name;

    public TestThread(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) {
        TestThread t1=new TestThread("https://gimg2.baidu.com/image_search/src=http://www.kaotop.com/skin/sinaskin/image/nopic.gif","1.jpg");
        TestThread t2=new TestThread("https://gimg2.baidu.com/image_search/src=http://www.kaotop.com/skin/sinaskin/image/nopic.gif","2.jpg");
        TestThread t3=new TestThread("https://gimg2.baidu.com/image_search/src=http://www.kaotop.com/skin/sinaskin/image/nopic.gif","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("downloader方法出现IO异常");
        }
    }
}

1.3 实现Runnable接口
// 1.1创建Thread改写
package Multics;

public class TestThread implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for(int i=0;i<20;i++){
            System.out.println("吃饭");
        }
    }

    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        TestThread testThread1=new TestThread();

        //创建线程对象,通过线程对象来开启线程,又称代理
        Thread thread=new Thread(testThread1);
        thread.start();
        //上面两行也可以简写成  new Thread(testThread1).start();

        for(int i=0;i<40;i++){
            System.out.println("看电视");
        }
    }
}
package Multics;

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

public class TestThread implements Runnable{
    
    //......
    
    public static void main(String[] args) {
        //创建t1,t2,t3
		//......
        
        new Thread(t1).start();
        new Thread(t2).start();
        new Thread(t3).start();
        
    }

}


//下载器
//......
继承Thread类实现Runnable接口子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力启动线程:子类对象.start()启动线程:传入目标对象+Thread对象.start()不建议使用:避免OOP单继承局限性推荐使用:避免单继承的局限性,灵活方便,便于同一个对象被多个线程使用 1.4 实现Callable接口(次要)
package Multics;

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


public class TestThread implements Callable{
    private String url;
    private String name;

    public TestThread(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 Exception{
        //创建t1,t2,t3
        //......
        
        
        //创建执行服务
        ExecutorService ser= Executors.newFixedThreadPool(3); //线程池

        //提交执行
        Future r1=ser.submit(t1);
        Future r2=ser.submit(t2);
        Future r3=ser.submit(t3);

        //获取结果
        boolean rs1=r1.get();
        boolean rs2=r2.get();
        boolean rs3=r3.get();

        //关闭服务
        ser.shutdown();
    }
}


//下载器
//......

实现Callable接口的好处:

    可以定义返回值可以抛出异常
例子:购买火车票

多个线程 *** 作同一个对象

package Multics;

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

public class TestThread 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) {
        TestThread ticket=new TestThread();

        new Thread(ticket,"张三").start();
        new Thread(ticket,"李四").start();
        new Thread(ticket,"黄牛").start();
    }
}

由结果可以看出,多线程代码可能会出现数据不安全的问题。需要用并发方法解决。

例子:龟兔赛跑

package Multics;

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%30==0){
                try {
                    Thread.sleep(1);
                } 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();
    }
}

1.5 静态代理模式

真实目标角色和代理角色实现同一个接口。换句话说,真实目标角色可以只实现很小一部分的接口内容,接口的大部分内容可以放到代理角色中实现。

package Multics;

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

interface Marry{
    //一些待实现接口
    //结婚内容1
    //结婚内容2
    //结婚内容3
    //结婚内容4

    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 after(){
        System.out.println("结婚之后,收尾款");
    }
    private void before(){
        System.out.println("结婚之前,布置现场");
    }
    //还可以实现Marry中的其他接口
}
Lambda表达式

目的:避免匿名内部类定义过多。可以将几乎不常用(比如只在一个地方用过一次)的函数直接写成lambda函数。大体思想和C++的lambda表达式相同。

//三种定义方式
(params)->expression[表达式]
(params)->statement[语句]
(params)->{statements}

//例
a->System.out.println("i like lambda -->"+a);
package Multics;

public class TestLambda {
    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like=new Like();
        like.lambda();

        like=new Like2();
        like.lambda();


        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }
        like=new Like3();
        like.lambda();

        //5.匿名内部类,通过接口声明
        like=new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };

        //6.用lambda简化
        like=()->{
            System.out.println("i like lambda5");
        };
        like.lambda();
    }
}

//1.定义一个函数式接口
interface ILike{
    void lambda();
}

//2.实现类
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

注:

函数式接口说明接口内只有一个待实现的方法,加入ILike中写了两个待实现方法,那么用lambda表达式就会报错。lambda表达式中的参数可以不声明类型。 2.线程 *** 作 2.0 Java线程基本内容

常见线程方法

setPriority(int newPriority)  //更改线程的优先级
static void sleep(long millis)  //指定毫秒内停止当前线程
void join()  //等待该线程终止
static void yield()  //暂停当前正在执行的线程对象,并执行其他线程
void interrupt()  //中断线程
boolean isAlive()  //测试线程是否处于活动状态

线程状态常量 -->Thread.State.常量:

NEW:尚未启动的线程RUNNABLE: 在Java虚拟机中处于就绪态的线程BLOCKED: 线程处于阻塞态WAITING: 线程正在等待另一个线程执行特定动作TIMED_WAITING:正在等待另一个线程执行动作达到指定等待时间的线程TERMINATED:结束态 2.1 线程停止

建议线程正常停止。不建议死循环。建议使用自定义的标志位,用于判断线程是否停止不要使用stop()或destory()等JDK已不建议使用的方法

package Multics;

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("测试"+i);
            if(i==900){
                testStop.stop();
                System.out.println("线程强制停止");
            }
        }
    }
}

2.2 线程休眠

sleep存在异常InterruptedExceptionsleep事件结束后线程进入就绪态每个对象都有一个锁,sleep不会释放锁

package Multics;

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

//模拟网络延时
public class TestSleep{

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

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

2.3 线程礼让

目的:让当前正在执行的线程暂停,但不进入阻塞态。这个过程不一定成功。

package Multics;

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()+"线程停止执行");
    }
}

2.4 线程强制执行

join()

package Multics;

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){  //i==200是有一个vip线程插队
                thread.join();
            }
            System.out.println("main"+i);
        }
    }
}

2.5 线程优先级

优先级用数字表示:1~10

Thread.MIN_PRIOTITY=1;Thread.MAX_PRIOTITY=10; //最高优先级Thread.NORM_PRIOTITY=5;

getPriority()   //获取优先级 
setPriority(int x)  //在线程启动前 设置优先级
2.6 守护(daemon)线程

线程分为用户线程和守护线程JVM必须确保用户线程执行完毕 如main()函数JVM不用等待守护线程执行完毕例:后台记录 *** 作日志,监控内存,垃圾回收…

package Multics;

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 线程同步

目的:多个线程 *** 作同一个资源。如抢票系统

主要思想:队列+锁(确保安全)。
处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问次对象的线程进入这个对象的等待吃形成队列,等待前面线程是哟弄个完毕,下一个线程在使用。

锁机制:为了保证数据在方法中被访问时的正确性,在访问时加入锁机制:synchronized。当一个线程获得对象的排他锁,就可以独占资源,其他线程必须等待,使用完后可以释放锁。

一个线程持有锁会导致其他所有需要此锁的线程挂起在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,导致性能问题如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题 三大不安全案例 1.抢票系统

package Multics;

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

线程 *** 作资源时没有上锁,导致线程不安全。

2.银行取钱
package Multics;

public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"基金");

        Drawing you=new Drawing(account,50,"你");
        Drawing friend=new Drawing(account,100,"friend");
        you.start();
        friend.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-=drawingMoney;
        nowMoney+=drawingMoney;

        System.out.println(account.name+"余额为:"+account.money);
        System.out.println(this.getName()+"现金:"+nowMoney);
    }
}

3.线程安全
package Multics;

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

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();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

同步方法

synchronized关键字

public synchronized void method(...){...}

synchronized修饰的方法必须先获得调用该方法对象的锁后才能执行,否则线程会阻塞。

1.抢票系统
package Multics;

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{
    //......

    //对this加锁
    private synchronized void buy(){
        //......
    }
}

2.银行取钱
package Multics;

public class UnsafeBank {
    public static void main(String[] args) {
        Account account=new Account(100,"基金");

        Drawing you=new Drawing(account,50,"你");
        Drawing friend=new Drawing(account,100,"friend");
        you.start();
        friend.start();
    }
}


//账户
class Account{...}

//银行  模拟取款
class Drawing extends Thread{
    //......
    
    //取钱
    @Override
    public void run() {
        //通过synchronized块对account上锁。默认上锁this
        synchronized(account){
           //源码套上synchronized块
        }
    }
}

3.线程安全
package Multics;

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

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

也可以使用现成的JUC安全类型集合

package Multics;

import java.util.concurrent.CopyOnWriteArrayList;

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(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
4.锁 4.1 死锁

产生死锁的四个必要条件:

    互斥条件:一个资源每次只能被一个进程使用。请求余保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

化妆案例

package Multics;

public class DeadDock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"A");
        Makeup g2=new Makeup(1,"B");

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

//口红
class Lipstick{ }
//镜子
class Mirror{ }

class Makeup extends Thread{
    //需要的资源都只有一份
    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 (Exception e) {
            e.printStackTrace();
        }
    }

    //化妆,一个人有口红,另一个人有镜子。双方都需要对方的资源
    private void makeup() {
        if (choice == 0) {
            synchronized (lipstick) {  //获得口红的锁
                System.out.println(this.girlName + "获得了口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //1s后,想获得镜子的锁
                synchronized (mirror) {  //获得镜子的锁
                    System.out.println(this.girlName + "获得了镜子的锁");
                }
            }
        }
        else {
            synchronized (mirror) {  //获得镜子的锁
                System.out.println(this.girlName + "获得了镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //1s后,想获得口红的锁
                synchronized (lipstick) {  //获得口红的锁
                    System.out.println(this.girlName + "获得了口红的锁");
                }
            }

        }
    }
}

修改代码

package Multics;

public class DeadDock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"A");
        Makeup g2=new Makeup(1,"B");

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

//口红
class Lipstick{ }
//镜子
class Mirror{ }

class Makeup extends Thread{
    //......
    //化妆,一个人有口红,另一个人有镜子。双方都需要对方的资源
    private void makeup() {
        if (choice == 0) {
            synchronized (lipstick) {  //获得口红的锁
                System.out.println(this.girlName + "获得了口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            //1s后,想获得镜子的锁
            synchronized (mirror) {  //获得镜子的锁
                System.out.println(this.girlName + "获得了镜子的锁");
            }
        }
        else {
            synchronized (mirror) {  //获得镜子的锁
                System.out.println(this.girlName + "获得了镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            //1s后,想获得口红的锁
            synchronized (lipstick) {  //获得口红的锁
                System.out.println(this.girlName + "获得了口红的锁");
            }

        }
    }
}

4.2 Lock锁(JDK)

java.util.concurrent.locks.Lock接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象枷锁,线程开始访问共享资源之前应先获得的Lock对象。

ReentrantLock类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。

package Multics;

import java.util.concurrent.locks.ReentrantLock;

//抢票模拟
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 ticketNums=10;

    //定义lock锁。默认对this上锁
    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--);
                }
            } finally {
                lock.unlock(); //显式解锁
            }
        }
    }
}

sychronized 和Lock对比:

Lock式显式锁。sychronized是隐式锁。后者加锁、释放锁自动完成。Lock只有代码块锁。sychronized有代码块所和方法锁。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)优先使用顺序:Lock>同步代码块>同步方法 5.线程通信 生产者、消费者问题

前提:

假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,知道仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,知道仓库中再次放入产品为止。

该问题中,生产者和消费者之间相互依赖,互为条件:

    对于生产者,没有生产产品之前,要通知消费者等待。生产了产品之后,有需要马上通知消费者消费。对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。该问题中,只用synchronized是不够的。1.synchronized可以组织并发更新同一个共享资源,实现了同步。 2.synchronized不能用来实现不同线程之间的消息传递(通信)

    //线程通信相关的java方法
    wait()  //表示线程已知等待,知道其他线程通知,与sleep不同,会释放锁
    wait(long timeout)  //指定等待的毫秒数
    notify()  //唤醒一个处于等待状态的线程
    notifyAll()  //唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先
    

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

    解决方式 1.管程法

    生产者:负责生产数据的模块(可能是方法,对象,线程,进程);消费者:负责处理数据的模块(可能是方法,对象,线程,进程);缓冲区:消费者不能直接使用生产者的数据。他们之间有一个缓冲区;

    package Multics;
    
    //需要的类:生产者、消费者、产品、缓冲区
    public class TestPC {
        public static void main(String[] args) {
            SynContainer container=new SynContainer();
    
            new Productor(container).start();
            new Consumer(container).start();
        }
    }
    
    
    //生产者
    class Productor extends Thread{
        SynContainer container;
        public Productor(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;
        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=0;
    
        //生产者放入产品
        public synchronized void push(Chicken chicken){
            //如果容器满了,就需要等待消费者消费
            if(count==chickens.length){
                //通知消费者消费,生产者等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //如果容器没有满,则放入产品
            chickens[count]=chicken;
            count++;
    
            //通知消费者消费
            this.notifyAll();
        }
    
    
        //消费者取出产品
        public synchronized Chicken pop(){
            //判断能否消费
            if(count==0){
                //等待生产者生产,消费者等待
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            //可以消费
            count--;
            Chicken chicken=chickens[count];
            //通知生产者生产
            this.notifyAll();
            return chicken;
        }
    }
    

    2.信号灯法
    package Multics;
    
    //需要的类:生产者、消费者、产品、缓冲区
    public class TestPC {
        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("节目A播放中");
                }
                else{
                    this.tv.play("节目B播放中");
                }
            }
        }
    }
    
    //消费者-->观众
    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{
        //演员表演,观众等待 T
        //观众观看,演员等待 F
        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;
        }
    }
    

    6.线程池

    背景:经常创建和销毁、使用特别大的资源,比如并发情况下的线程,对性能影响很大。

    思路:提前创建多个线程,放入线程池中,使用时直接获取,食用含放回池中。这样可以避免频繁创建销毁、实现重复利用。如显示生活中的公交车。

    优点:

    提高相应速度(减少了创建新线程的时间)降低资源消耗(重复利用线程池中的线程,不需要每次都创建)便于线程管理(核心池大小、最大线程数、线程没有任务时最多保持多长时间后会终止…)

    JDK5.0起提供了线程池相关API:ExecutorService和Executors

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

    void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行RunnableFuturesubmmit(Callabletask):执行任务,有返回值,一般用来执行Callablevoid shutdown():关闭线程池

    Executors:工具类、线程池的工具类,用于创建并返回不同类型的线程池。

    package Multics;
    
    import java.util.concurrent.Executor;
    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());
        }
    }
    

    欢迎分享,转载请注明来源:内存溢出

    原文地址: https://outofmemory.cn/zaji/5708254.html

    (0)
    打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
    上一篇 2022-12-17
    下一篇 2022-12-17

    发表评论

    登录后才能评论

    评论列表(0条)

    保存