首先先看看这个问题,程序和进程有什么区别?
我之前的理解是进程就是运行中的程序。但是读了操作系统后发现,其实进程是为了多道程序同时执行而引申出来的。因为操作系统同一时间只能运行一个程序(单cpu)
对于java来说,进程就是一个jvm。而线程就是一个Thread。当然jvm启动时还会启动很多线程,比如最熟悉的垃圾回收线程(应该是守护线程)。
没有真正意义上的多线程,正如没有真正意义上的多进程。原因如操作系统(单cpu)
所以我们常常说的是并发,而不是并行!!!
Main 函数本身其实就是一个线程,我们称他为主线程;
实现多线程我们可以继承 Thread 类,也可以继承 Runnable 接口;
继承 Thread 创建线程 --重写run方法--调用start方法启动线程
为什么我重写的是 run 方法,却要调用 start 方法来启动它?
父类实现算法,子类实现细节------ 模板模式
abstract class Diagram
{
abstract protected void print(int size);
abstract protected void printContent(String msg);
public final void display(String msg)
{
int len = msg.getBytes().length;
print(len);
printContent(msg);
print(len);
}
}
class StarDiagram extends Diagram{
@Override
protected void print(int size)
{
for(int i = 0;i<size+2;i++)
{
System.out.print(c);
}
System.out.println();
}
@Override
protected void printContent(String msg)
{
System.out.print("*");
System.out.print(msg);
System.out.println("*");
}
}
public class TemplateTest
{
public static void main(String[] args)
{
Diagram d1 = new StarDiagram('*');
d1.display("wangwenjun");
}
}
我们可以看到父类 Diagram 中的 display 方法规范了算法,也就是将打印上线边线和输出内容的部分进行了算法约束,我们不用关心他的算法逻辑,我们只需要实现他所抽象的两个方法 print 和 printContent 方法
奇怪我们明明实现的是这两个方法,但是为什么运行的时候需要调用 display 方法呢?
将程序的运行逻辑以及算法逻辑交由父类进行管理,子类只需要实现其中功能模块即可
为什么实现模块的抽象方法都是 protected 的呢?
因为我们不想让调用者关注到我们实现的细节,这也是面向对象思想封装的一个体现;
为什么 display 方法是不可继承的呢?
因为算法一旦确定就不允许更改,更改也只允许算法的所有者也就是他的主人更改,如果调用者都可通过继承进行修改,那么算法将没有严谨性可言;
java的多线程是通过线程轮流切换实现的。对应的就是操作系统的线程(好像是这样。。)
线程的状态
1.初始化:实例化一个Thread子类
2.运行状态:调用start,并且真正分配到cpu资源
3.阻塞状态:调用sleep、wait、放弃cpu资源,并且是在阻塞的那几秒
4.就绪状态:可以执行,等待cpu资源,这里应该是一个队列,但并不是FIFO,具体要看操作系统的分配算法。java的话应该是时间片轮转吧。
5.死亡状态:正常退出,或者异常退出
通过实现 Runnable 接口创建线程--重写run方法 -- new Thread(new Runnable())
其实 Runnable 只是一个任务的接口,他并不是一个线程,他的出现是为了将线程和业务执行逻辑分离
Runnable 和 Thread 的区别
Runnable 就是一个可执行任务的标识而已,仅此而已;而 Thread 才是线程所有 API 的体现;
继承了 Thread 父类就没有办法去继承其他类,而实现了 Runnable 接口也可以继承其他类并且实现其他接口,这个区别也是很多书中千篇一律提到的,其实 Java 中的对象即使继承了其他类,也可以通过再构造一个父类的方式继承很多个类,或者通过内部类的方式继承很多个类,因此这个区别个人觉得不痛不痒;
将任务执行单元和线程的执行控制区分开来,这才是引入 Runnable 最主要的目的,Thread 你就是一个线程的操作者,或者独裁者,你有 Thread 的所有方法,而 Runnable只是一个任务的标识,只有实现了它才能称之为一个任务,这也符合面向对象接口的逻辑,接口其实就是行为的规范和标识;
runnable和thread其实放在一起比较没什么意思,他们关注的东西就不是一个事情,
一个负责线程本身的功能,另外一个则专注于业务逻辑的实现,说白了 Runnable 中的 run
方法,即使不再 Thread 中使用,他在其他地方照样也能使用,并不能说他就是一个线程;
多线程之锁
所谓加锁,就是为了防止多个线程同时操作一份数据,如果多个线程操作的数据都是各
自的,那么就没有加锁的必要共享数据的锁对于访问他们的线程来说必须是同一份,否则锁只能私有的锁,各锁个的,起不到保护共享数据的目的,试想一下将 Object lock 的定义放到 run 方法里面,每次都会实例化一个 lock,每个线程获取的锁都是不一样的,也就没有争抢可言,说的在通俗一点甲楼有一个门上了锁,A 要进门,乙楼有一个门上了锁 B 要进门,A 和 B 抢的不是一个门,因此不存在数据保护或者共享;
锁的定义可以是任意的一个对象,该对象可以不参与任何运算,只要保证在访问的多个线程看来他是唯一的即可;
当同一份的数据被多个线程操作的时候才考虑同步!!!
同一份数据
如果不同的线程访问的不是同一份数据,就没有必要加锁保持同步,就像我之前举例 A,B 两个人进门的一样
多个线程访问
多个线程访问的时候采取考虑同步,如果一份数据只是被一个线程访问,就没有必要进行同步;
多个线程同步的代码块必须是同一个锁
单例模式
如何实现?锁?效率!双重锁?有问题,要加volatile,没什么意思。
懒汉?饿汉?静态内部类实现
生产者消费者
线程池