Java基础回顾-多线程

多线程

并发

指两个或多个时间再同一个时间段内发生(在多个任务中交替执行,一个一个的执行)

比喻:一个人吃两个馒头,一个一个吃

并行

指两个或多个时间再同一个时刻发生(同时发生)(多个任务同时执行)

比喻:两个人吃两个馒头,同时吃~


所以说,并行的速度更快(但是由于计算机处理事务特别快,所以当事务不是很多的时候,二者速度在我们人来感受起来,相差并不大)


线程与进程

进程:

指一个内存中正在运行的应用程序,即电脑当中每一个app都算一个进程~

每打开一个app(应用程序),就会进入内存产生一个进程~

线程:

先了解CPU(中央处理器):指挥电脑中的软件和硬件干活儿~

CPU的分类:

  1. AMD-----
  2. Inter-----Core(核心) i7 8866 四核八线程(8线程表示可以同时执行8个任务)
  3. 以电脑管家为例:每次使用电脑管家里的功能进行首页体检、病毒查杀、垃圾清理、电脑加速的时候,每一次都会开辟一条执行路径,这个路径就叫线程~

线程属于进程

是进程中的一个执行单元,负责程序的执行

假如CPU是单核心单线程CPU,它只能同时执行一个线程,如果要执行电脑管家,它就会在多个任务之间,高速的切换,轮流执行多个线程~

由于速度很快,就看起来像同时执行~(效率较低)

假如CPU是4核心8线程CPU,它就能同时执行8个线程,如果要执行电脑管家,它就会是8个线程在多个任务之间,高速的切换,那么它的速度就会是单核心单线程CPU的8倍(每个任务被执行到的几率都被提高8倍)!

由于速度很快,就看起来像同时执行~(效率较高)


多线程好处:

  1. 效率较高
  2. 多个线程之间互不影响

线程调度


分时调度

