JAVA多线程下高并发的处理经验

java中的线程:java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程。一旦创建一个新的线程,就会产生一个线程栈。线程总体分为:用户线程和守护线程,当所有用户线程执行完毕的时候,JVM自动关闭。
但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。 线程的生命周期:当一个线程被创建之后,进入新建状态,JVM则给他分配内存空间,并进行初始化操作。当线程对象调用了start()方法,该线程就处于就绪状态(可执行状态),JVM会为其创建方法调用栈、和程序计数器,处于可执行状态下的线程随时可以被cpu调度执行。
CPU执行该线程的时候,该线程进入执行状态。执行过程中,该线程遇倒像wait()等待阻塞、以及synchronized锁同步阻塞或者调用线程的sleep()方法等进入一个阻塞状态,阻塞之后通过notify()或者notifyAll()方法唤醒重新获取对象锁之后再行进入就绪状态,
等待cpu执行进去执行状态、当线程执行完或者return则线程正常结束,如果发生处理的运行时异常,则线程因为异常而结束。这是一个线程的整个运行的生命周期。如下图所示:

 

 


线程的几种实现:

1、继承Thread类,重写该类的run方法

class MyThread extends Thread {
    
    private int i = 0;
 
    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
 
    public static void main(String[] args) {
        Thread myThread1 = new MyThread(); // 创建一个新的线程  myThread1  此线程进入新建状态
        myThread1 .start();  // 调用start()方法使得线程进入可执行状态
    }
}
2、实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象

class MyRunnable implements Runnable {
    private int i = 0;
 
    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
 
    public static void main(String[] args) {
        Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
 
        Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
               
        thread1.start(); // 调用start()方法使得线程进入就绪状态
              
        }
    }
}
3、使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现clall()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程

public class ThreadTest {
 
    public static void main(String[] args) {
 
        Callable<Integer> myCallable = new MyCallable();    // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
 
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft);   //FutureTask对象作为Thread对象的target创建新的线程
                thread.start();                      //线程进入到就绪状态
            }
        }
 
        System.out.println("主线程for循环执行完毕..");
        
        try {
            int sum = ft.get();            //取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
 
    }
}
 
 
class MyCallable implements Callable<Integer> {
    private int i = 0;
 
    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }
 
}
继承Thread和实现Runnable接口实现多线程的区别:

继承Thread类、实现Runnable接口,在程序开发中只要是多线程,肯定永远以实现Runnable接口为主,因为实现Runnable接口相比继承Thread类有如下优势:

​ 1、可以避免由于Java的单继承特性而带来的局限;

​ 2、增强程序的健壮性,代码能够被多个线程共享,代码与数据是独立的;

​ 3、适合多个相同程序代码的线程区处理同一资源的情况。

线程的优先级别:

java线程可以通过setPriority()方法对其设定一个优先级别,高优先级别的线程比低优先级别的线程有更高的几率得到先执行,优先级可以用0到10的整数表示,0为最低优先级别、10为最高优先级别。当线程调度器决定那个线程需要调度时,会根据这个优先级进行调度选择;1)Thread类有三个优先级静态常量:MAX_PRIORITY为10,为线程最高优先级;MIN_PRIORITY取值为1,为线程最低优先级;NORM_PRIORITY取值为5,为线程中间位置的优先级。默认情况下,线程的优先级为NORM_PRIORITY。2)一般来说优先级别高的线程先获取cpu资源先运行,但特殊情况下由于现在的计算器都是多核多线程的配置,有可能优先级低的线程先执行,具体的执行还是看JVM调度来决定。

几种线程同步的方法:

1、使用synchronized获取对象互斥锁:这种方式是最常用也比较安全的一种方式,采用synchronized修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。我们在使用同步的时候进来爸锁的粒度控制的精细一点,有时候没必要锁整个方法,只需要锁一个代码块即可达到我们的业务需求,这样避免其他线程阻塞时间过长造成性能上的影响。

package per.thread;
 
import java.io.IOException;
 
public class Test {
    
    private int i = 0;
    private Object object = new Object();
     
