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

lyly4413 2019-02-24 17:36:59 33580 收藏 107
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/lyly4413/article/details/87866726
收起
java中的线程:java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程。一旦创建一个新的线程,就会产生一个线程栈。线程总体分为:用户线程和守护线程,当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

线程的生命周期:当一个线程被创建之后,进入新建状态,JVM则给他分配内存空间,并进行初始化操作。当线程对象调用了start()方法,该线程就处于就绪状态(可执行状态),JVM会为其创建方法调用栈、和程序计数器,处于可执行状态下的线程随时可以被cpu调度执行。CPU执行该线程的时候,该线程进入执行状态。执行过程中,该线程遇倒像wait()等待阻塞、以及synchronized锁同步阻塞或者调用线程的sleep()方法等进入一个阻塞状态,阻塞之后通过notify()或者notifyAll()方法唤醒重新获取对象锁之后再行进入就绪状态,等待cpu执行进去执行状态、当线程执行完或者return则线程正常结束,如果发生处理的运行时异常,则线程因为异常而结束。这是一个线程的整个运行的生命周期。如下图所示:

https://images0.cnblogs.com/blog/497634/201312/18152411-a974ea82ebc04e72bd874c3921f8bfec.jpg

线程的几种实现:

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值