所有线程轮流使用CPU,每个线程使用CPU的时间平均分~(AA制


抢占式调度

把线程分优先级,优先级高的线程优先使用CPU(如果优先级一样,CPU就随机选一个线程)(VIP制

CPU都是在多个进程的多个线程之间进行高速切换,谁的优先级高,呢么被执行的机会就大一些~


主线程

指执行主方法(main方法)的线程~


单线程程序:Java程序中只有一个线程

程序从main方法开始,从上到下,按顺序执行

Person.java

public class Person {
    private String name;

    public void run(){
        //输出10次name
        for (int i = 1; i <= 10; i++) {
            System.out.println("第" + i +"次name:"+name+i);
        }
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

PersonTest.java

public class PersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("牛牛");
        Person person2 = new Person("niniu");
        person1.run();
        person2.run();
    }
}

运行结果:

这就是一个单线程程序~😐

假如在牛牛的run()方法niuniu的run()方法之间加一个异常(这里我用运算异常:除法分母不能为零的异常),会发现,niuniu就不打印了,如下图所示:

public class PersonTest {
    public static void main(String[] args) {
        Person person1 = new Person("牛牛");
        Person person2 = new Person("niniu");
        
        person1.run();
        
        //加入一个运算异常
        System.out.println(1/0);
        
        person2.run();
    }
}

运行结果:


由于单线程有弊端,所以我们学习创建多线程!😀

创建线程类(创建多线程)

Java使用java.lang.Thread类代表线程

Java虚拟机允许应用程序并发的运行多个执行线程-------->并发:同时发生

创建新执行线程有两种方法

  • 一种是将类声明为Thread的子类------------------->该子类应该重写Thread类的run方法,接着该子类就可以分配并启动该子类的实例。

即创建一个自定义的子类去继承Thread类,然后重写里面的run();方法,然后在测试类中new刚刚创建的Thread的子类对象,并使用它去调用start();方法。如下代码和运行结果:

代码:

ChildreThread.java

public class ChildreThread extends Thread{
    private String name;

    //构造器
    public ChildreThread(String name) {
        this.name = name;
    }

    //重写run方法
    public void run(){
        //输出100次name
        for (int i = 1; i <= 100; i++) {
            System.out.println("第" + i +"次name:"+name+i);
        }
    }
}

ChildreThreadTest.java

public class ChildreThreadTest {
    public static void main(String[] args) {
        ChildreThread childreThread2 = new ChildreThread("start创建的多线程");
        childreThread2.start();//start创建的多线程


        //输出100次"主线程"
        for (int i = 1; i <= 100; i++) {
            System.out.println("第" + i +"次主线程");
        }
    }


}

运行结果:

发现两个线程在互相争抢运行~成功!😀

产生多线程的原理:

本来main函数可以自上而下执行一条main线程,但是中途出现了一个“childreThread2.start();//start创建的多线程”,于是main方法这条路径上就出现了一条新的线程路径(这条路径指向继承Thread的子类中的run();方法),于是乎,CPU就会对这两条线程路径进行随机选择,也可以说是这两条线程路径在争抢CPU的使用权,所以就出现了主线程和新创建的线程交替执行的画面~😂

多线程的内存图解:

优点:

每一个线程都在一个新的栈里,所以他们互不干扰~😀

  • 另一种是实现声明实现Runnable接口的类------>略

Thread类中的常用方法

  • public String getName();--------->获取当前线程名;
  • public void start();--------->导致该线程开始执行;Java虚拟机调用此线程的run方法;
  • public void run();--------->此线程要执行的任务在此处定义代码;
  • public static void sleep(long millis);--------->使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行);
  • public static Thread currentThread();--------->返回对当前正在执行的线程对象的引用;

获取线程名称

public String getName();--------->获取当前线程名:

MyThread.java

/*获取线程的名称:
*       1.使用Thread类中的getName()方法
*               String getName() 返回该线程的名称
*       2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
*               static Thread currentThread() 返回对当前正在执行的线程对象的引用。
* */

//第一步:定义一个Thread类的子类
public class MyThread extends Thread{
    //第二步:重写Thread类中的run方法,设置线程任务


    @Override
    public void run() {
        //获取线程的名称
        String name = getName();
        System.out.println(name);
    }
}

MyThreadTest.java

public class MyThreadTest {
    public static void main(String[] args) {
        //创建子类对象
        MyThread myThread = new MyThread();
        //调用start方法开启一个新线程
        myThread.start();//创建的新线程会去执行它自己重写的run方法,即MyThread类中的run方法
    }
}

运行结果:

如果再创建一个新线程:

public class MyThreadTest {
    public static void main(String[] args) {
        //创建子类对象
        MyThread myThread = new MyThread();
        //调用start方法开启一个新线程
        myThread.start();//创建的新线程会去执行它自己重写的run方法,即MyThread类中的run方法
        //如果再创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-1
        //如果又创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-2
        //以此类推.....还会有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();

    }
}

运行结果:

在重写的run方法中,换另外一种方式获取线程名,即使用Thread类中的currentThread()方法,直接就可以获取到当前正在执行的线程,然后使用getName方法得到线程的名称!

代码如下:

MyThread.java

/*获取线程的名称:
*       1.使用Thread类中的getName()方法
*               String getName() 返回该线程的名称
*       2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
*               static Thread currentThread() 返回对当前正在执行的线程对象的引用。
* */

//第一步:定义一个Thread类的子类
public class MyThread extends Thread{
    //第二步:重写Thread类中的run方法,设置线程任务


    @Override
    public void run() {
        //获取线程的名称
//        String name = getName();
//        System.out.println(name);
        Thread thread = Thread.currentThread();//currentThread()或取当前线程
        System.out.println(thread+"------这是线程");//打印当前线程
        String threadName = thread.getName();//getName()获取线程名称
        System.out.println(threadName+"---------------------这是线程名称");//打印上一步获取到的线程名称
    }
}

MyThreadTest.java

public class MyThreadTest {
    public static void main(String[] args) {
        //创建子类对象
        MyThread myThread = new MyThread();
        //调用start方法开启一个新线程
        myThread.start();//创建的新线程会去执行它自己重写的run方法,即MyThread类中的run方法
        //如果再创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-1
        //如果又创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-2
        //以此类推.....还会有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();

    }
}

运行结果:

代码优化:

使用一行代码(链式编程):

MyThread.java

/*获取线程的名称:
*       1.使用Thread类中的getName()方法
*               String getName() 返回该线程的名称
*       2.可以先获取到当前正在执行的线程,使用线程中的方法getName()获取线程的名称
*               static Thread currentThread() 返回对当前正在执行的线程对象的引用。
* */

//第一步:定义一个Thread类的子类
public class MyThread extends Thread{
    //第二步:重写Thread类中的run方法,设置线程任务


    @Override
    public void run() {
        //获取线程的名称
//        String name = getName();
//        System.out.println(name);
/*        Thread thread = Thread.currentThread();//currentThread()或取当前线程
        System.out.println(thread+"------这是线程");//打印当前线程
        String threadName = thread.getName();//getName()获取线程名称
        System.out.println(threadName+"---------------------这是线程名称");//打印上一步获取到的线程名称*/

        System.out.println(Thread.currentThread()+"------这是线程!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
        System.out.println(Thread.currentThread().getName()+"---------------------这是线程名称");
    }
}

MyThreadTest.java

public class MyThreadTest {
    public static void main(String[] args) {
        //创建子类对象
        MyThread myThread = new MyThread();
        //调用start方法开启一个新线程
        myThread.start();//创建的新线程会去执行它自己重写的run方法,即MyThread类中的run方法
        //如果再创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-1
        //如果又创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-2
        //以此类推.....还会有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();

    }
}

运行结果:

获取主线程名称:

public class MyThreadTest {
    public static void main(String[] args) {
        //创建子类对象
        MyThread myThread = new MyThread();
        //调用start方法开启一个新线程
        myThread.start();//创建的新线程会去执行它自己重写的run方法,即MyThread类中的run方法
        //如果再创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-1
        //如果又创建一个新线程
        new MyThread().start();//新线程的名称就会是Thread-2
        //以此类推.....还会有Thread-3,Thread-4,Thread-5,Thread-6...Thread-n
        new MyThread().start();
        new MyThread().start();
        new MyThread().start();

        //获取主线程名称
        System.out.println(Thread.currentThread().getName());

    }
}

运行结果:


设置线程的名称

两种方式:

  1. 使用Thread类中的setName(自定义线程名)方法

  1. .创建一个带参数的构造方法,参数传递线程的名称;然后调用父类的带参数的构造方法,把线程名称传递个父类,让父类Thread给子线程起一个名字!

  • 使用Thread类中的setName(自定义线程名)方法

MyThread.java

/*设置线程的名称:
*       1.使用Thread类中的setName()方法
*                void setName(String name) 改变线程名称,使之与参数 name 相同。
*       2.创建一个带参数的构造方法,参数传递线程的名称;
*         然后调用父类的带参数的构造方法,把线程名称传递个父类,让父类Thread给子线程起一个名字!
* */

public class MyThread extends Thread{

    //方式2
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);//将name传递给父类
    }

    @Override
    public void run() {
        //获取并打印线程名称
        System.out.println(Thread.currentThread().getName());
    }
}

