多线程介绍
进程介绍
不同的应用程序运行的过程中都需要在内存中分配自己独立的运行空间,彼此之间不会相互的影响。我们把每个独立应用程序在内存的独立空间称为当前应用程序运行的一个进程。
进程:它是内存中的一段独立的空间,可以负责当前应用程序的运行。当前这个进程负责调度当前程序中的所有运行细节。
线程介绍
线程是CPU调度的基本单位,一个进程有一个或者多个线程。线程有自己的本地空间,为了加快程序的运行,每一个线程将进程中的变量复制一份存储在本地内存。
现在的操作系统基本都是多用户,多任务的操作系统。每个任务就是一个进程。而在这个进程中就会有线程。
真正可以完成程序运行和功能的实现靠的是进程中的线程。
多线程:在一个进程中,我们同时开启多个线程,让多个线程同时去完成某些任务(功能)。(比如后台服务系统,就可以用多个线程同时响应多个客户的请求)
多线程的目的:提高程序的运行效率。
多线程的原理
cpu在线程中做时间片的切换。
其实真正电脑中的程序的运行不是同时在运行的。CPU负责程序的运行,而CPU在运行程序的过程中某个时刻点上,它其实只能运行一个程序。而不是多个程序。而CPU它可以在多个程序之间进行高速的切换。而切换频率和速度太快,导致人的肉眼看不到。
每个程序就是进程,而每个进程中会有多个线程,而CPU是在这些线程之间进行切换。
了解了CPU对一个任务的执行过程,我们就必须知道,多线程可以提高程序的运行效率,但不能无限制的开线程。
实现线程的几种方式
1.实现Runnble接口,下面是实现代码package cn.edu.hust.threadMethod;
import javax.management.relation.RoleUnresolved;
import java.sql.Timestamp;
import java.util.Date;
/**
* 线程的几种方法
*
*
* init():创建一个线程是它的parent创建,复制了父类的优先级
* 加载资源、等,然后再分配线程ID
*
*interrupt():其他线程调用这个方法中断,如果线程还有sleep()方法
* 或者是线程已经终止,那么中断标识位
* 将会清除,返回false,否则返回true
*
*
* resume()、stop()、suspend()方法过期,不会释放资源
*/
class SleepThread implements Runnable
{
//线程不断的睡眠
@Override
public void run() {
while(true)
{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BusyThread implements Runnable
{
//线程不断的忙碌
@Override
public void run() {
while(true)
{
}
}
}
/**
* 安全终止线程的两种方式
* 可以使用中断也可以使用voltile变量
*/
class CountThread implements Runnable
{
private Long i=0L;
private volatile boolean flag=false;
@Override
public void run() {
while(!flag&&!Thread.currentThread().isInterrupted())
{
i++;
}
System.out.println(i);
}
public void cancel()
{
flag=true;
}
}
public class ThreadTest {
private static Object lock=new Object();
private static volatile boolean flag=false;
/**
*
*Wait 和 Notify方法来进行线程通信
*/
static class WaitThread implements Runnable
{
/**
* 使用wait()方法就会释放对象锁
*/
@Override
public void run() {
synchronized (lock)
{
while(!flag)
{
System.out.println(Thread.currentThread().getName()+" flag is false waiting at"
+ new Timestamp(new Date().getTime()).toString());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+" flag is false Running at"
+ new Timestamp(new Date().getTime()).toString());
}
}
}
static class NotifyThread implements Runnable
{
@Override
public void run() {
/**
* 使用notify方法进行消息通知,只有同步代码块进行结束后才释放锁
*/
synchronized (lock)
{
System.out.println(Thread.currentThread().getName()+" flag is true notify at"
+ new Timestamp(new Date().getTime()).toString());
lock.notify();
flag=true;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
/**
Thread t1=new Thread(new SleepThread(),"sleep-thread");
Thread t2=new Thread(new BusyThread(),"busy-thread");
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
Thread.sleep(1000);
t1.interrupt();
t2.interrupt();
System.out.println("the sleep thread status is "+t1.isInterrupted());
System.out.println("the busy thread status is "+t2.isInterrupted());
**/
/**
Thread t1=new Thread(new CountThread(),"countThread-1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
CountThread count=new CountThread();
Thread t2=new Thread(count,"countThread-2");
t2.start();
Thread.sleep(1000);
count.cancel();
**/
/**
* 使用wait和notify方法时需要加傻姑娘对象锁
*/
/**
Thread t1=new Thread(new WaitThread(),"WaitThread");
Thread t2=new Thread(new NotifyThread(),"NotifyThread");
t1.start();
t2.start();
**/
for(int i=0;i<10;i++)
{
Thread t=new Thread(new CountStaticThread(),"thread-"+i);
t.start();
t.join();
}
System.out.println(CountStaticThread.i);
}
}
class CountStaticThread implements Runnable
{
public static Long i=0L;
@Override
public void run() {
i++;
System.out.println(Thread.currentThread().getName()+" count i="+i);
}
}
2.继承Thread类,实现代码如下:
package cn.edu.hust.threadMethod;
public class ThreadExtends extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在运行");
}
public static void main(String[] args)
{
ThreadExtends t=new ThreadExtends();
t.start();
}
}
3.实现Callable接口,代码如下 :
package cn.edu.hust.threadMethod;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<Integer>
{
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=100;i++)
{
sum+=i;
}
return sum;
}
}
public class ThreadExtends extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"正在运行");
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadExtends t=new ThreadExtends();
t.start();
MyThread myThread=new MyThread();
FutureTask<Integer> task=new FutureTask<Integer>(myThread);
new Thread(task).start();
System.out.println(task.get());
}
}
Java多线程的关键字
volatile
volatile关键字,只能用来修饰变量。volatile关键字的特性是:能够保证所修饰变量的可见性,对于变量的赋值、读取等单一操作是线程安全的,对于复合型操作(例如volatile修饰的变量i的操作i++)则不是线程安全的。
那么什么叫做内存的可见性呢?对于volatile修饰的变量,如果一个线程修改之后,JMM(Java内存模型)将会将其他线程存储的该变量设置为无效状态,其他线程必须从主内存刷新该变量的值(注意,每一个线程拥有变量的拷贝,存储在各自的工作内存之间)。这样,就实现了内存的可见性。
下面我们来看一下代码,就能充分理解这个关键字了。
public class Test {
public volatile int inc = 0;
public void increase() {
inc++;
}
public static void main(String[] args) {
final Test test = new Test();
for(int i=0;i<10;i++){
new Thread(){
public void run() {
for(int j=0;j<1000;j++)
test.increase();
};
}.start();
}
while(Thread.activeCount()>1) //保证前面的线程都执行完
Thread.yield();
System.out.println(test.inc);
}
}
这个结果每一次运行都会变化。
2.synchronized
被synchron关键字修饰,是线程安全的。synchronized实现线程同步的基石是:对象即锁;也就是对象都可以作为锁。主要分为3个方面:
1.修饰普通方法时,实例对象作为锁。
2.修饰静态方法时,该类的Class对象作为锁。
3.修饰的同步代码块,括弧内配置的对象。
实现synchronized修饰的代码块的原理是:基于Monitorenter和Monitorexit进行实现的。
对于synchronized关键字需要注意的是
1.synchronized修饰的代码块内部出现异常的时候,会释放锁。
2.在synchronized修饰的代码块内,不会响应中断
3.Lock
不同于volatile和synchronized等关键字,Lock是Java中一个接口,实现锁的底层是CAS操作,CAS也就是compareAndSwap方法,这是一个乐观锁思想实现的,对于JUC包的原子类,CAS的操作时先进行操作,然后和预期的值进行对比,如果不等于,那么证明有其他线程修改了这个变量,所以需要自旋(也就是死循环),然后知道操作成功。需要注意的是,CAS操作,底层调用的是操作系统的本地方法。
下面,让我们来看一段JUC原子类的CAS实现,后充分理解CAS思想
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
可以看出这里死循环,如果CAS操作没有成功,就一直进行CPU自旋。
对于Lock的实现类,内部的机制是每一个锁实现自己的AQS,同步队列器来进行获取锁和释放锁的操作。AQS内部使用的模版方法,内部提供了一些方法给开发者重写,可以看看下面的代码。
我们可以是实现自己的AQS来实现自己的独占锁。
package cn.edu.hust.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock
{
private static class MyAQS extends AbstractQueuedSynchronizer
{
//独占式获取同步状态,查询是否符合预期,然后使用CAS更新状态
@Override
protected boolean tryAcquire(int arg) {
//如果预期的值为0表示独占锁可以被获取,更新状态
if(compareAndSetState(0,1))
{
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
/**
* 实现逻辑:
* 1。如果锁的状态是0,抛出异常
* 2。设置当前线程为null
* 3。改变状态,返回结果
* @param arg
* @return
*/
@Override
protected boolean tryRelease(int arg) {
if(getState()==0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
//当前同步器是否在 独占模式下被线程占用,1的时候被线程占用
@Override
protected boolean isHeldExclusively() {
return getState()==1;
}
//返回一个condition,每个Condition都含有Condition队列
Condition newCondition() { return new ConditionObject();};
}
MyAQS myAQS=new MyAQS();
@Override
public void lock() {
myAQS.acquire(1);
}
@Override
public boolean tryLock() {
return myAQS.tryAcquire(1);
}
@Override
public void unlock() {
myAQS.release(1);
}
@Override
public Condition newCondition() {
return myAQS.newCondition();
}
@Override
public void lockInterruptibly() throws InterruptedException {
myAQS.acquireInterruptibly(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return myAQS.tryAcquireNanos(1,unit.toNanos(time));
}
}
ReentrantLock
可重入锁,从字面意思可以知道,可以进行重复获取锁的锁。我们知道对于一般的锁,如果再次进行加锁,将会阻塞自己。通过上面的代码可以知道,对于一般的锁,状态的值一般有两个0,1,对于重入锁,getState()的值是大于1的,每加锁一次,这个值就加一,每释放一次锁,就减一。
对于这些锁,可以公平的获取锁,或者非公平的获取锁。默认情况下,实现的是非公平的获取锁。非公平的获取锁,使用的是一个FIFO的可调度队列。
public String getInc()
{
lock.lock();
try{
i++;
}
finally {
lock.unlock();
return i+"";
}
}
需要注意的是,对于锁的释放需要放在finally中,以防止发生异常的时候,保证锁的释放。
ReentrantReadWriteLock
可重入读写锁,可重入读写锁维护的是一对锁,读锁和写锁。
class LockTest{
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
private HashMap<String,String> maps=new HashMap<>();
//读锁
private Lock r=lock.readLock();
//写锁
private Lock w=lock.writeLock();
public String getName(String s)
{
r.lock();
try{
maps.get(s);
}finally {
r.unlock();
return "default";
}
}
//写锁实现HashMap的put操作同步
public void setValue(String k,String v)
{
w.lock();
try{
maps.put(k,v);
}finally {
w.unlock();
}
}
}
线程池的使用
线程池的好处
1.降低资源消耗
2.提高响应数独
3.提高线程的可管理性
线程池的原理
对于新任务的提交,进入以下流程:
如果不是,那就创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2.线程池来判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储子啊这个工作队列里。如果工作队列满,则进入下一个流程。
3.线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程执行任务。如果已经满了,则交给饱和和策略来处理这个任务
线程池的流程图:
我们可以使用ThreadPoolExecutor来创建线程池,这个类的执行流程如下:
我们来看看这个流程:
1.线程小于corePoolSize,创建线程执行任务,需要获取全局锁
2.多余corePoolSize,加入BlockingQueue
3.队列已满,创建线程来处理任务,需要获取全局锁
4.超过maxpoolSize,任务拒绝,调用RejectedExecutionHandler.rejecttedExecutin()方法。
设计总的思路:调用execute()方法,尽可能避免获取全局锁。
常用的几种线程池
1、 Single Thread Executor : 只有一个线程的线程池,因此所有提交的任务是顺序执行,
2、 Cached Thread Pool : 线程池里有很多线程需要同时执行,老的可用线程将被新的任务触发重新执行,如果线程超过60秒内没执行,那么将被终止并从池中删除,
3、 Fixed Thread Pool : 拥有固定线程数的线程池,如果没有任务执行,那么线程会一直等待,
在构造函数中的参数4是线程池的大小,你可以随意设置,也可以和cpu的核数量保持一致,获取cpu的核数量int cpuNums = Runtime.getRuntime().availableProcessors();
4、 Scheduled Thread Pool : 用来调度即将执行的任务的线程池,可能是不是直接执行, 每隔多久执行一次... 策略型的
5、Single Thread Scheduled Pool : 只有一个线程,用来调度任务在指定时间执行。
Java中的并发队列
什么是阻塞队列
阻塞是支持两个附加操作的队列。
1.支持阻塞的支持方法:当队列满的时候,队列回阻塞插入元素的线程,直到队列不满
2.支持阻塞的移除方法:意思是在队列为空时,区去元素的线程会等待队列变为非空
ArrayBlockingQueue:数据结构组成的有界阻塞队列
LinkedBlokingQueue:一个链表结构组成的有界组塞队列
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
DealyQueue:一个使用优先级队列实现的无界阻塞队列
SynchronousQueue:一个不存储元素的组塞队列
LinkedTransferQueue:一个链表结构组成的无界阻塞队列
LinkedBlockingDeque:链表结构组成的双向阻塞队列
1.ArrayBlockingQueue
FIFO,不保证公平访问
2.LinkedBlockingQueue
FIFO
3.PriorityBlockingQueue
支持优先级,不保证同优先级元素的顺序
4.DelayQueue
通过使用PrioprityQueue来实现,队列中的元素必须实现Delayed接口,只有时间到了,才从队列中出来
缓存系统的设计
定时任务调度
5.SynchronousQueue
一个put操作必须等待一个take操作
支持公平访问
默认不公平
适合传递场景
6.LinkedTransferQueue
1.transfer方法
将生产者传入的元素立刻传递给消费者,如果没有消费者接受,放在tail,然后自旋等待
2.tryTransfer方法
试探生产者传入的元素是否能直接传递给消费者,如果没有消费者接受,返回false
7.LinkedBlockingDeque
阻塞队列的实现原理
使用通知模式实现
当生产者往满的队列里添加元素,会阻塞住生产者,当消费者消费一个队列中的元素后,会通知生产者当前队列可用。
Java中的反射和动态代理
通过反射的方式可以获取class对象中的属性、方法、构造函数等,以下是实例:
package cn.java.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
public class MyReflect {
public String className = null;
@SuppressWarnings("rawtypes")
public Class personClass = null;
/**
* 反射Person类
* @throws Exception
*/
@Before
public void init() throws Exception {
className = "cn.java.reflect.Person";
personClass = Class.forName(className);
}
/**
*获取某个class文件对象
*/
@Test
public void getClassName() throws Exception {
System.out.println(personClass);
}
/**
*获取某个class文件对象的另一种方式
*/
@Test
public void getClassName2() throws Exception {
System.out.println(Person.class);
}
/**
*创建一个class文件表示的真实对象,底层会调用空参数的构造方法
*/
@Test
public void getNewInstance() throws Exception {
System.out.println(personClass.newInstance());
}
/**
*获取非私有的构造函数
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void getPublicConstructor() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class,String.class);
Person person = (Person)constructor.newInstance(100L,"zhangsan");
System.out.println(person.getId());
System.out.println(person.getName());
}
/**
*获得私有的构造函数
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void getPrivateConstructor() throws Exception {
Constructor con = personClass.getDeclaredConstructor(String.class);
con.setAccessible(true);//强制取消Java的权限检测
Person person2 = (Person)con.newInstance("zhangsan");
System.out.println(person2.getName());
}
/**
*获取非私有的成员变量
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void getNotPrivateField() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class,String.class);
Object obj = constructor.newInstance(100L,"zhangsan");
Field field = personClass.getField("name");
field.set(obj, "lisi");
System.out.println(field.get(obj));
}
/**
*获取私有的成员变量
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
public void getPrivateField() throws Exception {
Constructor constructor = personClass.getConstructor(Long.class);
Object obj = constructor.newInstance(100L);
Field field2 = personClass.getDeclaredField("id");
field2.setAccessible(true);//强制取消Java的权限检测
field2.set(obj,10000L);
System.out.println(field2.get(obj));
}
/**
*获取非私有的成员函数
*/
@SuppressWarnings({ "unchecked" })
@Test
public void getNotPrivateMethod() throws Exception {
System.out.println(personClass.getMethod("toString"));
Object obj = personClass.newInstance();//获取空参的构造函数
Object object = personClass.getMethod("toString").invoke(obj);
System.out.println(object);
}
/**
*获取私有的成员函数
*/
@SuppressWarnings("unchecked")
@Test
public void getPrivateMethod() throws Exception {
Object obj = personClass.newInstance();//获取空参的构造函数
Method method = personClass.getDeclaredMethod("getSomeThing");
method.setAccessible(true);
Object value = method.invoke(obj);
System.out.println(value);
}
/**
*
*/
@Test
public void otherMethod() throws Exception {
//当前加载这个class文件的那个类加载器对象
System.out.println(personClass.getClassLoader());
//获取某个类实现的所有接口
Class[] interfaces = personClass.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1);
}
//反射当前这个类的直接父类
System.out.println(personClass.getGenericSuperclass());
/**
* getResourceAsStream这个方法可以获取到一个输入流,这个输入流会关联到name所表示的那个文件上。
*/
//path 不以’/'开头时默认是从此类所在的包下取资源,以’/'开头则是从ClassPath根下获取。其只是通过path构造一个绝对路径,最终还是由ClassLoader获取资源。
System.out.println(personClass.getResourceAsStream("/log4j.properties"));
//默认则是从ClassPath根下获取,path不能以’/'开头,最终是由ClassLoader获取资源。
System.out.println(personClass.getResourceAsStream("/log4j.properties"));
//判断当前的Class对象表示是否是数组
System.out.println(personClass.isArray());
System.out.println(new String[3].getClass().isArray());
//判断当前的Class对象表示是否是枚举类
System.out.println(personClass.isEnum());
System.out.println(Class.forName("cn.java.reflect.City").isEnum());
//判断当前的Class对象表示是否是接口
System.out.println(personClass.isInterface());
System.out.println(Class.forName("cn.java.reflect.TestInterface").isInterface());
}
}
动态代理
在之前的代码调用阶段,我们用action调用service的方法实现业务即可。
由于之前在service中实现的业务可能不能够满足当先客户的要求,需要我们重新修改service中的方法,但是service的方法不只在我们这个模块使用,在其他模块也在调用,其他模块调用的时候,现有的service方法已经能够满足业务需求,所以我们不能只为了我们的业务而修改service,导致其他模块授影响。 那怎么办呢?
可以通过动态代理的方式,扩展我们的service中的方法实现,使得在原油的方法中增加更多的业务,而不是实际修改service中的方法,这种实现技术就叫做动态代理。
动态代理:在不修改原业务的基础上,基于原业务方法,进行重新的扩展,实现新的业务。
例如下面的例子:
1、 旧业务
买家调用action,购买衣服,衣服在数据库的标价为50元,购买流程就是简单的调用。
2、 新业务
在原先的价格上可以使用优惠券,但是这个功能在以前没有实现过,我们通过代理类,代理了原先的接口方法,在这个方法的基础上,修改了返回值。
代理实现流程:
1、 书写代理类和代理方法,在代理方法中实现代理Proxy.newProxyInstance
2、 代理中需要的参数分别为:被代理的类的类加载器soneObjectclass.getClassLoader(),被代理类的所有实现接口new Class[] { Interface.class },句柄方法new InvocationHandler()
3、 在句柄方法中复写invoke方法,invoke方法的输入有3个参数Object proxy(代理类对象), Method method(被代理类的方法),Object[] args(被代理类方法的传入参数),在这个方法中,我们可以定制化的开发新的业务。
4、 获取代理类,强转成被代理的接口
5、 最后,我们可以像没被代理一样,调用接口的认可方法,方法被调用后,方法名和参数列表将被传入代理类的invoke方法中,进行新业务的逻辑流程。
原业务接口IBoss
public interface IBoss {//接口 int yifu(String size); } |
原业务实现类
public class Boss implements IBoss{ public int yifu(String size){ System.err.println("天猫小强旗舰店,老板给客户发快递----衣服型号:"+size); //这件衣服的价钱,从数据库读取 return 50; } public void kuzi(){ System.err.println("天猫小强旗舰店,老板给客户发快递----裤子"); } } |
原业务调用
public class SaleAction { @Test public void saleByBossSelf() throws Exception { IBoss boss = new Boss(); System.out.println("老板自营!"); int money = boss.yifu("xxl"); System.out.println("衣服成交价:" + money); } } |
代理类
public static IBoss getProxyBoss(final int discountCoupon) throws Exception { Object proxedObj = Proxy.newProxyInstance(Boss.class.getClassLoader(), new Class[] { IBoss.class }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Integer returnValue = (Integer) method.invoke(new Boss(), args);// 调用原始对象以后返回的值 return returnValue - discountCoupon; } }); return (IBoss)proxedObj; } } |
新业务调用
public class ProxySaleAction { @Test public void saleByProxy() throws Exception { IBoss boss = ProxyBoss.getProxyBoss(20);// 将代理的方法实例化成接口 System.out.println("代理经营!"); int money = boss.yifu("xxl");// 调用接口的方法,实际上调用方式没有变 System.out.println("衣服成交价:" + money); } } |