【相关名词解释】
1. 程序
一组指令的集合,是静态的。
2. 进程
一个程序的一次运行
当程序启动后,操作系统就会给这个程序分配一个进程的ID,并且会给他分配一块独立的内存空间。
若一个程序被启动了多次,就有多个进程
多个进程之间无法共享数据
如果两段代码需要进行数据交互,成本比较高(通过硬盘或网络)
进程之间的切换成本也比较高,现在操作系统都支持多任务,操作系统在每一个任务之间进行任务,要给进程做镜像,要记录当前进程的状态,执行到哪个指令了
操作系统分配资源的最小单位是进程
每一个进程至少有一个线程
3. 线程
进程中的其中一条执行路径,多个线程会属于同一个进程
这多个线程会共享同一个进程中的一些资源,比如Java中堆内存的数据,比如方法区的数据等
如果两段代码需要进行数据交互,可以直接在内存中操作(会出现安全问题)
线程之间的切换,需要记录的数据少很多,因为很多线程数据是共享的,这部分不需要单独镜像,只需要记录每一个线程要执行的下一条指令
CPU调度的最小单位是线程
4. JVM
(1)方法区
(2)堆
(3)Java虚拟机栈
(4)本地方法栈
(5)程序计数器:记录每一个线程要执行的下一条指令
Java程序一个main方法,就是一个进程,但是可以在main中,再开启多个线程
Java程序后台有几个线程是默默运行的:GC线程、异常的检查和处理、类加载
5. 并行和并发
并行:指两个或多个事件在同一时刻发生(同时发生),指在同一时刻,有多个指令在多个处理器上同时执行
并发:指两个或多个事件在同一事件段内发生,在同一时刻只能有一条指令执行,但多个进程的指令被快速轮换执行,使得在宏观上有多个进程同时执行的效果
6. 线程调度
(1)分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
(2)抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调用
【多线程】
创建方式有四种:
继承Thread、实现Runnable接口、实现Callable接口、线程池(这里学两种)
1. 多线程的创建和启动方式一:继承Thread类
步骤:
(1)编写一个类,让他继承Thread类
(2)重写父类的public void run(){},该方法不是由程序员调用,而是线程调度时,自动调用
要让一个线程做什么事,必须把这个代码写到run中,run()的方法体,称为线程体
(3)创建自定义线程类的对象
(4)启动线程
调用自定义线程类对象的start()方法——启动线程方法
【注】不要手动调run()方法,run()方法是顺序执行,start()方法是多线程
如:
public class ThreadTest {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for(int i = 2;i <= 10;i+=2){
System.out.println("在线程内:"+i);
}
}
}.start();
for (int i = 1;i<10;i+=2){
System.out.println("在main中:"+i);
}
}
}
2. 多线程的创建和启动方式二:实现Runnable接口
步骤:
(1)编写线程类,实现Runnable
(2)重写接口的抽象方法public void run()
(3)创建自定义对象类
(4)创建一个Thread对象类,让Thread对象类代理自定义线程类对象
(5)启动线程
如:
public class RunnableTest {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t = new Thread(my);
t.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 2;i<=10;i+=2){
System.out.println("线程内:"+i);
}
}
}
3. Thread类的方法
(1)构造方法
public Thread():分配一个新的线程对象
public Thread(String name):分配一个指定名字的新的线程对象
public Thread(Runnable target):分配一个带有指定目标新的线程对象
public Thread(Runnable target,String name):分配一个带有指定目标的星的线程对象并命名
String getName():获取线程名字
如果没有手动指定线程名称,默认是Thread-编号,从0开始
如果需要手动指定线程名称,可以通过构造器,或者setName(String name)方法
static Thread currentThread():获取执行当前语句的线程对象
(2)线程优先级
public final int getPriority():返回线程优先级
public final void setPriority(int newPriority):改变线程优先级
线程优先级高的,有更多的机会/概率被优先调用
t.setPriority();
java.lang.IllegalArgumentException
当设置的优先级不在MIN_PRIORITY到MAX_PRIORITY范围内,就会报这个异常
正常范围:1~10
(3)和线程的状态有关的方法
public static void sleep(long millis)throws InterruptedException:线程休眠,单位(毫秒)
public static void yield():让当前线程暂停一下,让出CPU,但是下次CPU还是原来的调用
public join():等待该线程终止。该线程是调用join方法的线程(加塞、阻塞)
在()内添加时间,就是阻塞该时间,时间到了之后继续抢CPU
如:
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread("偶数线程"){
public void run(){
for(int i = 2;i<=100;i+=2){
System.out.println(i+getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
for(int i = 1;i<100;i+=2){
System.out.println(i+Thread.currentThread().getName());
if(i == 5){
Thread.currentThread().join();
//让奇数线程彻底暂停,让出全部CPU,直到偶数线程走完再继续
}
}
}
}
线程练习1:加塞
自定义线程类ChatThread:问是否结束(输入Y/y结束),如果输入的不是y,继续问是否结束,知道输入y才结束。
主线程打印[1,10],每隔10毫秒打印一个数字,现在当主线程打印完5之后,就让自定义线程类加塞,直到自定义线程类结束,主线程再继续
代码:
import java.util.Scanner;
class ChatThread extends Thread{
Scanner input = new Scanner(System.in);
@Override
public void run() {
while(true){
System.out.println("请确认是否结束?Y/y");
char answer = input.next().charAt(0);
if(answer == 'Y'||answer == 'y'){
break;
}
}
}
}
public class TestThread {
public static void main(String[] args) {
for(int i = 1;i<=10;i++){
System.out.println(i);
if(i == 5){
ChatThread t= new ChatThread();
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
先把该写的逻辑写全,然后补上try…catch
线程练习2:修改和加塞
编写龟兔赛跑多线程程序,设赛道长度为30米
兔子的速度为10米每秒,兔子每跑完10米休眠的时间10秒
乌龟的速度是1米每秒,乌龟每跑完10米的休眠时间是1秒
要求:要等兔子和乌龟的线程结束,主线程(裁判)才能公布最后的结果
提示:System.currentTimeMillis()方法扩返回当前时间的毫秒值(long 类型)
分析:跑道长度为30米,即从0循环到30
兔子速度为10米每秒,即兔子跑1米用时100毫秒
乌龟速度为1米每秒,即乌龟跑1米用时1000毫秒
代码:
兔子类:
public class Rabbit extends Thread{
long time;
public Rabbit(String name) {
super(name);
}
@Override
public void run() {
long start = System.currentTimeMillis();
for(int i = 1;i<=30;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"跑了"+i+"秒");
if(i == 10||i == 20){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
time = end-start;
System.out.println(getName()+"耗时:"+(end-start));
}
}
乌龟类
public class Gui extends Thread {
long time;
public Gui(String name) {
super(name);
}
@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 1; i <= 30; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "跑了" + i + "秒");
if (i == 10 || i == 20) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end = System.currentTimeMillis();
time = end-start;
System.out.println(getName()+"耗时:"+(end-start));
}
}
测试类:
public class Competition {
public static void main(String[] args) {
Gui g = new Gui("乌龟");
Rabbit r = new Rabbit("兔子");
g.start();
r.start();
try {
r.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
g.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(g.time > r.time){
System.out.println("兔子胜");
}else {
System.out.println("乌龟胜");
}
}
}
4. 线程的停止
原本有stop(),但是已过时。
替换为flag,放入for循环的条件判断中,如:
通过join()方法等待快的线程结束,结束后,改变慢线程的boolean值,从而实现慢线程停止
public class StopTest {
public static void main(String[] args) {
Odd o = new Odd();
Even e = new Even();
o.start();
e.start();
try {
e.join();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
o.setFlag(false);
}
}
class Odd extends Thread{
boolean flag = true;
@Override
public void run() {
for(int i = 1;i<=100&&flag;i+=2){
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
class Even extends Thread{
@Override
public void run() {
for(int i = 2;i <= 100;i+=2){
System.out.println(i);
}
}
}
线程练习3:龟兔赛跑优化(通过比较距离)
将重复代码进行优化,整合为一个类,通过创建线程对象进行调用
代码为:
Sporter类:
public class Sporters extends Thread{
private int distance;
private final int MAX_DISTANCE = 30;
private static boolean flag = true;
private int restTime;
private int runTime;
public Sporters(String name, int restTime, int runTime) {
super(name);
this.restTime = restTime;
this.runTime = runTime;
}
@Override
public void run() {
while(distance <MAX_DISTANCE && flag){
try {
Thread.sleep(runTime);
distance++;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"跑了"+distance);
if(distance == 10 || distance == 20){
try {
Thread.sleep(restTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if(distance == MAX_DISTANCE){
System.out.println(getName()+"已经到达终点");
flag = false;
}
}
public int getDistance() {
return distance;
}
}
测试类:
public class NewCompetition {
public static void main(String[] args) {
Sporters tu = new Sporters("兔子", 10000, 100);
Sporters gui = new Sporters("乌龟", 1000, 1000);
tu.start();
gui.start();
try {
tu.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
gui.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (tu.getDistance() > gui.getDistance()) {
System.out.println("兔子胜");
} else {
System.out.println("乌龟胜");
}
}
}
5. 线程安全
当使用多个线程访问同一资源(文件、变量、记录等)的时候,如多个线程只有读操作,就不会发生安全问题,但是如果多个线程中对资源有读写操作,就容易出现安全问题
(1)局部变量是不能共享的
因为每一个线程对象,都会调用一次run方法,而方法的每一次调用在“栈”中都会开辟独立内存空间
(2)实例变量可以共享,但前提是同一个对象
即,不使用继承,而使用接口实现线程去代理或者使用静态变量
如:
public class Tickets {
public static void main(String[] args) {
Tick t = new Tick();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Tick implements Runnable{
private int i = 10;
@Override
public void run() {
while(i>1){
i--;
System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+i+"张");
}
}
}
6. 如何解决安全问题?
(1)使用同步机制
同步就是给某段代码加“锁”
锁是一个对象,又被称为监视器对象。就是监视使用共享数据的这几个线程的调度执行情况
Java分为3个对象
①对象头:当前对象类所属的指针、MarkWord(GC、锁标记)、数组长度
②对象的实例变量
③对齐空白
即每一个对象都有一个标记位,标记当前哪个线程“占有”这个锁对象
这个标记位会记录这个线程的ID,只有“占有”锁对象的线程才有机会执行被锁的代码,称为同步代码
(2)如何给代码加锁:synchronized
①同步代码:锁整个方法
【修饰符】synchronized 返回类型 方法名(【形参列表】){ 方法体 }
②同步代码块:锁方法里的一部分
synchronized(锁对象){
需要被锁起来的代码
}
如:将上面的卖票过程进行优化:
public class Tickets {
public static void main(String[] args) {
Tick t = new Tick();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Tick implements Runnable{
private int i = 100;
@Override
public void run() {
while(i>1){
oneTicket();
}
}
public synchronized void oneTicket(){
if(i>=1){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
i--;
System.out.println(Thread.currentThread().getName()+"卖出一张,剩余"+i+"张");
}
}
}
(3)什么时候释放锁?
当synchronized锁住的代码全部执行完才会释放锁
(4)锁对象的选择问题
①任意类型的对象都可以当做监视线程的锁对象,无限制
②必须保证使用共享数据的多个线程,使用的是同一个锁对象
③同步方法的锁对象是不能自由选择的,是默认的
非静态方法的锁对象是this对象
静态方法的锁对象是当前类的Class对象
如,将上题再次进行优化:添加票对象
public class Ticket {
private final int MAX_TOTAL = 10;
private int total = MAX_TOTAL;
public synchronized void sale(){
if(total > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
total --;
System.out.println(Thread.currentThread().getName()+"卖出一张票,还剩"+total);
}else{
System.out.println("没有票了");
}
}
public boolean check(){
return total>0;
}
}
main中启动线程:
public class Test {
public static void main(String[] args) {
Ticket t = new Ticket();
new Thread("窗口一"){
@Override
public void run() {
while(t.check()){
t.sale();
}
}
}.start();
new Thread("窗口二"){
@Override
public void run() {
while(t.check()){
t.sale();
}
}
}.start();
}
}
7. 什么是单例设计模式
(1)设计模式
程序员根据自己的经验总结出来的代码模板、套路
常见设计模式有23种
(2)单例设计模式
某个类的对象在整个应用程序中只有唯一的一个
如:Java中的JVM对象
(3)如何实现单例
①构造器私有化
②在类的内部创建好这个类的唯一对象
(4)单例设计模式的形式
①饿汉式
无论是否用到这个类的对象,在类初始化的时候,直接创建对象
②懒汉式
等需要对象时再创建
8. 线程通信的等待唤醒机制
wait()
notify():一般使用notifyall()
当这两个方法不是线程的监视器对象调用时,会报IllegalMonitorStateException(非法监视器)异常
监视器对象:同步锁对象