创建线程
线程是进程中的执行单元,一个进程可以有多个线程。
Java VM 启动的时候会有一个进程—-java.exe。
该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。扩展:其实更细节说明jvm,jvm启动不止一个线程,至少还有一个负责垃圾回收机制的线程。
创建线程的第一种方式:继承Thread类
步骤:- 定义类继承Thread。
- 复写Thread类中的run方法。run方法用于存储线程要运行的代码
- 调用线程的start方法。只有调用start方法,线程才执行
link1
发现运行结果每一次都不同。
因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)cpu在做着快速的切换,以达到看上去是同时运行的效果。我们可以把多线程的运行行为想象成在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。线程都有自己的名称,默认是 Thread-编号 该编号从0开始。
获取当前线程对象:
static Thread currentThread()
获取线程名称:getName()
设置线程名称:setName()
或者构造函数。如果想要实现一个多窗口同时卖票的应用,为了使多个线程使用一个票数变量,可以使用定义静态变量的方法 link2
但是静态变量生命周期太长,十分浪费资源。所以就需要一种新的创建线程的方法。创建线程的第一种方式:实现Runable接口
步骤:
- 定义类实现Runnable接口
- 覆盖Runnable接口中的run方法,将线程要运行的代码存放在该run方法中。
- 通过Thread类建立线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
实现方式好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
两种方式区别:
- 继承Thread : 线程代码存放Thread子类run方法中。
- 实现Runnable,线程代码存在接口的子类的run方法。
多线程的安全问题
同步代码块解决安全问题
当多条语句在操作同一个线程 共享数据 时,一个线程对多条语句只执行了一部分,还没有执行完,此时 另一个线程参与进来执行就会导致共享数据的错误。例如将买票问题 link1
为了解决这个问题,可以采用 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行 的方法解决。在java中,可以使用同步代码块解决
synchronized(对象) { //code }
link2
加入同步锁后比较消耗资源。以一个银行存款的例子分析同步代码块。link3
同步函数解决安全问题
当整个函数中的代码都需要同步时,可以使用同步函数。
修改link3中的银行存款的例子 link4
修改卖票的例子 link5同步函数中没有定义Object对象作为锁,因为同步函数中使用的锁是this。
静态同步函数中使用的锁是Class(类名.class),因为静态方法中不可以定义this。单例设计模式
懒汉式:class Single{ private static final Single s = new Single(); private Single(){} public static Single getInstance(){ return s; //只有一句代码,没有同步问题 } }
饿汉式:
class Single{ private static Single s = null; //s是共享数据,可能产生安全问题 private Single(){} public static Single getInstance(){ if(s==null){ //双重判断 synchronized(Single.class){ if(s==null) s = new Single(); } } return s; } }
懒汉式和饿汉式的不同:
- 懒汉式的特点在于实例的延迟加载,多线程访问时会出现安全问题,所以要加同步解决,使用双重判断的方法解决效率问题,使用的锁是该类的字节码文件对象,就是类名.class 文件
死锁
死锁产生的原因是 同步中嵌套同步,但是用的是不同的锁。
例如 link6
多线程之间的通信
假设现在有一个程序,一个线程输入数据,一个线程输出数据。可能出现不同步的现象发生。link1。
为了解决这个现象,需要加入同步。link2
现在打印的输出依然不是期望中的交替打印,可以使用 等待唤醒机制 来实现。link3wait()
,notify()
,notifyAll()
都要使用在同步中,因为要对持有监视器(锁)的线程进行操作。
这几个方法是定义在Object类中的,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。对以上代码的优化。link4
生产者消费者模型
假设需求:一个生产者生产商品,一个消费者购买商品,使两个线程能够交替执行,跟以上的代码类似 link5
但是当有多个线程时,如有4个线程(2个消费者,2个生产者)就会出现同一个商品被消费2次,或生产2个商品只有一个被消费的情况 link6
修改上面的代码,只需要
- 27行和39行的if改为while,多次判断标志
- 35行和46行的notify()修改为notifyAll(),唤醒全部线程(即将对方线程也唤醒)。即可
- link7
JDK 1.5对于同步进行了升级,用Condition和Lock取代了原来的synchronized。所以,对于上述link7的修改就是 link8
一个Lock可以绑定多个Condition,所以对于生产者消费者模型,可以让生产者只唤醒消费者,消费者只唤醒生产者 link9
停止线程
JDK中有stop()方法停止线程,但是已经过时。
现在想要停止线程,方法就是要让 run()方法结束
如 利用flag控制循环 link10但是如果有wait()方法,程序进入冻结状态,无法读取标记,程序始终无法终止。此时就要对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
为此,Thread类提供了interrupt()方法。
调用interrupt()方法,会将程序强制恢复到运行状态,但是会抛出异常,可以直接在catch中更改flag标记。link11守护线程
守护线程可以理解为后台线程,当前台线程运行时,守护线程也运行,当前台线程结束时,守护线程自动结束。link12
Join()线程
当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
link13设置优先级
setPriority(),一般使用常量MAX_PRIORITY(最高优先级),
MIN_PRIORITY (最低优先级),NORM_PRIORITY (默认优先级)多种不同的创建线程的方法 link14