Java多线程

Java虚拟机允许应用程序并发地运行多个执行线程,它们会交替运行,彼此之间可以进行通信。

每个线程都有一个优先级,这样有助于系统确定线程的调度顺序。

多线程并发执行可以提高程序效率,更加充分的使用CPU。

并行和并发

  • 并行:就是同时执行,比如你边看电视边吃薯片。(需要多核CPU来实现)
  • 并发:就是交替执行,比如你看会电视,然后吃会薯片,再看会电视,再吃会薯片......

​ 指的是两个任务都在请求运行,但是处理器只能执行一个任务,就把这两个任务安排轮流执行,

​ 由于时间间隔较短,使人感觉两个任务都在运行。

1.1 创建新线程的两种方式:

1.继承Thread 类重写run() 方法;

class MyThread extends Thread{
    @Override
    public void run() {
        // To do anything you want.
    }
}

调用:

  MyThread thread = new MyThread();
  thread.start();

2.实现Runnable接口重写run()方法;

 class MyRunnable implements Runnable{
        @Override
        public void run() {
            // To do anything you want. 
        }
    }

调用:

     MyRunnable runnable=new MyRunnable();
     new Thread(runnable).start();

注意最后都是通过 Start() 方法来开启线程。

1.2 两种方式区别:

继承Thread:

好处:可以直接使用Thread中方法,代码简洁。

弊端:如果已经有了父类,则不能使用这种方式。

实现Runnable接口:

好处:当线程类中已经有了父类时可以使用。

弊端:不能直接使用Thread中的方法,需要先获取到线程对象,才能使用,代码复杂。

使用时需优先考虑Thread

2. Thread 常用方法:

方法名描述
currentThread()返回当前执行的线程对象
getName()返回该线程的名称
setName(String name)设置线程名称
sleep(long millis)使线程休眠(暂停执行),参数为毫秒
start()使线程开始执行;Java虚拟机调用该线程的run()方法
setPriority(int priority)更改线程优先级
setDaemon(bolean on)将该线程标记为守护线程或用户线程
isDaemon()判断线程是为守护线程
isAlive()判断当前线程是否为活动状态
join()让主线程等待子线程结束后再继续运行(会阻塞主线程)
yield()暂停当前正在执行的线程对象,并执行其他线程

3.守护线程

Java线程中有两种线程: User Thread(用户线程), Daemon Thread(守护线程,也叫后台线程)

新创建的线程默认都是前台线程,只有当创建线程为守护线程时,新线程才是守护线程。

当所有用户线程都退出后,守护线程也会退出。(举个例子来说的话就是:当你关闭浏览器后,所有的网页都会关闭)

可以通过 setDaemon(true) 改变线程为守护线程,注意该方法必须在start()方法之前调用。

4.线程的优先级

每个Java线程都有有一个优先级,默认优先级是NORM_PRIORITY(5)。

优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程。

设置优先级有助于操作系统确定线程的调度顺序,但是不能保证线程执行的顺序,也就是说高优先级的线程不一定先执行完。

当某个线程中创建一个新的Thread对象,该新线程的初始优先级被设定为创建线程的优先级。

JDK预定义优先值为:

    public final static int MIN_PRIORITY = 1;  //The minimum priority that a thread can have.
    public final static int NORM_PRIORITY = 5; //The default priority that is assigned to a thread.
    public final static int MAX_PRIORITY = 10; //The maximum priority that a thread can have.

设置优先级使用setPriority()方法,该方法源码为:

 public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            synchronized(this) {
                this.priority = newPriority;
                if (isAlive()) {
                    nativeSetPriority(newPriority);
                }
            }
        }
    }

根据源码我们可以得出,优先级取值范围为1~10,如果小于1或者大于10则将抛出异常。

5.线程同步

多线程开发时,当有多段代码需要执行,我们希望某一段代码执行的过程中CPU不要切换到其他线程

这就需要用到同步代码块,这样的话只有当同步代码块执行结束后才会执行其它代码。

通过关键字 synchronized 来实现 ,格式:

  //任意对象都可以当做锁对象
  //注意:匿名对象不能当做锁对象,因为不能保证两个锁对象是同一个对象
  synchronized(lock){    //获取锁
   //需要同步的代码块;
  }  //释放锁

除了代码块可以同步外,方法也是可以同步的。

声明格式 :

  synchronized void 方法名称(){}

同步方法虽然没有声明锁对象,但是它也是有锁对象的:

非静态同步方法:this 静态同步方法:类名.class (就是当前类的字节码对象)

锁对象,它是同步代码块的关键。当线程执行同步代码块时,首先会检查锁对象的标志位,默认标志位为1,此时线程会执行同步代码块,同时将锁对象标志位置为0。当一个新的线程执行到这段代码块是,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后,锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码。循环往复,知道共享资源被处理完位为止。这个过程就好比一个公用电话亭,只有前一个人都打完电话出来后,后面的人才可以打。

注意:同步代码块中锁对象可以使任意类型的对象,但是多个线程共享的锁对象必须是唯一的。

6.死锁

死锁就是两个或多个线程被永久阻塞是的一种情形。

假设这样一场景,小明去买冰棍:

售货员:“你先给我钱,我再给你冰棍”

小明:“你先给我冰棍,我再给你钱”

最终的结果就是小明买不到冰棍,售货员也得不到钱。

在这里钱跟冰棍就是锁,两个线程在运行时都在等待对方的锁,这样就造成的阻塞,这种现象称为死锁。

接下来通过代码来模拟:

class DeadLockThread implements Runnable {

    static Object ice = new Object(); // 锁对象:冰棍
    static Object money = new Object(); // 锁对象:钱

    private boolean flag; // true:小明 false:售货员

    public DeadLockThread(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {  //小明
            while (true) {
                synchronized (money) {
                        System.out.println("小明: 给我冰棍");
                    synchronized (ice) {
                        System.out.println("售货员: 给你冰棍");
                    }
                }
            }

        } else {  //售货员
            while (true) {
                synchronized (ice) {
                        System.out.println("售货员: 给我钱");
                    synchronized (money) {
                        System.out.println("小明: 给你钱");
                    }
                }
            }
        }
    }
}

创建两个线程:

        DeadLockThread t1=new DeadLockThread(true); //小明
        DeadLockThread t2=new DeadLockThread(false); //售货员
        
        //创建并开启两个线程
        new Thread(t1).start();
        new Thread(t2).start();

输出结果:

小明: 给我冰棍
售货员: 给我钱

最终小明锁着”钱”,售货员锁着”冰棍”,小明得不到冰棍,售货员得不到钱。

在开发中我们是尽量死锁的,方式之一就是尽量不要使用同步代码块的嵌套。

7.单例设计模式

目的:保证类在内存当中只有一个对象。

两种写法:

//饿汉式
class Singleton{
   //1.私有构造函数
   private Singleton() {}
   //2.创建本类对象
   private static Singleton s=new Singleton();
   //3.对外提供公共的访问方法
   public static Singleton getInstance(){
     return s;
   }
 }
 //懒汉式
 class Singleton{
    //1.私有构造函数
   private Singleton() {}
   //2.创建本类对象
   private static Singleton s;
    //3.对外提供公共的访问方法
   public static Singleton getInstance(){
     if(s==null)
       s=new Singleton();
     return s;
   }
 }
Last modification:July 12th, 2020 at 07:20 pm