    public static void main(String[] args) throws IOException  {
        
        Test test = new Test();
        
        Test.MyThread thread1 = test.new MyThread();
        Test.MyThread thread2 = test.new MyThread();
        thread1.start();
        thread2.start();
    } 
     
     
    class MyThread extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                i++;
                System.out.println("i:"+i);
                try {
                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");
                    Thread.currentThread().sleep(10000);
                } catch (InterruptedException e) {
                    // TODO: handle exception
                }
                System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");
                i++;
                System.out.println("i:"+i);
            }
        }
    }
}
2、使用特殊域变量volatile实现线程同步:volatile修饰的变量是一种稍弱的同步机制,因为每个线程中的成员变量都会对这个对象的一个私有拷贝,每个线程获取的数据都是从私有拷贝内存中获取,而volatile修饰之后代表这个变量只能从共享内存中获取,禁止私有拷贝。在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比synchronized关键字更轻量级的同步机制。从内存的可见性上来看,写入volatile变量相当于退出同步代码块,而读取volatile变量相当于进入同步代码块。但代码中过度依赖于volatile变量来控制同步状态,往往比使用锁更加不安全,使用同步机制会更安全一些。当且仅当满足以下所有条件时,才应该使用volatile变量: 1、对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。 2、该变量没有包含在具有其他变量的不变式中。

 class Bank {
            //需要同步的变量加上volatile
            private volatile int account = 100;
 
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized 
            public void save(int money) {
                account += money;
            }
        }
3、使用重入锁Lock实现线程同步: 在jdk1.5以后java.util.concurrent.locks包下提供了这一种方式来实现同步访问。因为synchronized同步之后会存在一个阻塞的过程,如果这个阻塞的时间过久,严重影响我们代码的质量以及带来系统性能上的问题。因为我们需要一种机制,让等待的线程到达一定时间之后能够响应中断,这就是Lock的作用。另外lock还可以知道线程有没有成功获取到对象锁,synchronized无法做到。Lock比synchronized提供更多的功能。但要注意的是:1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

package com.dylan.thread.ch2.c04.task;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * This class simulates a print queue
 *
 */
public class PrintQueue {
 
    /**
     *  创建一个ReentrantLock实例 
     */
    private final Lock queueLock=new ReentrantLock();
    
    /**
     * Method that prints a document
     * @param document document to print
     */
    public void printJob(Object document){
        //获得锁 
        queueLock.lock();
        
        try {
            Long duration=(long)(Math.random()*10000);
            System.out.printf("%s: PrintQueue: Printing a Job during %d seconds\n",Thread.currentThread().getName(),(duration/1000));
            Thread.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放锁 
            queueLock.unlock();
        }
    }
}
 注:关于Lock对象和synchronized关键字的选择: 

        a.最好两个都不用,使用一种java.util.concurrent包提供的机制,能够帮助用户处理所有与锁相关的代码。 
        b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码 
        c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁 

4、使用ThreadLocal管理变量:如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响;ThreadLocal 类的常用方法:

ThreadLocal() : 创建一个线程本地变量 
get() : 返回此线程局部变量的当前线程副本中的值 
initialValue() : 返回此线程局部变量的当前线程的"初始值" 
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
  public class Bank{
            //使用ThreadLocal类管理共享变量account
            private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
                @Override
                protected Integer initialValue(){
                    return 100;
                }
            };
            public void save(int money){
                account.set(account.get()+money);
            }
            public int getAccount(){
                return account.get();
            }
        }
5、使用阻塞队列实现线程同步:自从Java 1.5之后,在java.util.concurrent包下提供了若干个阻塞队列,或Redis消息队列等来实现同步等等。。。

java多线程并发的业务场景:在互联网的大环境下很多场景都对并发要求越来越高,像天猫双十一秒杀、春运火车票抢票、微信抢红包、以及一些业务对某种资源的请求数量的控制、以及一些业务需要整个系统的输入输出顺序一致性等问题,这些都需要考虑到并发导致的数据安全性问题。

 

posted @ 2020-08-01 15:29  红尘沙漏  阅读(1024)  评论(0编辑  收藏  举报