Java多线程

多线程

1.基本概念

①程序(program):是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

②进程(process):是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过

③生命周期:如:运行中的QQ,运行中的MP3播放器。

④程序是静态的,进程是动态的,进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

⑤线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径.若一个进程同一时间并行执行多个线程,就是支持多线程的;线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小 ;一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患

⑥什么时候要使用多线程:

程序需要同时执行两个或多个任务。

程序需要实现一些需要等待的任务时,如用户输入、文件读写 操作、网络操作、搜索等。

需要一些后台运行的程序时。

2.多线程的创建方法
(1)继承Thread类重写run()方法

①创建一个继承于Thread的子类

②重写Thread的run()方法–将此线程声明的操作声明在run()方法中

③创建Thread的子类对象

④通过此对象调用start()方法

注:

1.start方法的两个作用:①启动当前线程 ②调用当前现成的run方法。

2.直接调run不行,可以调但是不是多线程,仅为主线程,仅会单独执行run()方法,等run()方法执行结束后才会执行其他方法。可用Thread().currentThead().getName来查看线程名

3.当已经启动一个线程后不可以再用此对象(已经start的线程)再去执行调用start,这时会报异常;如果还想调用start需要新建一个对象去调用。

例子:遍历100以内的所有的偶数

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();//执行分线程
        for (int i = 0; i <= 100; i = i + 2) {
            if (i % 2 == 0) {
                System.out.println(i + "**********main**********");
            }
        }
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i = i + 2) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

两个线程:一个输出奇数,一个输出偶数

public class ThreadDemo {
    public static void main(String[] args) {
        odd ji = new odd();
        ji.start();
        ou o = new ou();
        o.start();
    }
}

class odd extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