MyThreadTest.java

public class MyThreadTest {
    public static void main(String[] args) {
        //new一个子类对象,开启新线程
        MyThread myThread = new MyThread();
        //先自定义一下线程名称,然后再开启新线程
        myThread.setName("niu_niu");
        myThread.start();

        //new一个子类对象并直接传入自定义线程名称参数,开启新线程
        MyThread myThread1 = new MyThread("传入自定义参数更改线程名称");
        myThread1.start();

        //获取主线程名称
        System.out.println(Thread.currentThread().getName());

    }
}

运行结果:


sleep类

public static void sleep(long millis);--------->使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行);

public class MyThreadTest {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i <= 60; i++) {
            System.out.println(i);
            //使用Thread类的sleep方法让程序睡眠一秒钟
            try {
                Thread.sleep(1000);//静态方法通过类名直接调用,括号里单位为毫秒,1000毫秒==1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:(每过一秒打印一个数字)


创建多线程的第二种方式-Runable接口(推荐使用)

编写java.lang.Runable接口的实现类,并重写run()方法,该run()方法的方法体同样是该线程的线程执行体。

但是!java.lang.Runable接口里没有点start()方法,那么要怎么开启线程呢?

不急,这么来就行-------->先创建一个java.lang.Runable接口的实现类的实现类对象,然后把实现类对象当作参数传到Thread类里面,最后再去点start();这样就开启了一个新线程了~😁

public class MyThread implements Runnable{

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        System.out.println("新线程名:"+name);
    }
}
public class MyThreadTest {

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            MyThread myThread = new MyThread();
            Thread thread = new Thread(myThread);
            thread.start();
    }


