通过Thread类或Runnable接口创建线程对象之后进入初始状态;调用start方法进入可运行状态(就绪状态),此时并不是真正的运行,只是代表已经做好了运行前的各项装备;如果此线程获取到cpu的时间片,则进入到真正的可运行状态,执行run方法里面的业务逻辑;如果run方法执行完毕或调用stop方法则线程运行结束,进入死亡状态;在运行状态时调用不同方法也会进入其他不同状态,如果调用强制运行方法join或休眠方法将进入等待状态,时间到后自动进入就绪状态,随时准备获取cpu时间片;如果看到synchronized则进入同步队列等待状态,或者如果调用了wait方法则进入等待状态,等待状态的线程必须要通过notify唤醒才可进入等待状态,如果其它线程执行完毕,本线程拿到同步锁则进入就绪状态,等待获取cpu时间片。某个线程是否会执行只能看它能否争抢到cpu时间片,但是通过调高优先级来让线程更大概率的被优先执行。
多线程运行的原理是: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> { 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<String> futureTask = new FutureTask<String>(callable); Thread thread = new Thread(futureTask); thread.start(); String result = futureTask.get(); //获取任务线程执行结果 System.out.println("线程的执行结果:"+result); } }
见下面的线程池专讲。 参考文档:Callable,Runnable比较及用法以及创建线程的4种方法
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()方法是用来释放锁的。
public class MyLock { private static ArrayList<Integer> arrayList = new ArrayList<Integer>(); private static Lock lock = new ReentrantLock(); public static <E> 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<Integer> arrayList = new ArrayList<Integer>(); 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才能获取锁,执行它的代码,进而保证了线程安全。
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }
一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。
/** * @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的选择:
程序执行时有主内存,每个线程工作时也有自己的工作内存。当一个线程开始工作时会从主内存中拷贝一个变量的副本到工作内存中,在工作内存中操作完副本时再更新回主内存。当存在多线程时,如果工作内存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的:
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<String> future = pool.submit(new Callable<String>() { @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<Future<?>> futureList = new ArrayList<Future<?>>(); 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()的方式,对于一般场景是没问题的,但如果是在并发请求很高的情况下,就会有隐患:
不管是通过Executors创建线程池,还是通过Spring来管理,都得知道有哪几种线程池:
由以上线程池类型可知,除了CachedThreadPool其他线程池都有饱和的可能,当饱和以后就需要相应的策略处理请求线程的任务,比如,达到上限时通过ThreadPoolExecutor.setRejectedExecutionHandler方法设置一个拒绝任务的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy几种策略。
我有一个微信公众号,经常会分享一些Java技术相关的干货;如果你喜欢我的分享,可以用微信搜索“Java团长”或者“javatuanzhang”关注。
如对本文有疑问, 点击进行留言回复!!
Android 4.0使用Kotlin调用C语言以及汇编语言
Java Class.forName()用法和newInstance()方法原理解析
网友评论