class ou extends Thread {
    @Override
    public void run() {
        for (int i = 2; i <= 100; i++) {
            if (i % 2 != 0) {
                //因为ou继承了Thread类,因此可以直接使用getName()来获取当前的线程名。
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
(2)匿名子类
public class ThreadDemo {
    public static void main(String[] args) {
        new Thread() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
        new Thread() {
            @Override
            public void run() {
                for (int i = 2; i <= 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
    }
}
(3)实现runnable接口中的run()方法

①创建一个实现了Runable接口的类

②实现类去实现Runable中的抽象方法:run()

③创建实现类的对象

④将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象

⑤通过Thread类的对象调用start()方法

public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread thread = new Thread(myThread);
        thread.setName("线程一");
        thread.start();
        //在创建一个线程
        Thread thread1 = new Thread(myThread);
        thread1.setName("线程二");
        thread1.start();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {//因为不是继承Thread方法,因此不能直接getName()所以要用此形式
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
(4)实现Callable接口
1.Callable功能更强大

①call()可以有返回值

②call()可以抛出异常,被外面的操作捕获,获取异常的信息

③支持泛型的返回值

④需要借助FutureTask类,比如获取返回结果

2.步骤:

①创建一个实现Callable接口的实现类

②实现Call()方法,并将此线程需要执行的操作声明在Call()方法中

③创建Callable接口实现类的对象

④创建FutureTask对象,并将Callable接口实现类的对象传入FutureTask的构造器中

⑤new一个Thread,并将FutureTask对象作为参数传递到Thread中,并调用start()

⑥可以使用FutureTask对象.get()去获取Call()方法中的返回值,利用Object类型的对象去接收,get()方法需要加try/catch

public class Test {
    public static void main(String[] args) {
        //③创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //④创建FutureTask对象,并将Callable接口实现类的对象传入FutureTask的构造器中
        FutureTask futureTask = new FutureTask(numThread);
        //因为FutureTalk实现了Runnable接口,因此它可以放入Thread中做参数
        //⑤new一个Thread,并将FutureTask对象作为参数传递到Thread中,并调用start()
        new Thread(futureTask).start();
        Object num = null;
        try {
            //get()的返回值即为FutureTask构造器参数Callable实现类重写call()的返回值
            //⑥可以使用FutureTask对象.get()去获取Call()方法中的返回值,利用Object类型的对象去接收
            num = futureTask.get();
            System.out.println("总和为" + num);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
//①创建一个实现Callable接口的实现类
class NumThread implements Callable {
    //②实现Call()方法,并将此线程需要执行的操作声明在Call()方法中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        //sum是int类型,但是Object无int类型,因此返回int时自动装箱位Integer
        return sum;
    }
}
(5)使用线程池
1.优点

①提高响应速度(减少了创建新线程的时间)

②降低资源消耗(重复利用线程池中线程,不需要每次都创建),便于线程管理

③corePoolSize:核心池的大小

④maximumPoolSize:最大线程数

⑤keepAliveTime:线程没有任务时最多保持多长时间后会终止

2.使用

①创建线程池:利用ExecutorService类型来接收

 Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池

 Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

 Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

 Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

②执行指定线程的操作:对象可以在()new也可以提前创建

service.submit(参数为实现了Callable的对象);

service.execute(参数为实现Runnable的对象);

③ExecutorService对象.shutdown();关闭连接池

④设置线程池属性:

先将ExecutorService对象强转为ThreadPoolExecutor类型:ThreadPoolExecutor service1=(ThreadPoolExecutor)service;

利用强转后的service1对象来调用set/getcorePoolSize、set/getmaximumPoolSize、set/getkeepAliveTime方法

public class Test {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        //设置线程池属性:先将ExecutorService对象强转为ThreadPoolExecutor类型.利用强转后的service1对象来调用set/getcorePoolSize、set/getmaximumPoolSize、set/getkeepAliveTime方法
        ThreadPoolExecutor service1=(ThreadPoolExecutor)service;
        service1.setCorePoolSize(15);
        service1.getCorePoolSize();
        //适合适用于Callable,参数为实现了Callable的对象
        //service.submit();
        //适合适用于Runnable,参数为实现Runnable的对象
        service.execute(new NumThread());
        service.execute(new NumThread1());
        //关闭连接池
        service.shutdown();
    }
}
class NumThread implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class NumThread1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
卖票例题

①用继承的方式

public class Test {
    public static void main(String[] args) {
        Wiondow w = new Wiondow();
        //创建了一个Wiondow提供给三个对象使用,因此属性值是共用的
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口一");
        s2.setName("窗口二");
        s3.setName("窗口三");
        s1.start();
        s2.start();
        s3.start();
    }

}

class Wiondow extends Thread {
    private static int number = 100;//加static
    @Override
    public void run() {
        while (true) {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出:" + number);
                number--;
            } else {
                break;
            }

        }
    }
}

②用实现类的方式

public class Test {
    public static void main(String[] args) {
        Wiondow w = new Wiondow();
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口一");
        s2.setName("窗口二");
        s3.setName("窗口三");
        s1.start();
        s2.start();
        s3.start();
    }
}

class Wiondow implements Runnable {
    private  int number = 100;//不用加static了,因为三个对象共用一个Wiondow
    @Override
    public void run() {
        while (true) {
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "卖出:" + number);
                number--;
            } else {
                break;
            }

        }
    }
}
两种方式的异同

开发中优先选择接口的方式

原因:①实现的方式没有类的单继承性局限性

​ ②实现的方式更适合来处理多个线程有共享数据的情况

联系:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中

3.Thread常用方法

(1)start(): 启动线程,并执行对象的run()方法

(2)run(): 通常需要重写方法,将需要执行的操作写在此方法中。线程在被调度时执行的操作

(3)currentThread():静态方法,返回当前代码执行的线程(返回类型为Thread);this.方法=Thread.currentThread().方法

(4)getName(): 返回线程的名称,返回值类型String

(5)setName(String name):设置该线程名称,返回值类型void

(6)yield():线程让步,不会阻塞,只是使线程转换为就绪状态,释放该线程的执行权,让系统调度器重新调度一次,只有比该线程优先级高或相同的线程才有可能获得CPU的执行权。返回值void

(7)join() :在线程A中调用线程B的join方法,则线程A会变成阻塞状态,直到线程B执行完毕后A才结束阻塞状态,A或其他线程再执行。使用:对象(当前线程).join(),join省略了当前的对象及this或Thread.currentThread(),如main中有对象m1则m1.join()

(8)sleep(long millis):阻塞millis毫秒(1s=1000ms),要设置异常或抛出异常,由于继承过来的run方法没有设置抛出异常,因此不可以将异常抛出。由于sleep为静态方法因此只可以对当前线程进行设置,sleep后线程进入就绪状态而不是立即开始执行,需CPU的使用权,然后进入执行执行状态

(9)isAlive():返回boolean,判断线程是否还活着,对象.isAlive()

(10)后台线程:当程序中只有后台线程时我,进程不久就会结束。利用 线程.setDaemon(true) 来将某个线程设置为后台线程,该方法要在start()方法之前使用。使用 线程.isDaemon() 来判断当前线程是后台线程。

//给主线程和自定义线程命名(尽量在在所有的线程调用start()之前)
Thread.currentThread().setName("主线程");
myThread.setName("线程1");
//利用构造器设置线程
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread("线程1");
        myThread.start();
    }
}
class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
    }

    public MyThread(String name) {
        super(name);
    }
}
//yield
public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            if (i%20==0){
                //yield()=this.yield=Thread.currentThread().yield();
                yield();//因为继承了Thread类因此可以直接调用
            }
        }
    }