        //输出100次"主线程"
        for (int i = 1; i <= 10; i++) {
            System.out.println("第" + i +"次主线程");
        }
    }
}

运行结果:


实现Runable接口创建多线程的好处:

  • 避免了单继承的局限性----------------一个类只能继承一个类(一个人只能有一个亲爹!),类继承了Thread类就不能继承其他的类。用Runable接口创建多线程之后,子类还可以继承其他的类,实现其他的而接口!
  • 增强了程序的扩展性,降低了程序的耦合性(解耦)----------实现Runable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)实现类中,重写的run方法就是用来设置线程要执行的任务;而创建Threrad类对象,调用start()方法,就是用来开启新线程;这样我们可以传给Thread类对象不同的参数,就可以控制线程执行不同的任务了~

匿名内部类的方式实现线程的创建

格式:

new 父类/接口(){

​ 重写父类/接口中的方法;

};

AnonymousInnerClass.java

public class AnonymousInnerClass {
    public static void main(String[] args) {
        //匿名内部类:线程的父类是Thread的方式
        new Thread(){
            @Override
            public void run() {
                System.out.println("新线程1:"+Thread.currentThread().getName());
            }
        }.start();
        //匿名内部类:线程的接口是Runable的方式
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                System.out.println("新线程2:" + Thread.currentThread().getName());
            }
        };
        new Thread(runnable).start();
    }
}

运行结果:


线程安全问题

/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private int ticket = 100;

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            //先判断电影票是否存在
            if (ticket>0){

                //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //有票,(票递减)===>ticket--
                System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
                ticket--;
            }
        }

    }
}
import junit.framework.TestCase;

