天天看点

关于Java多线程的一些内容及synchronized的用法

  Java多线程是Java的一个重要特性,今天没事总结一下,当然只是一个简单总结,毕竟要是多线程真正写起来一篇是远远不够的。

  

  创建多线程的两种方式

  

  先说比较简单的,在Java中实现多线程一般有四种方式,但是常用的就是两种,一种是继承Thread类,重写run方法,另外一种就是实现Runnable接口,实现run方法,之后创建一个线程类,将实现Runnable接口的类作为线程类的参数传递进去。直接上两种方式的代码见下图

  

关于Java多线程的一些内容及synchronized的用法

  

关于Java多线程的一些内容及synchronized的用法

  这里代码很简单,就不做什么解释了,唯一要解释的几个点就是如果在main方法里面调用Thread.currentThread().getName()得到的是主线程的名字,主线程默认的名字是main。启动线程的方式是调用start方法而不是run方法,run方法是线程方法的内容。当start方法调用后线程处于就绪状态,之后分配到时间片就会启动线程。

  线程的生命周期

  

  接下来总结一下线程的生命周期,关于线程的生命周期有很多种不同的说法,其中比较流行的一种是线程大致可以分为五个状态,新建状态,就绪状态,运行状态,阻塞状态以及死亡状态。每种状态有不同的说法,比如就绪状态可能被叫做等待状态,阻塞状态可能被叫做挂起状态等等

  

  当创建一个新的线程对象,那么此时线程处于新建状态,当调用线程的start方法,该线程处于就绪状态,此时线程并不会运行,如果cpu时间片分配到线程,那么该线程就会处于运行状态,在线程的就绪状态可以调用线程的多个set方法来设置线程的各种属性,常见的用法是调用set来设置线程的优先级priority,线程的名字,线程的属性(是否是守护线程),所谓守护线程就是后台线程

  当线程调用sleep方法就会进入休眠状态,所谓休眠状态就是阻塞状态的一种,当调用该方法的时候就抛出一个异常InterruptedException,但是因为在线程的run方法中不能用throws关键字,所以用try catch进行捕获。当线程处于休眠状态调用

interrupt方法会中断休眠抛出异常

  当线程执行完了run方法或者因为某种特殊情况结束了run方法,线程就结束处于死亡状态。

  

  关于终止线程的方法有三种:第一种是使用while循环,默认循环判断条件为true,当执行到某个临界值想要退出的时候设置一个boolean值,将其值设为false,此时无法进行循环,跳出run方法,线程终止。第二种方法是调用stop方法,但是这种方法在Java早期的版本中存在,基本不推荐使用,因为不知道可能会发生什么意想不到的情况。还有一种·方式是调用interrupt方法来中断线程。

  关于线程join方法的使用

  正常情况下Java的多个线程是处于异步执行状态,即多个线程交替执行,不知道具体的运行顺序,当一个线程调用start方法后如果不想和其他线程交替运行,那么你可以调用线程的join方法,这个方法的作用是使调用start方法的线程优先执行完,其他的线程必须处于阻塞状态,只有该线程执行完以后其他线程才有机会调用。实际场景就是如果线程b需要线程a运行完的值做进一步的运算,那么线程a调用start方法后调用join方法让线程a处于最高优先级即可。好了直接上代码比较直观,见下图

  

关于Java多线程的一些内容及synchronized的用法

  执行这段代码,由于join方法的存在,子线程t1会先执行100次,之后才是主线程执行50次,如果没有join方法,执行的结果是子线程与主线程交替进行。这就是join方法的作用

  关于synchronized关键字的用法

  好了,接下来可以总结下synchronized的用法了,多线程固然有其优势的地方,但是当多线程共享一段数据时由于默认是异步操作,可能一个线程执行了一部分操作共享数据的语句就被另外一个线程去执行了,这样很容易造成共享数据出错。因为线程的本质就是多条语句的一个集合。当然这样很容易想到解决办法,就是让操作共享数据的语句在一个线程内执行完,让线程的异步操作变为同步操作,也就是多个线程是排队进行操作,一段时间内只有一个线程其他线程处于阻塞状态。这就需要用到synchronized关键字了

  synchronized可以修饰的东西有很多,比如可以修饰类,普通方法,静态方法,常量,代码块等。

  一个比较常用到需要同步操作的地方就是卖票,假设一共有四个窗口卖400张票,那么根据实际情况你很容易想到在同一时刻一张票只能被其中的一个窗口卖而不能同时被多个窗口卖,同时一张票只有在被四个窗口中的任意一个窗口卖掉以后,其他的窗口才能在剩余票的基础上卖下一张票。也就是说只有一个线程执行,而其他三个线程在看戏,解决办法就是用synchronized进行同步。

  直接上代码看,见下图

  