//join
//例1
        MyThread myThread=new MyThread();
        new Thread() {
            @Override
            public void run() {
                try {
                    myThread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
//例二
public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            if (i%20==0){
               try {
                   //对象.join(),join省略了当前的对象及this或Thread.currentThread(),如main中有对象m1则m1.join()
                    join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
//sleep
public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            if (i%20==0){
               try {
                    sleep();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
4.线程的优先级
(1)优先级大小(1~10):高的不一定先执行,只是抢占概率较高

MAX_PRIORITY:10

MIN _PRIORITY:1

NORM_PRIORITY:5

(2)涉及的方法

①getPriority() :返回线程优先

②setPriority(int newPriority) :改变线程的优先级

myThread.setPriority(Thread.MAX_PRIORITY);
myThread1.setPriority(7);
System.out.println("优先级"+Thread.currentThread().getPriority());
System.out.println("优先级"+getPriority());//也可以不写当先线程的方法,如在某类中,不用写获得当前线程,但加上更清楚
5.线程的声明周期
(1)JDK中用Thread.State类定义了线程的几种状态

①新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态

②就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源

③运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能

④阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态

⑤死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

6.线程的同步–解决线程安全问题
(1)线程的安全问题:在卖票过程中出现了重票和错票,这就是线程的安全问题(有安全问题的前提:有共享数据)
(2)问题出现的原因:当某个线程操作车票过程中尚未操作完成,其他线程参与进来也操作车票
(3)解决措施:当一个线程A在操作ticket的时候其他线程不能参与进来,直到线程A操作完ticket时,其他线程才可以操作ticket。这种情况即使线程A出现了阻塞,也不能被改变
(4)在java中如何解决:在java中我们通过同步机制,来解决线程的安全问题(推荐使用顺序:LOCK、同步代码块、同步方法)
方式一:同步代码块

synchronized(同步监视器){被同步的代码}

相当于加锁,一个线程执行完才会执行别的线程。

说明:①操作共享数据的代码即为被同步的代码(不能包含多了也不能包含少了,如把锁加在while之前,则一个对象都执行完即票数到0才会执行下一个线程,如果放在while后则锁住判断条件,然后解锁后再判断下一次while)

​ ②共享数据:多个线程共同操作的变量。比如:ticket就是共享数据

​ ③同步监视器(锁):任何一个类的对象都可以充当锁,加上锁后,暂时只执行当前的线程(要求多个线程必须共用同一把锁;即最好定义在方法外,定义为属性)

注:①在实现类方法中可以考虑用当前类的对象即this(表示当前类的对象w,要考虑this是不是同一个对象),用this就不用定义其他的对象了

​ ②在继承thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类来充当同步监视器即当synchronized(当前类.calss){}

​ ③在继承父类的多线程时,声明锁时要声明为static的,这样才能保证多个对象共用一个对象

//使用同步代码块解决实现接口方法的线程安全问题:对象的创建不用加static,因为实现thread的方法中定义的属性天然是共享的
public class Test {
    public static void main(String[] args) {
        Wiondow w = new Wiondow();
        //创建了一个Wiondow提供给三个对象使用,因此属性值是共用的
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口一");
        s2.setName("窗口二");
        s3.setName("窗口三");
        s1.start();
        s2.start();
        s3.start();
    }

}
class Wiondow implements Runnable {
    private int number = 100;//不用加static了,因为三个对象共用一个Wiondow
    Object o = new Object();
    Dog d=new Dog();//可以在下面自定义一个类,在此使用如Dog

    @Override
    public void run() {
        //Object o = new Object();不可以把锁放在这里,因为没调用一次方法就创建一个对象,并不是同一个锁
        while (true) {
            synchronized (o) {
            synchronized (this) {//在实现类方法中可以用当前类的对象即this(表示当前类的对象w),用this就不用定义其他的对象了
                if (number > 0) {
                    System.out.println(Thread.currentThread().getName() + "卖出:" + number);
                    number--;
                } else {
                    break;
                }
            }

        }
    }
}

class Dog {
}



//在继承父类的多线程时,声明锁时要声明为static的,这样才能保证多个对象共用一个对象
public class WindowTest {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket extends Thread {
    private static int ticker = 100;
    //在继承父类的多线程时,声明锁时要声明为static的,这样才能保证多个对象共用一个属性对象
    private static Object o=new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (o) {//不可以用this,因为this分别代表t1、t2、t3三个对象,但是可以用Ticket.class用类来充当,因为类也是对象,类Ticket只会加载一次,因此满足条件
            synchronized(Ticket.class){
                if (ticker > 0) {
                    System.out.println(getName() + ":卖票,票号为:" + ticker);
                    ticker--;
                } else {
                    break;
                }
            }
        }
    }
}
方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的

注:

​ ①同步方法仍需要同步监视器,但是不需要显示的声明。其中,非静态的同步方法的同步监视器是this,静态的同步方法的同步监视器是当前类本身

②被synchronized修饰的方法在同一时间只允许一个线程访问

//用同步方法解决实现方式中的线程安全问题
public class Test {
    public static void main(String[] args) {
        Wiondow w = new Wiondow();
        //创建了一个Wiondow提供给三个对象使用,因此属性值是共用的
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口一");
        s2.setName("窗口二");
        s3.setName("窗口三");
        s1.start();
        s2.start();
        s3.start();
    }

}
class Wiondow implements Runnable {
    private int number = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    private synchronized void show() {//同步方法中使用的同步监视器为this对象
        if (number > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出:" + number);
            number--;
        }
    }
}




//用同步方法解决继承方式中的线程安全问题
public class WindowTest {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();
        Ticket t2 = new Ticket();
        Ticket t3 = new Ticket();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket extends Thread {
    private static int ticker = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    private static synchronized void show() {//继承类方法的同步监视器为当前类Ticket
        if (ticker > 0) {
            //需要以当前对象调用方法,因为静态方法中不能调动非静态方法
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticker);
            ticker--;
        }
    }
}
方式三:Lock锁–JDK5.0新增方式

①实例化ReentrantLock-- private ReentrantLock lock = new ReentrantLock();()中可填true与false,后者为默认,true为公平,哪个线程先来哪个线程先执行按顺序(先进先出),false可以出现窗口1连续打印好多张等情况

②调用lock方法(即在try中的内容被锁住)–lock.lock();

③将共享数据放入try中

④在try后加finally,并在finally中解锁–lock.unlock();

public class Test {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window implements Runnable {
    private int number = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();//如果lock是继承方法则需要在此加静态
    @Override
    public void run() {
        while (true) {
            //2.调用lock方法,即在try中的内容被锁住
            lock.lock();
            try {
                if (number > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售出" + number + "号票");
                    number--;
                } else {
                    break;
                }
            } finally {
                //3.加finally并解锁
                lock.unlock();
            }
        }
    }
}
(5)synchronized与lock的相同与不同

相同:都是用来解决线程安全问题的

不同:synchronized在执行完相应的同步代码以后,自动的释放同步监视器

​ lock需要手动的去启动同步(lock())与结束同步(unlock())

(6)代码同步的局限性:操作代码同步时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低
(7)练习
//银行有一个账户。有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
//问题:该程序是否有安全问题,如果有,如何解决?
//    1.是否是多线程问题:是,两个储户线程
//    2.是否有共享数据:有,账户或账户余额
//    3.是否有线程安全问题:有
//    4.如何解决线程安全问题:同步机制
public class Test {
    public static void main(String[] args) {
        Account a = new Account(0);
        Customer c1 = new Customer(a);
        Customer c2 = new Customer(a);
        c1.setName("小李");
        c2.setName("小明");
        c1.start();
        c2.start();
    }
}

class Account {
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }

    //使用synchroniezd解决线程安全问题
    public synchronized void dispose(double atm) {//用代码块方式时这个可以用this,因为三个对象共用的是同一个Account
        //synchronized(this)--run中account调用的dispose()方法。
        balance += atm;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":存款成功,余额为:" + balance);
    }
}

class Customer extends Thread {
    private Account account;//想要在非继承或非实现类中调用其他类的方法,要现在此类中定义要调用类的对象

    public Customer(Account account) {
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.dispose(1000);
        }
    }
}
7.死锁

(1)死锁:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

(2)我们使用同步时,要避免死锁

8.线程通信的三个方法
(1)线程通信的三个方法

​ ①wait():一旦执行此方法,当前线程就进入阻塞状态并释放同步监视器

​ ②notify()方法:一旦执行此方法就唤醒被wait的一个线程,如果有多个线程则会唤醒优先级较高的那个

​ ③notifyAll()方法:一旦执行此方法就唤醒所有被wait的线程

(2)注:

​ ①线程被唤醒:该线程可以执行

​ ②等待:该线程不可再执行,直到被唤醒

​ ③释放锁:允许别的线程执行

(3)说明:

​ ①wait、notify、notifyAll这三个方法只能出现在同步代码块或同步方法中,lock中也不行

​ ②三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出现异常,如下二

​ ③这三个方法定义在object中,因为方便任何一个对象充当同步监视器是可以调用该方法

public class Test {
    public static void main(String[] args) {
        Number n = new Number();
        Thread t1 = new Thread(n);
        Thread t2 = new Thread(n);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}

class Number implements Runnable {
    private int number = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {--下面的natify和wait均省略了this,如果此处改为obj,则下面的notify和wait均为obk.方法
                notify();//唤醒另一个线程
                //notifyAll();唤醒所有线程
                if (number <= 100) {
                    try {
                        Thread.sleep(100);//阻塞但是不会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    try {
                        //使得调用如下wait()方法的线程进入阻塞状态,虽然阻塞了但会释放锁
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }

        }
    }
}

(4)面试题:sleep()与wait()的异同

相同点

​ ①一旦执行方法都可以使当前的线程进入阻塞状态

不同点

​ ①两个方法收录的位置不同:Thread类中声明sleep(),Object类中声明wait()

​ ②调用的要求不同:sleep()可以在需要的情况下调用;wait()方法必须使用在同步代码块或同步监视器中

​ ③关于是否释放同步监视器:若两个方法都使用在同步代码块或同步监视器中,sleep()不会释放同步监视器,但wait会释放同步监视器

(4)生产者消费者问题
/**
 * @author Yu
 * @create 2022-05-11 21:06
 * @description 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
 * 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图
 * 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
 * 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如
 * 果店中有产品了再通知消费者来取走产品。
 * 这里可能出现两个问题:
 * 生产者比消费者快时,消费者会漏掉一些数据没有取到。
 * 消费者比生产者快时,消费者会取相同的数据。
 * 分析:
 * 1.是否是多线程问题:是--生产者    消费者
 * 2.是否有共享数据:有--店员(或产品)
 * 3.如何解决线程安全问题:同步机制
 * 4.是否涉及线程通信:是
 */
public class PS {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");
        Customer c1 = new Customer(clerk);
        c1.setName("消费者1");
        Customer c2=new Customer(clerk);
        c2.setName("消费者2");
        p1.start();
        c1.start();
        c2.start();
    }
}
class Clerk {
    private int produceCount = 0;

    public synchronized void produceProduct() {
        if (produceCount < 20) {
            produceCount++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + produceCount + "个产品");
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public synchronized void consumeProduct() {
        if (produceCount > 0) {
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + produceCount + "个产品");
            produceCount--;
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Producer extends Thread {
    private Clerk clerk;

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

    @Override
    public void run() {

        System.out.println(getName() + ":开始消费产品。。。。。。");
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
class Customer extends Thread {
    private Clerk clerk;
    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        System.out.println(getName() + ":开始生产产品。。。。。。");
        while (true) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值