通过Thread类或Runnable接口创建线程对象之后进入初始状态;调用start方法进入可运行状态(就绪状态),此时并不是真正的运行,只是代表已经做好了运行前的各项装备;如果此线程获取到cpu的时间片,则进入到真正的可运行状态,执行run方法里面的业务逻辑;如果run方法执行完毕或调用stop方法则线程运行结束,进入死亡状态;在运行状态时调用不同方法也会进入其他不同状态,如果调用强制运行方法join或休眠方法将进入等待状态,时间到后自动进入就绪状态,随时准备获取cpu时间片;如果看到synchronized则进入同步队列等待状态,或者如果调用了wait方法则进入等待状态,等待状态的线程必须要通过notify唤醒才可进入等待状态,如果其它线程执行完毕,本线程拿到同步锁则进入就绪状态,等待获取cpu时间片。某个线程是否会执行只能看它能否争抢到cpu时间片,但是通过调高优先级来让线程更大概率的被优先执行。
参考文档:https://mp.weixin.qq.com/s?src=11×tamp=1513562547&ver=581&signature=30FEkCCQvF3E1tt67vYVym5tRNsSk3d8HGe0v9TAonJmhLh4-53fDEBbgwNF*Olgp5rAlGFAJQXYnviaFRwiQ9NmbtIWnZGpot*GcuV0Ok*3WzWxg4X6e2mxU0JrgbRb&new=1
多线程运行的原理是:cpu在线程中做时间片的切换。cpu负责程序的执行,在每个时间点它其实只能运行一个程序而不是多个程序,不停的在多个程序之间高速切换,而一个程序其实就是一个进程即多个线程,说到底其实就是cpu在多个线程之间不停的做高速切换,而开多个线程就是不让cpu歇着,最大程度的压榨它来为程序服务。实现多线程有三种方式:继承Thread类;实现Runnable接口;使用线程池。
public class MyExtendsThread extends Thread {
String flag;
public MyExtendsThread(String flag){
this.flag = flag;
}
@Override
public void run(){
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"开始工作了...");
Random random = new Random();
for (int i = 0;i < 20;i++){
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"============="+flag);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t0 = new MyExtendsThread("t0");
Thread t1 = new MyExtendsThread("t1");
t0.start();
t1.start();
// t0.run();
// t1.run();
}
}
调用线程要用start方法,而不是run方法,使用run方法只是调用方法,实际执行的还是Main线程,而调用start方法可以明显的看到线程争抢。
public class MyThreadImplementRunnable implements Runnable {
int x;
public MyThreadImplementRunnable(int x) {
this.x = x;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"开始执行");
Random random = new Random();
for(int i = 0;i<20;i++){
try {
Thread.sleep(random.nextInt(10)*100);
System.out.println(name+"============="+x);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThreadImplementRunnable(1),"线程1");
Thread t2 = new Thread(new MyThreadImplementRunnable(2),"线程2");
t1.start();
t2.start();
}
}
public class MyThreadImplementCallable implements Callable {
String name;
public MyThreadImplementCallable(String name) {
this.name = name;
}
@Override
public String call() throws Exception {
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"开始工作==============");
Random random = new Random();
Thread.sleep(random.nextInt(5)*100); //模拟执行业务
return name+":执行完成";
}
public static void main(String[] args) throws Exception{
MyThreadImplementCallable callable = new MyThreadImplementCallable("测试");
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get(); //获取任务线程执行结果
System.out.println("线程的执行结果:"+result);
}
}
见下面的线程池专讲。
参考文档:https://www.cnblogs.com/langtianya/archive/2013/03/14/2959713.html
public class MySynchronized {
public static void main(String[] args){
final MySynchronized synchronized1 = new MySynchronized();
final MySynchronized synchronized2 = new MySynchronized();
new Thread("thread1"){
@Override
public void run(){
synchronized (synchronized1){
try {
System.out.println(this.getName()+":start");
Thread.sleep(1000);
System.out.println(this.getName()+":wake up");
System.out.println(this.getName()+":end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
new Thread("thread2"){
@Override
public void run() {
synchronized (synchronized1){ //争抢同一把锁时,线程1没释放之前,线程2只能等待
// synchronized (synchronized2){ //如果不是一把锁,可以看到两句话交叉打印,发生争抢
System.out.println(this.getName()+":start");
System.out.println(this.getName()+":end");
}
}
}.start();
}
}
synchronized是java中的关键字,属于java语言的内置特性。如果一个代码块使用synchronized修饰,则这块代码是同步的,当一个线程获取到这个锁并且开始执行时,其它线程只能一直眼睁睁的等着这个线程执行然后释放锁,其中释放锁只有两种原因:1.线程正常执行完毕;2.线程执行时发生异常,jvm自动将锁释放。可以看到使用synchronized关键字之后每个时刻只会有一个线程执行代码块里面的共享代码,线程安全;缺点也很明显,其它线程只能等锁释放,资源浪费严重。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
}
lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的。
unLock()方法是用来释放锁的。
- lock就是用来获取锁的,前面说到如果采用Lock,必须主动去释放锁。即使发生异常,程序也不会自动释放锁,因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
public class MyLock {
private static ArrayList arrayList = new ArrayList();
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread() {
@Override
public void run() {
Thread thread = Thread.currentThread();
lock.lock(); //获取锁
try {
System.out.println(thread.getName() + "得到了锁");
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
} catch (Exception e) {
} finally {
System.out.println(thread.getName() + "释放了锁");
lock.unlock(); //释放锁
}
};
}.start();
new Thread() {
@Override
public void run() {
Thread thread = Thread.currentThread();
lock.lock();
try {
System.out.println(thread.getName() + "得到了锁");
for (int i = 0; i < 5; i++) {
arrayList.add(i);
}
} catch (Exception e) {
} finally {
System.out.println(thread.getName() + "释放了锁");
lock.unlock();
}
};
}.start();
}
}
//观察现象:一个线程获得锁后,另一个线程取不到锁,不会一直等待
public class MyTryLock {
private static List arrayList = new ArrayList();
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread("线程1") {
@Override
public void run() {
Thread thread = Thread.currentThread();
boolean tryLock = lock.tryLock();
System.out.println(thread.getName()+"======="+tryLock);
if(tryLock){
try {
System.out.println(thread.getName() + "得到了锁");
for(int i = 0;i < 20;i++){
arrayList.add(i);
}
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}
}.start();
new Thread("线程2") {
@Override
public void run() {
Thread thread = Thread.currentThread();
boolean tryLock = lock.tryLock();
System.out.println(thread.getName()+"======="+tryLock);
if(tryLock){
try {
System.out.println(thread.getName() + "得到了锁");
for(int i = 0;i < 20;i++){
arrayList.add(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}
}.start();
}
}
线程1和线程2共享成员变量arrayList,当线程1获取锁的时候,线程2就获取不到锁,没办法执行它的业务逻辑,只有等线程1执行完毕,释放了锁,线程2才能获取锁,执行它的代码,进而保证了线程安全。
- lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。
因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以中断的。而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
- Lock接口的实现类——ReentrantLock
直接使用lock接口的话,我们需要实现很多方法,不方便,ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法,ReentrantLock,意思是“可重入锁”,使用它可以创建Lock对象。
- ReadWriteLock也是一个接口,在它里面只定义了两个方法:
public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。
- ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁,使用这个读写锁操作的结果就是:要么执行的全是读操作,结束完之后全执行写操作,中间不会交叉读写。
/**
* @author 刘俊重
* 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
* 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。
*/
public class MyReentrantReadWriteLock {
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public static void main(String[] args) {
final MyReentrantReadWriteLock myTest = new MyReentrantReadWriteLock();
new Thread("线程1"){
@Override
public void run(){
myTest.read(Thread.currentThread());
myTest.writer(Thread.currentThread());
}
}.start();
new Thread("线程2"){
@Override
public void run(){
myTest.read(Thread.currentThread());
myTest.writer(Thread.currentThread());
}
}.start();
}
/**
* @Description 读方法
* @Author 刘俊重
* @Date 2017/12/18
*/
private void read(Thread thread){
readWriteLock.readLock().lock();
try {
long start = System.currentTimeMillis();
while (System.currentTimeMillis()-start<=1){
System.out.println(thread.getName()+"===正在执行读操作");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
System.out.println(thread.getName()+"==释放读锁");
}
}
/**
* @Description 写方法
* @Author 刘俊重
* @Date 2017/12/18
*/
private void writer(Thread thread){
readWriteLock.writeLock().lock();
try {
long start = System.currentTimeMillis();
while (System.currentTimeMillis()-start<=1){
System.out.println(thread.getName()+"===正在执行写操作");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
System.out.println(thread.getName()+"==释放写锁");
}
}
}
Lock和Synchronized的选择:
- Lock是一个接口,而sysnchrinized是java关键字,属于内置的语言实现;
- synchronized关键字程序运行完成之后或出现异常时会释放锁,使用lock不会自动释放锁,只能自己使用unlock释放,否则会引起死锁,最好在finally中释放;
- 使用lock可以使用trylock方法判断有没有获得锁,使用synchronized无法判断;
- 使用lock可以让等待锁的线程中断,使用synchronized无法让线程中断,只能一直等待下去;
- 使用lock可以提高多线程读操作的效率。
结论:如果竞争的资源不激烈,则使用synchronized和lock效率差不多;如果有大量线程同时竞争,则lock要远远优于synchronized。
程序执行时有主内存,每个线程工作时也有自己的工作内存。当一个线程开始工作时会从主内存中拷贝一个变量的副本到工作内存中,在工作内存中操作完副本时再更新回主内存。当存在多线程时,如果工作内存A处理完还没来得及更新回主内存之前,工作内存B就从主内存中拉取了这个变量,那么很明显这个变量并不是最新的数据,会出现问题。怎么解决呢?可以使用volatile,volatile有个最显著的特性就是对它所修饰变量具有可见性,什么意思呢,就是当一个线程修改了变量的值,新的值会立刻(马上)同步到主内存中,其它线程使用时拉取到的就是最新的变量值。尽管volatile能保证变量的可见性,但并不能保证线程安全,因为它不能保证原子性。要想线程安全还是要用同步或者锁。
有一篇文档写volatile写的很好,贴一下:http://dwz.cn/76TMGW
JDK1.5之后引入了高级并发特性,在java.util.concurrent包中,是专门用于多线程并发编程的,充分利用了现代计算机多处理器和多核心的功能以编写大规模并发应用程序。主要包含原子量、并发集合、同步器、可重入锁,并对线程池的创建提供了强力的支持。
public static void main(String[] args) {
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//获取cpu核心数
int num = Runtime.getRuntime().availableProcessors();
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(num);
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(8);
ScheduledExecutorService newSingleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();
}
说到线程池使用之前再强调一下Runnable的孪生兄弟——Callable,他们两个很像,只是Runnable的run方法不会有任何返回结果,主线程无法获得任务线程的返回值;但是Callable的call方法可以返回结果,但是主线程在获取时是被阻塞,需要等待任务线程返回才能拿到结果,所以Callable比Runnable更强大,那么怎么获取到这个执行结果呢?答案是Future,使用Future可以获取到Callable执行的结果。
现在开始说线程池怎么使用,也有两种方式,一种Runnable的,一种Callable的:
- 提交 Runnable ,任务完成后 Future 对象返回 null,调用excute,提交任务, 匿名Runable重写run方法, run方法里是业务逻辑。
示例代码:
public class TestPoolWithRunnable {
public static void main(String[] args) throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i=0;i<10;i++){
Future> submit = pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始执行");
}
});
System.out.println("执行结果:"+submit.get()); //所有的执行结果全是null
}
pool.shutdown(); //关闭线程池
}
}
/**
* @author 刘俊重
* Callable 跟Runnable的区别:
* Runnable的run方法不会有任何返回结果,所以主线程无法获得任务线程的返回值
* Callable的call方法可以返回结果,但是主线程在获取时是被阻塞,需要等待任务线程返回才能拿到结果
*/
public class TestPoolWithCallable {
public static void main(String[] args) throws Exception{
ExecutorService pool = Executors.newFixedThreadPool(4);
for(int i=0;i<10;i++){
Future future = pool.submit(new Callable() {
@Override
public String call() throws Exception {
Thread.sleep(500);
return "===="+Thread.currentThread().getName();
}
});
//从Future中get结果,这个方法是会被阻塞的,一直要等到线程任务返回结果
System.out.println("执行结果:"+future.get());
}
pool.shutdown();
}
}
如何解决获取执行结果阻塞的问题?
在使用future.get()方法获取结果时,这个方法是阻塞的,怎么提高效率呢?如果在不要求立马拿到执行结果的情况下,可以先将执行结果放在队列里面,待程序执行完毕之后在获取每个线程的执行结果,示例代码如下:
public class TestThreadPool {
public static void main(String[] args) throws Exception{
Future> submit = null;
//创建缓存线程池
ExecutorService cachePool = Executors.newCachedThreadPool();
//用来存在Callable执行结果
List> futureList = new ArrayList>();
for(int i = 0;i<10;i++){
//cachePool提交线程,Callable,Runnable无返回值
//submit = cachePool.submit(new TaskCallable(i));
submit = cachePool.submit(new TaskRunnable(i));
//把这些执行结果放到list中,后面再取可以避免阻塞
futureList.add(submit);
}
cachePool.shutdown();
//打印执行结果
for(Future f : futureList){
boolean done = f.isDone();
System.out.println(done?"已完成":"未完成");
System.out.println("线程返回结果:"+f.get());
}
}
}
把submit放在list集合中,线程直线完毕之后再取。
直接使用new Thread().start()的方式,对于一般场景是没问题的,但如果是在并发请求很高的情况下,就会有隐患:
- 新建线程的开销。线程虽然比进程要轻量许多,但对于JVM来说,新建一个线程的代价还是很大的,决不同于新建一个对象。
- 资源消耗量。没有一个池来限制线程的数量,会导致线程的数量直接取决于应用的并发量。
- 稳定性。当线程数量超过系统资源所能承受的程度,稳定性就会成问题。
不管是通过Executors创建线程池,还是通过Spring来管理,都得知道有哪几种线程池:
- FixedThreadPool:定长线程池,提交任务时创建线程,直到池的最大容量,如果有线程非预期结束,会补充新线程;
- CachedThreadPool:可变线程池,它犹如一个弹簧,如果没有任务需求时,它回收空闲线程,如果需求增加,则按需增加线程,不对池的大小做限制;
- SingleThreadExecutor:单线程。处理不过来的任务会进入FIFO队列等待执行;
- SecheduledThreadPool:周期性线程池。支持执行周期性线程任务
其实,这些不同类型的线程池都是通过构建一个ThreadPoolExecutor来完成的,所不同的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory这么几个参数。
由以上线程池类型可知,除了CachedThreadPool其他线程池都有饱和的可能,当饱和以后就需要相应的策略处理请求线程的任务,比如,达到上限时通过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy几种策略。