sincerit java基础之多线程

线程状态
在这里插入图片描述
Java线程具有五中基本状态

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期

Runnable状态是线程获得cpu资源的唯一入口,绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;所以获得synchronized之后是先到就绪状态再到执行状态

如何使用多线程
第一个是自己定义一个类MyThread在继承Thread,再重写run方法

扫描二维码关注公众号,回复: 4295014 查看本文章
class MyThread extends Thread {
    private int i = 0;
    @Override
    public void run() {
        // TODO Something
    }
}

第二个是实现Runnable接口,并重写该接口的run()方法

class MyRunnable implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        // TODO something
    }
}

再创建一个线程对象,启动线程到就绪状态

1. MyThread myThread = new MyThread();
2. myThread.start();
 
1. MyRunnable myRunnable = new MyRunnable();
2. myRunnable.strat();

或者在主函数直接创建Thread对象实现重写run()在启动线程  
new Thread(new Runnable(){
  public void run() {
    // TODO something 
  }
}).start();

使用多线程就不得不了解线程同步的概念

什么是线程同步
线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。A,B相互协调完成任务

为何要使用线程同步机制(synchronized)
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 多个线程读或者写相同的数据等情况时可能会导致数据不一致。为了解决这些问题,引入了synchronized同步方法

使用synchronized实现同步方法背后的原理:
即有synchronized关键字修饰的方法,这个方法肯定是某一个类里面的,要用对象调用
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

所以要使用公共资源变量去操作数据时一定要先拿到内置锁,否则只能等待其他线程执行完释放锁才能去获取锁
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类

synchronized可以有三种加锁的方式
成员方法代码块上加锁

public void method() {
  synchronized(this) { // this 指的是这个对象
    // TODO 代码块
  }
}

成员方法上加锁

public synchronized void method() {
     // TODO 代码块
  }

静态方法加锁

public class SynchronizedTest {
  // 取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
      public static synchronized void method1(){ 
          System.out.println("Method 1 start");
          try {
              System.out.println("Method 1 execute");
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Method 1 end");
      }
 
      public static synchronized void method2(){
          System.out.println("Method 2 start");
          try {
              System.out.println("Method 2 execute");
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Method 2 end");
      }
 
      public static void main(String[] args) {
          final SynchronizedTest test = new SynchronizedTest(); 
          final SynchronizedTest test2 = new SynchronizedTest();
 
          new Thread(new Runnable() {
              @Override
              public void run() {
                  test.method1();   // 直接调用
              }
          }).start();
 
          new Thread(new Runnable() {
              @Override
              public void run() {
                  test2.method2();
              }
          }).start();
      }
  }

使用synchronized的缺陷
出自该片: https://www.cnblogs.com/dolphin0520/p/3923167.html

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢(lock是一个接口)?

我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:

1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

2)线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。

因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:

如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。

另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。

总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:

1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;

2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

Lock的用法
看源码

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

常用的是lock() 和 unlock()
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待.
如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:

Lock lock = new ReentrantLock(); // ReentrantLock(重入锁) 以可以响应中断的方式加锁
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //要手动释放锁
}

猜你喜欢

转载自blog.csdn.net/sincerit/article/details/84629943