public class MovieTicketTest extends TestCase {
    public static void main(String[] args) {
        //new一个Runable的实现类对象--->MovieTicket
        MovieTicket movieTicket = new MovieTicket();
        //用一个MovieTicket往三个线程的Thread里传,模拟电影院3个卖票窗口一起卖100张电影票的效果
        Thread thread1 = new Thread(movieTicket);
        Thread thread2 = new Thread(movieTicket);
        Thread thread3 = new Thread(movieTicket);
        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:

出现了重复的票和不存在的票!!!!!!!!!!

即出现了线程安全的问题!!!!!!!!!!


出现线程安全问题的原理


解决线程安全问题

线程同步

线程同步方式1:同步代码块

synchronized关键字可以用于方法中的某个区块中,表示只对这个区块中的资源进行互斥访问。

格式:

synchroniuzed(同步锁){
    需要同步操作的代码(可能会出现线程安全问题的代码)(访问了共享数据的代码)
}

什么是同步锁?

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

​ 1.锁对象可以是任意类型。

​ 2.必须保证多个线程对象要使用同一把锁。

​ 3.锁对象的作用:把同步代码块”锁住“,只让一个线程在代码块中执行

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到同步锁,谁就进入代码块,此时其他的线程只能在外等候(BLOCKED)

/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private int ticket = 100;

    //创建一个锁对象(在run方法外面创建锁对象是因为,如果在run方法里面创建锁对象,那么每次开启一条线程就会创建一个锁随想,这样就不能保证锁对象唯一了)
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            //创建同步代码块
            synchronized (obj){
                //先判断电影票是否存在
                if (ticket>0){

                    //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    //有票,(票递减)===>ticket--
                    System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}
import junit.framework.TestCase;

public class MovieTicketTest extends TestCase {
    public static void main(String[] args) {
        //new一个Runable的实现类对象--->MovieTicket
        MovieTicket movieTicket = new MovieTicket();
        //用一个MovieTicket往三个线程的Thread里传,模拟电影院3个卖票窗口一起卖100张电影票的效果
        Thread thread1 = new Thread(movieTicket);
        Thread thread2 = new Thread(movieTicket);
        Thread thread3 = new Thread(movieTicket);
        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:没有出现重复票和不存在的票了,说明synchronized关键字有效解决了线程安全问题😀😀😀

该同步技术的原理

原理:使用了锁对象,这个锁对象又叫同步锁或对象锁,也叫对象监视器

个人理解:

当一个线程抢夺到CPU的执行权后,执行run方法时,会碰到synchronized代码块;

此时该线程就会检查synchronized代码块是否有锁对象,如果有,则会获取到锁对象,接着就执行同步代码块;

与此同时,另一个线程虽然也抢夺到了CPU的执行权,但是锁对象此时已将被占用,所以其他抢夺到CPU使用权的线程就无法获得锁对象,这就导致没有获得锁对象的线程无法进入同步代码块;

无法获得锁对象的线程就会进入阻塞状态,只能等待那个已经获取到锁对象的线程执行完毕后把锁对象释放出来

于是乎,当多线程执行时,大家都去争CPU并且还去争着获取唯一的锁对象,当某一个线程获取到锁对象的时候,其它线程就会进入阻塞状态,就只能干巴巴的等着锁对象被释放出来(获取到锁对象的线程执行完毕后就会把锁对象释放出来),然后大家再去抢夺被释放出来的、唯一的锁对象😶😶😶

这样就达到了同步代码块每次只能由某一个线程执行一次,这样模拟卖票程序就不会出现多个窗口卖同一张票或卖不存在的票的情况了~

线程同步方式2:同步方法

格式:

public synchroniuzed void method(){
    需要同步操作的代码(可能会出现线程安全问题的代码)(访问了共享数据的代码)
}

MovieTicket.java

/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private int ticket = 100;

    //创建一个锁对象(在run方法外面创建锁对象是因为,如果在run方法里面创建锁对象,那么每次开启一条线程就会创建一个锁随想,这样就不能保证锁对象唯一了)
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            sellTickets();
        }
    }
    //定义一个同步方法sellTickets(卖票)
    public synchronized void sellTickets(){
        //先判断电影票是否存在
        if (ticket>0){

            //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //有票,(票递减)===>ticket--
            System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}

MovieTicketTest.java

import junit.framework.TestCase;

public class MovieTicketTest extends TestCase {
    public static void main(String[] args) {
        //new一个Runable的实现类对象--->MovieTicket
        MovieTicket movieTicket = new MovieTicket();
        //用一个MovieTicket往三个线程的Thread里传,模拟电影院3个卖票窗口一起卖100张电影票的效果
        Thread thread1 = new Thread(movieTicket);
        Thread thread2 = new Thread(movieTicket);
        Thread thread3 = new Thread(movieTicket);
        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:

该同步技术的原理

原理:同步方法也会把方法内部的代码锁住,只让一个线程执行,它的锁对象就是线程的实现类对象

还有一种静态同步方法
/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private static int ticket = 100;//静态变量

    //创建一个锁对象(在run方法外面创建锁对象是因为,如果在run方法里面创建锁对象,那么每次开启一条线程就会创建一个锁随想,这样就不能保证锁对象唯一了)
    Object obj = new Object();

    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            sellTicketsStatic();
        }
    }
    //定义一个同步方法sellTickets(卖票)
    public static synchronized void sellTicketsStatic(){//静态方法
        //先判断电影票是否存在
        if (ticket>0){

            //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //有票,(票递减)===>ticket--
            System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
            ticket--;
        }
    }
}
import junit.framework.TestCase;

public class MovieTicketTest extends TestCase {
    public static void main(String[] args) {
        //new一个Runable的实现类对象--->MovieTicket
        MovieTicket movieTicket = new MovieTicket();
        //用一个MovieTicket往三个线程的Thread里传,模拟电影院3个卖票窗口一起卖100张电影票的效果
        Thread thread1 = new Thread(movieTicket);
        Thread thread2 = new Thread(movieTicket);
        Thread thread3 = new Thread(movieTicket);
        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

运行结果:

静态同步方法的锁对象是实现类对象.class;(比如MovieTicket.classs)

线程同步方式3:Lock锁机制

Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。

Lock接口中的常用方法:

方法摘要
voidlock() 获取锁。
voidlockInterruptibly() 如果当前线程未被中断,则获取锁。
ConditionnewCondition() 返回绑定到此 Lock 实例的新 Condition 实例。
booleantryLock() 仅在调用时锁为空闲状态才获取该锁。
boolean[tryLock](…/…/…/…/java/util/concurrent/locks/Lock.html#tryLock(long, java.util.concurrent.TimeUnit))(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
voidunlock() 释放锁。

lock()获取锁unlock()释放锁使用步骤:3步

  1. 在成员位置创建一个ReentrantLock对象
  2. 在可能出现线程安全问题的代码前面调用lock()方法获取锁
  3. 在可能出现线程安全问题的代码后面调用unlock()方法释放锁

代码如下:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private int ticket = 100;

    //在成员位置创建一个`ReentrantLock`对象
    Lock lock = new ReentrantLock();


    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            //在可能出现线程安全问题的代码前面调用`lock()`方法获取锁
            lock.lock();
            //先判断电影票是否存在
            if (ticket>0){

                //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //有票,(票递减)===>ticket--
                System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
                ticket--;
            }
            //在可能出现线程安全问题的代码后面调用`unlock()`方法释放锁
            lock.unlock();
        }
    }
}

结果略。

上述代码提高效率写法:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现多线程卖票案例
 */
public class MovieTicket implements Runnable{
    //首先定义一个共享票源
    private int ticket = 100;

    //在成员位置创建一个`ReentrantLock`对象
    Lock lock = new ReentrantLock();


    //设置线程任务:卖票
    @Override
    public void run() {
        //使用死循环让卖票操作一直重复
        while (true){
            //在可能出现线程安全问题的代码前面调用`lock()`方法获取锁
            lock.lock();
            //先判断电影票是否存在
            if (ticket>0){

                //我在这里为了使程序在实验的票数较少的情况下,出现重复卖票的几率增加,特意添加一个睡眠语句
                try {
                    Thread.sleep(100);
                    //有票,(票递减)===>ticket--
                    System.out.println(Thread.currentThread().getName()+"号窗口正在卖第"+ticket+"张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //在可能出现线程安全问题的代码后面调用`unlock()`方法释放锁
                    lock.unlock();//这样写,无论程序是否异常都会释放锁,提高程序效率
                }
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于Java面试题太多背不完的问题,以下是一些建议和解决方法: 1. 理解核心概念:首先,要确保对Java的核心概念有深入的理解,包括面向对象编程、多线程、异常处理、集合框架等。这些概念是Java面试中经常涉及的重要知识点。 2. 重点关注常见问题:了解常见的Java面试题,特别是那些经常被问到的问题。这些问题通常涉及Java基础知识、常用类和库、设计模式等。可以通过查阅面试题集合、参考书籍或在线资源来获取这些问题。 3. 制定学习计划:根据自己的时间和能力,制定一个合理的学习计划。可以将面试题分为不同的主题,每天或每周专注于一个主题进行学习。这样可以有针对性地提高自己的知识水平。 4. 实践和编码:通过实践和编码来加深对Java知识的理解和记忆。可以尝试解决一些编程问题,参与开源项目或者自己动手实现一些小项目。这样可以将理论知识应用到实际中,加深记忆。 5. 参加模拟面试:参加模拟面试可以帮助你熟悉面试的流程和问题类型,并提供反馈和建议。可以找一些朋友或者加入面试准备的群组,进行模拟面试的练习。 6. 多做笔记和总结:在学习过程中,多做笔记和总结是很重要的。可以将重要的知识点、面试题和解答记录下来,方便日后复习和回顾。 总之,Java面试题太多背不完是一个常见的问题,但通过合理的学习方法和实践,可以提高自己的面试准备水平。记住,重要的是理解核心概念和掌握常见问题,而不是仅仅死记硬背。祝你面试顺利!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牛牛ō^ō

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值