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