关于Java多线程的一些内容及synchronized的用法

  简单解释一下就是创建四个线程模拟四个窗口,他们共用的是由t创建的线程,只不过四个线程起了不同的名字,在run方法内用synchronized锁住某个对象,修饰的就是synchronized下面包住的这段代码。由于每个对象都有一把锁,所以当一个线程比如t1执行run方法碰到synchronized关键字,就获得了对象的锁,这里是获得了a的锁,这样线程t1就获得了执行卖票代码的独家控制权,其他三个线程此时只能处于阻塞状态,当t1的cpu时间片运行完后释放锁,之后四个线程继续竞争锁。这里a可以替换成一个普通对象o,或者是this,Ticket2.class,效果是一样的,只要确保四个线程竞争的是同一个对象的锁即可,其中this表示的是当前对象,其实就是t。但是如果在while(true)内部创建一个普通的Object对象,之后synchronized锁住这个对象是不起作用的,因为这样每个线程执行到run方法实际是相当于创建了四个不同的对象,彼此之间互不干扰。

  用synchronized修饰普通方法,那么这个方法就变为同步方法,当然还有修饰代码块使其变为同步代码块。当一个线程访问对象的同步方法时,该对象的所有同步方法都会被阻塞,其他线程此时无法访问该对象的任意一个同步方法,但是可以访问该对象的非同步方法,见代码如下

  

public class Test7 {
   public static void main(String[] args) {
      Thread7 t=new Thread7();
      Thread a=new Thread(t, "线程1号");
      Thread b=new Thread(t,"线程2号");
      a.start();
      b.start();
}
}
class Thread7 implements Runnable{
    private int number;
    private int number2;

    public Thread7() {
        number=;
        number2=;
    }
    public void add(){
        synchronized (this) {
            for(int i=;i<;i++){
                System.out.println(Thread.currentThread().getName()+":"+(number++));
                try {
                    Thread.sleep();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    //只是一个输出操作,没有进行写,所以不用同步
    public void print(){
        for(int i=;i<;i++){
            System.out.println(Thread.currentThread().getName()+"number:"+number2);
            try {
                Thread.sleep();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    @Override
    public void run() {
        String name=Thread.currentThread().getName();//获取当前线程的名字
        if(name.equals("线程1号")){
            add();
        }else if(name.equals("线程2号")){
            print();
        }
    }

}
           

  执行上面这段代码,你会发现两个线程交替进行,这就验证了之前的说法,新的线程是可以访问非同步方法的.

  

  用synchronized修饰静态方法,因为静态方法属于类而不属于某个特定对象,因此一个线程一旦访问某个同步静态方法,就获得了这个对象的所属类的锁,此时其他线程无法同时访问该类下任意一个对象的同步静态方法,直接看代码见下图

  

关于Java多线程的一些内容及synchronized的用法

  

  这里虽然t1和t2属于两个不同的对象,但是由于同步静态方法的存在,使得两个线程a和b处于同步状态,即只有其中一个线程执行完以后另外一个线程才能执行,这就是同步静态方法起到的作用。

  将一个方法修饰为同步方法有两种形式,一种是在方法名前加synchronized关键字,另外一种是在方法内部的第一行加上synchronized(this),两者起到的效果是一样的,不过后者的细粒度更高。 下面两种写法是等价的

  

关于Java多线程的一些内容及synchronized的用法

  基本上关于synchronized的用法就上面这些,最后再补充一下,synchronized关键字是不能被继承的,也就是说如果一个类的某个方法是同步方法,那么子类继承该类后这个方法并不是同步的,只是一个普通的方法,如果想要让其变为同步方法那么必须显示声明一下。