背景:并发知识是一个程序员段位升级的体现,同样也是进入BAT的必经之路,有必要把并发知识重新梳理一遍。
并发concurrent:
说到并发concurrent,肯定首先想到了线程,创建线程有两种方法:1、从Java.lang.Thread类派生一个新的线程类,重载它的run()方法;2、实现Runnalbe接口,重载Runnalbe接口中的run()方法;建议使用方法二创建线程,因为,如果是通过扩展 Thread类的方法来创建线程,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能;而实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性,也更符合面向对象编程的思想,最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享。
创建线程的两种方法:
1 package www.concurent.test;
2 public class TraditionalThread {
3
4 public static void main(String[] args) {
5 //Thread1:
6 Thread thread = new Thread() {
7 @Override
8 public void run() {
9 while(true) {
10 try {
11 Thread.sleep(1000);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15 System.out.println("thread1: "+Thread.currentThread().getName());
16 }
17 }
18 };
19 thread.start();
20
21 //Thread2:
22 //Runnable变量是线程要运行的代码的宿主,更适合面向对象思想的线程方法
23 Thread thread2 = new Thread(new Runnable() {
24 @Override
25 public void run() {
26 while(true) {
27 try {
28 Thread.sleep(1000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 System.out.println("thread2: "+Thread.currentThread().getName());
33 }
34 }
35 });
36 thread2.start();
37 }
38 }
线程和Timer定时器很类似,下面介绍了两种和线程相似的定时器写法:1、定时一天之后调用方法查询天气情况接口,然后每隔60秒后继续调用该方法;2、定时每天00:39:32调用查询天气情况接口,通过Hutool工具和Timer定时器调用HTTP天气状况接口的返回结果如下截图:(result2得到了天津的天气状况)
通过Hutool工具和Timer定时器调用HTTP天气状况接口:
1 import java.util.Calendar;
2 import java.util.Date;
3 import java.util.Timer;
4 import java.util.TimerTask;
5 import cn.hutool.http.HttpUtil;
6
7 public class TraditionalTimerTest {
8 //时间间隔
9 private static final long PERIOD_DAY = 24 * 60 * 60 * 1000;
10 //Timer 定时器
11 public static void main(String[] args) {
12 Calendar cl = Calendar.getInstance();
13 cl.set(Calendar.HOUR_OF_DAY, 0);
14 cl.set(Calendar.MINUTE, 39);
15 cl.set(Calendar.SECOND, 32);
16 Date date = cl.getTime();
17 Date dateNow = new Date();
18 //如果第一次执行定时任务的时间 小于 当前的时间
19 //此时要在 第一次执行定时任务的时间 加一天,以便此任务在下个时间点执行。如果不加一天,任务会立即执行。
20 if (date.before(dateNow)) {
21 Calendar clAdd = Calendar.getInstance();
22 clAdd.setTime(dateNow);
23 clAdd.add(Calendar.DAY_OF_MONTH, 1);
24 date = clAdd.getTime();
25 }
26 //Timer1:
27 new Timer().schedule(new TimerTask() {
28 @Override
29 public void run() {
30 System.out.println("hello");
31 //Hutool调用http接口
32 String result1 = HttpUtil.get("http://t.weather.sojson.com/api/weather/city/101030100");
33 System.out.println("result1: "+result1);
34 }
35 //一天之后调用方法查询天气情况接口,然后每隔60秒后继续调用该方法
36 },PERIOD_DAY , 1000*60);
37 //Timer2:
38 new Timer().schedule(new TimerTask() {
39 @Override
40 public void run() {
41 //Hutool调用http接口
42 String result2 = HttpUtil.get("http://t.weather.sojson.com/api/weather/city/101030100");
43 System.out.println("result2: " + result2);
44 }
45 //定时每天00:39:32调用查询天气情况接口
46 }, date , PERIOD_DAY);
47 }
48
49 }
如果是单个线程调用都还ok,要是有多个线程同时调用那就会出现并发产生;比如有一个方法Output()是经过charAt(i)获取字符串i的字符并且打印再控制台,然后线程A和线程B同时调用Output()方法,此时就会出现线程不安全问题(如银行取钱和转账同时进行),也就是并发;执行结果发现,线程A为执行完毕线程B就开始执行了,为了能后保证当有一个线程来执行某个方法时,其他的线程不能进来执行该方法,实现排他性,可以通过synchronized和ReentrantLock来实现线程同步;二者其实区别不大,synchronized由于是底层JVM实现的互斥,因此效率会高一些,而ReentrantLock的功能则比synchronized更多,比如定时获取某个锁,多个等待条件等,另外synchronized 会让线程阻塞,ReentrantLock会让线程等待,但是从行为效果上来看是一样的;下面有个例子:并发结果如截图显示,理想状态是打印“huawei”或者“isoftstone”,但是由于并发打印出来诸如此类“ishuaweoftstoni”结果。
FYI:
1 import java.util.concurrent.locks.Lock;
2 import java.util.concurrent.locks.ReentrantLock;
3
4 public class MyThreadSynchronized {
5 public static void main(String[] args) {
6 //要想调用内部类的对象,必须有外部类的实例对象
7 new MyThreadSynchronized().init(); // 外部类的实例对象
8 }
9 public void init() {
10 final Outputer outputer = new Outputer();
11 //thread1:
12 new Thread(new Runnable() {
13 @Override
14 public void run() {
15 while(!false) {
16 try {
17 Thread.sleep(100);
18 } catch (InterruptedException e) {
19 e.printStackTrace();
20 }
21 outputer.output3("huawei");
22 }
23 }
24 }).start();
25
26 //thread2:
27 new Thread(new Runnable() {
28 @Override
29 public void run() {
30 while(!false) {
31 try {
32 Thread.sleep(100);
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 outputer.output("isoftstone");
37 }
38 }
39 }).start();
40 }
41 //当有一个线程来执行某个方法时,其他的线程不能进来执行该方法,排他性、独一无二;
42 //使用synchronized/lock同步,且线程用的同步锁是同一个同步对象,可用this互斥或方法.class
43 //synchronized由于是底层JVM实现的互斥,因此效率会高一些
44 //ReentrantLock的功能则比synchronized更多,比如定时获取某个锁,多个等待条件
45 //synchronized 会让线程阻塞,ReentrantLock会让线程等待,但是从行为效果上来看是一样的;
46 class Outputer{
47 //内部类 静态方法中不能new内部类的实例对象
48 //synchronized:
49 public synchronized void output(String name) {
50 for(int i = 0; i<name.length(); i++) {
51 System.out.print(name.charAt(i));
52 }
53 System.out.println();// switch line
54 }
55
56 //线程不安全
57 public void output3(String name) {
58 for(int i = 0; i<name.length(); i++) {
59 System.out.print(name.charAt(i));
60 }
61 System.out.println();// switch line
62 }
63
64 //lock:
65 public void ouputlock(String name) {
66 Lock lock = new ReentrantLock();
67 lock.lock(); // 上锁同步
68 try {
69 for (int i = 0; i < name.length(); i++) {
70 System.out.print(name.charAt(i));
71 }
72 System.out.println();// switch line
73 } finally {
74 lock.unlock(); // 解锁
75 }
76 }
77
78 }
79
80 }