今天是线程领域的开篇第一讲。java线程的文章,在网上已经铺天盖地了,但个人觉得线程是一个比较抽象的领域,很多文章没有从根本思维方式上把它具象化,一开始就直接给你从java.util.concurrent包开讲,结果读者被搞得云里雾里,越发觉得线程很神密,很深奥,很难以理解。一些复杂业务的线程运行确实难以琢磨的。但咱们不从业务开讲,咱从概念开讲,解决了概念的问题,线程编程就好办了。
在这一章里,我们提出第一讲的几个最基本的问题,都是初次接触线程时最疑惑的。
1、线程是个啥?
2、创建JAVA Thread
3、为啥要使用线程?
4、为啥使用线程非得要NEW,不NEW不行吗?
5、JAVA两个线程如何通讯?
1、线程是个啥?
在这里多的不唠,少的不说,线程的专业概念,各位另找。你就想像成一个命令就好了。
java应用启动时,最先启动main线程,main线程启动完成后再启动其他业务线程。
2、创建JAVA Thread
最简单开始创建线程有下面两种方式
1.通过继承Thread类创建线程。
2.通过实现Runnable接口创建线程。
其他方式创建线程后面再讲
3、为啥要使用线程?
因为cpu处理数据的速度足够快,其时间都是纳秒级别。如果服务器上的应用不多,那么cpu多数情况下是空闲的,为了充分利用cpu,应当使用线程。
一个简单例子:比如一个业务代码块里需要访问四、五个第三方的接口,并且访问这些接口又不需要先后顺序,同时访问的参数又不是共享参数。那么此时就应当使用线程来访问获取第三方接口数据。
使用方法是每个接口访问启动一个线程,等所有接口访问都获得数据后再进行计算处理。
根据实验数据访问一个http接口平均时间在700至900毫秒间,4-5个接口按顺序执行的得3~4秒时间这是让人无法接受的。如果使用线程时间将缩小到900毫秒以下。
复杂的业务计算代码中使用的线程更多,比如flink框架
4、为啥使用Thread非得要NEW,不NEW不行吗?
通俗的讲多使用一个线程,就相当于另起一个炉灶。比如夫妻快餐小吃店,厨师只有一位就是老板(也可能是老板娘),想提高上菜速度那必须得再加一个厨师,那你不就得new一个咯。所以使用线程非得要NEW,不new不行。new多了也不行,因为小吃店的厨房小啊。
5、JAVA两个线程如何通讯?
因为在CPU读取内存中的共享数据速度是非常的快,而业务处理过程所耗时间绝大多数是读取时间的几倍乃至上百倍。操作多线程最最重要的一个问题就是共享参数的一致性
,解决的办法就是让多个线程进行通讯。java 关键字volatile,synchronized,持有同一对象等等都是通讯的一种手段。下面我们看看持有同一对象通讯的方法。
package LearnThread.learnthread;
public class MyThread implements Runnable{
private Object phone;
private MyCounter count;
public MyThread(MyCounter count,Object phone) {
this.phone = phone;
this.count = count;
}
@Override
public void run() {
System.out.println("MyThread1开跑前=" + count.getRunNum());
for (int i = 0; i < count.getRunNum(); i++) {
System.out.println("MyThread1=" + (i+1) + "=" + count.getRunNum());
count.setRunNum(count.getRunNum() - 1);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package LearnThread.learnthread;
public class MyThread2 implements Runnable{
private Object phone;
private MyCounter count;
public MyThread2(MyCounter count,Object phone) {
this.phone = phone;
this.count = count;
}
@Override
public void run() {
System.out.println("MyThread2开跑前=" + count.getRunNum());
for (int i = 0; i < count.getRunNum(); i++) {
System.out.println("MyThread2=" + (i+1) + "=" + count.getRunNum());
count.setRunNum(count.getRunNum() - 1);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package LearnThread.learnthread;
public class MyCounter {
private Integer runNum = 0;
public MyCounter(Integer runNum) {
this.runNum = runNum;
}
public void setRunNum(Integer runNum) {
this.runNum = runNum;
}
public Integer getRunNum() {
return runNum;
}
public static void main(String[] args) {
MyCounter count = new MyCounter(20);
Object phone = new Object();
MyThread run1 = new MyThread(count,phone);
MyThread2 run2 = new MyThread2(count,phone);
Thread thread1 = new Thread(run1);
Thread thread2 = new Thread(run2);
thread1.start();
thread2.start();
}
}
执行结果:19,17,15都不见了。此时有人会说:你这代码没加上同步块啊
说得非常好。我在MyThread和MyThread2的run方法加上一样的同步块,如下
增加了同步块,但加锁的都是自己。然并卵,在极端情况下还是会出现如下的结果:13不见了。
原因就是因为两个线程没有建立起有效的通讯。要建立起有效的通讯两个线程同步块必须持有同一个对象,将代码中的synchronized (this) --> synchronized (count) 就解决问题
将MyThread,MyThread2的run方法修改如下,会有什么效果,请看。MyThread的等待时间是100毫秒,MyThread2的等待时间是1毫秒(请自行修改代码)
执行结果
大部份时间是mythread2执行的
synchronized (count) {
count.wait(1);
System.out.println("MyThread2=" + (i+1) + "=" + count.getRunNum());
count.setRunNum(count.getRunNum() - 1);
count.notify();
//Thread.sleep(1);
}
将Mythread2的count.notify()放开后,执行结果如下
整个时间是交替进行的。再次证明要建立线程间的通讯,各线程应当持有同一对象。
看完这篇基础文章,你应该会对线程概念有个大致的了解,它也不是那么难。
如果你觉得这篇文章对你有用,请给个三赞吧