java并发编程基础

img

线程的创建和运行

Java创建线程的方式有三种:实现Runnable接口的run方法,集成Thread类并重写run方法,使用FutureTask方式。

  1. 继承Thread类
public class Demo1
{
    public static class MyThread extends Thread
    {
        @Override
        public void run()
        {
            System.out.println(getName());
        }
    }
    public static void main(String[] args)
    {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

调用start方法后线程并没有马上执行而是处于就绪状态,这个就绪状态是指该线程已经获取了出CPU资源外的其他资源,等待获取CPU资源后才会真正处于运行状态。一旦run方法执行完毕,该线程就处于终止状态。

继承Thread类,可以直接使用this获取当前线程对象,而实现Runnable接口只能通过 Thread.currentThread()获取当前线程。

  1. 实现Runnable接口方式
public class Demo1
{
    public static class RunnableTask implements Runnable
    {
        @Override
        public void run()
        {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args)
    {
        new Thread(new RunnableTask()).start();
        new Thread(new RunnableTask()).start();
    }
}

上面代码所示,两个线程公用一个task代码逻辑,如果需要,可以给RunableTask添加参数进行任务分区。另外,RunableTask类可以继承其他类。但是他们都有一个缺点,就是任务没有返回值。

  1. 使用FutureTask方式
    new Thead(new FutureTask<>(new Runable(){}));
public class CallerTask implements Callable<String>
{
    @Override
    public String call()
        throws Exception
    {
        return "hello";
    }
    public static void main(String[] args)
    {
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        new Thread(futureTask).start();

        try
        {
            String str = futureTask.get();
            System.out.println(str);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }

    }
}

线程通知和等待

Object类中定义的线程相关方法:

  1. wait()方法

当一个线程调用了一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几个事情之一才可能返回:
(1)其他线程调用了该共享对象的notify()或者notifyAll()方法;
(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
如果调用wait()方法的线程事先没有获取该对象的监视器锁,则调用wait()方法时线程会抛出IllegalMonitorStateException异常。

获取共享对象的监视器锁:

  1. 执行synchronized同步代码块时,使用该共享对象作为参数:
synchronized(obj) {
  // do something
}
  1. 调用共享对象的方法,并且该方式使用synchrozied修饰:
synchronized void add(int a, int b) {

}

注意,一个线程可以从挂起状态变成可运行状态(也就是被唤醒),即使该线程没有被其他线程调用notify()、notifyAll()方法进行通知,或者被终端,或者等待超时,这就是所谓的虚假唤醒。
虚假唤醒在实际应用中很少发生,但要防患于未然,做法就是不停地测试该线程是否满足唤醒的条件。

如:

synchronized (obj) {
  while(条件不满足) {
    wait();
  }
}

另外需要注意的是,当前线程调用了共享变量的wait()方法后,只会释放当前共享对象上的锁,如果当前线程还持有其他共享对象的锁,这些锁是不会释放的。
如:

private static volatile Object resouceA = new Object();
private static volatile Object resouceB = new Object();

public static void main(String[] args) {
  Thread threadA = new Thread(new Runnable() {
    public void run() {
      synchrozied (recourseA) {
        synchronized (resouceB) {
          resourceA.wait();  // 只会释放recourseA
        }
      }
    }
  });
}
  1. obj.wait(long timeout)函数
    释放共享对象obj的锁,当前线程阻塞,timeout后自动返回。

  2. notify()函数
    一个线程调用共享对象的notify()方法后,会唤醒一个该共享对象上调用wait系列方法后被挂起的线程。一个共享对象可能有多个线程在等待,具体唤醒哪个线程是随机的。

  3. notifyAll()函数
    唤醒共享对象所有因wait()方法被挂起的线程

interrupt

interrput只是给线程加个终端标记,当线程因为调用sleep、wait、join方法处于等待状态,会检测是否有中断标志,如果有,则会抛出异常(InterrputException).不一定是当前线程

interrupted(),检测当前线程的中断状态,如果是中断,则清除中断状态。

    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }

让出CPU执行权的yield方法

/**
     * A hint(暗示) to the scheduler(调度) that the current thread is willing to yield(停止、让路)
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
public static native void yield();

线程中断

线程中断:中断线程的阻塞

  • void interrput()方法:给线程设置中断标志。如果线程已经调用了wait()、join()、sleep()方法而进入阻塞状态,会抛出InterruptException异常。
Thread thread = new Thread(new Runnable()
{
    @Override
    public void run()
    {
        try
        {
            Thread.sleep(5000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().isInterrupted());  // false
    }
});
//        thread.setDaemon(true);
thread.start();
thread.interrupt();
System.out.println("end...");

  • isInterrupt()方法:获取中断状态
  • Thread.interrupted() :获取当前线程的中断状态,如果为true,则清除中断状态。
Thread thread = new Thread(new Runnable()
{
    @Override
    public void run()
    {
        try
        {
            Thread.interrupted();  // 清除中断状态
            Thread.sleep(5000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().isInterrupted());
    }
});
thread.start();
thread.interrupt();
System.out.println("end...");

使用isInterrupt可以让阻塞的线程继续执行。

线程死锁

现场死锁条件:

  • 互斥条件
  • 请求并持有条件
  • 不可剥夺条件
  • 环路等待条件
Thread th1 = new Thread(new Runnable()
{
    @Override
    public void run()
    {
        synchronized (resourceA)
        {
            System.out.println(Thread.currentThread() + " geted A");
            try
            {
                Thread.sleep(3000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("waiting get B...");
            synchronized (resourceB)
            {
                System.out.println(Thread.currentThread() + " geted B");
            }
        }
    }
});

Thread th2 = new Thread(new Runnable()
{
    @Override
    public void run()
    {
        synchronized (resourceB)
        {
            System.out.println(Thread.currentThread() + " geted B");
            try
            {
                Thread.sleep(3000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            System.out.println("waiting get A...");
            synchronized (resourceA)
            {
                System.out.println(Thread.currentThread() + " geted A");
            }
        }
    }
});
th1.start();
th2.start();

如何避免死锁:资源有序申请

守护线程和用户线程

java中线程分为两类:daemon(守护)/user(用户)线程。

JVM启动时会调用main函数,main函数所在线程就是一个用户线程。

JVM守护线程:垃圾回收线程

守护线程:就是守护用户线程、起到辅助作用,用户线程结束了,守护线程就没有存在的意义了。

// 守护线程就是一个标识
th.setDaemon(true);
int JNICALL
JavaMain(void * _args)
{
  // ...
  // 执行Java中的main函数
  (*env) -> CallStaticVoidMethod(env, mainClass, mainId, mainArgs);
  
  // main函数返回值
  ret = (*env) -> ExceptionOccurred(env) == NULL ? 0: 1;
  
  // 等待所有非守护线程结束,然后销毁JVM进程
  LEAVE();
}

ThreadLocal

保证线程安全。


JDK提供的,提供了线程本地变量,每个线程访问这边变量都会创建一个本地副本。当多个线程操作这个变量时,实际操作的时自己本地内存的变量。

# 每个线程的本地变量 存在放在调用线程的threadLocals变量里面

// ThreadLocal的get方法
public T get() {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
    }
  }
  return setInitialValue();
}
// 因此,每个线程的本地变量的key就是ThreadLocal对象。
// 每个线程都有一个map变量,key为ThreadLocal对象实例

InheritableThreadLocal类

子线程访问父线程的本地变量

# Thread.threadLocals只供当前线程使用
# Thread.inheritableThreadLocals可供当前线程、子线程使用

什么是多线程并发编程

  • 并发:一段时间内多个任务同时执行

  • 并行:单个cpu时间片内同时执行

多线程并发可提高CPU利用率。


Java中线程安全问题

资源共享:多个线程都可以访问同一资源。

线程安全问题是指当多个线程同时读写一个共享资源并且没有任何同步措施,导致出现脏数据或者其他不可预见的问题,如下图:


Java内存模型规定,所有的变量都存放在主内存中,当线程使用变量时,会把主内存的变量复制到自己的工作空间或者叫工作内存,线程读写变量时操作的是自己的内存的变量,处理完之后更新到主内存中。


线程安全

POST /fdFile/upload
{
  file: File, 
  relativedId:'',
  strRelativedType: 'group', // 文件类型(文件分组)
  permision: 7,  // 文件权限
}
posted @ 2021-04-15 18:54  fight139  阅读(70)  评论(0编辑  收藏  举报