多进程:在操作系统中同时运行多个任务(程序)
多线程:在同一应用程序中有多个顺序流同时执行
线程与进程的区别:
进程对应的是一个服务,线程是进程中的执行单元
一个进程可以包含多个线程,一个线程一定属于某个进程
多线程特点:线程是异步的,线程是并发的,多个线程同时工作,线程与线程之间是互不干扰的
例子:火车站(进程)和售票窗口(线程)火车站 的关系
三种方法:
继承Thread类
实现Runnable接口 / 通过匿名内部类来创建线程对象
使用Callable结合Task实现多线程
方法一:继承Thread类,重写run()
方法
xxxxxxxxxx
361package com.os467.thread;
2
3public class ThreadDemo01 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象
8 ThreadImpl01 threadImpl01 = new ThreadImpl01();
9 ThreadImpl01 threadImpl02 = new ThreadImpl01();
10
11 //开启线程
12 threadImpl01.start();
13 threadImpl02.start();
14
15 }
16
17}
18
19
20class ThreadImpl01 extends Thread{
21
22 /**
23 * 让程序跑起来的方法
24 */
25
26 public void run() {
27
28 for (int i = 0; i < 10; i++) {
29
30 System.out.println("第"+(i + 1)+"个数为"+i);
31
32 }
33
34
35 }
36}
xxxxxxxxxx
31//设置线程的名称
2threadImpl01.setName("t1");
3threadImpl02.setName("t2");
获取当前正在运行的线程
xxxxxxxxxx
81class ThreadImpl01 extends Thread{
2
3 //获取当前正在运行的线程
4 Thread thread = Thread.currentThread();
5
6 System.out.println(thread.getName()+"正在执行");
7
8}
方法二:实现一个Runnable接口
此方法的实现类只是一个可运行类,不能被称为线程类
xxxxxxxxxx
531package com.os467.thread;
2
3public class ThreadDemo02 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象
8 RunnableImpl02 runnableImpl02 = new RunnableImpl02();
9
10 //创建线程类
11 Thread thread1 = new Thread(runnableImpl02);
12
13 Thread thread2 = new Thread(runnableImpl02);
14
15
16 //设置线程名称
17 thread1.setName("t1");
18
19 //设置线程名称
20 thread2.setName("t2");
21
22 //开启线程
23 thread1.start();
24
25 //开启线程
26 thread2.start();
27
28
29 }
30
31}
32
33/**
34 * 可运行类
35 */
36class RunnableImpl02 implements Runnable{
37
38
39
40 public void run() {
41
42 for (int i = 0; i < 10; i++) {
43
44 //获取当前正在运行的线程
45 Thread thread = Thread.currentThread();
46
47 System.out.println(thread.getName()+"正在执行:"+(i + 1)+"个数为"+i);
48
49 }
50
51 }
52
53}
xxxxxxxxxx
441package com.os467.thread;
2
3public class ThreadDemo03 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象
8 Thread thread1 = new Thread(new Runnable() {
9
10 public void run() {
11
12 for (int i = 0; i < 10; i++) {
13
14 System.out.println(Thread.currentThread().getName()+" : "+i);
15
16 }
17
18 }
19 });
20
21 //创建线程对象
22 Thread thread2 = new Thread(new Runnable() {
23
24 public void run() {
25
26 for (int i = 0; i < 10; i++) {
27
28 System.out.println(Thread.currentThread().getName()+" : "+i);
29
30 }
31
32 }
33 });
34
35 thread1.setName("t1");
36 thread2.setName("t2");
37
38 //开启线程
39 thread1.start();
40 thread2.start();
41
42 }
43
44}
新建状态
创建出线程对象之后,线程就处于新建状态
就绪状态
当一个线程调用了start()
方法之后,该线程处于就绪状态,处于就绪状态的线程拥有争夺CPU资源的使用权力
当一个线程争夺到了CPU使用权之后,就回去执行线程,运行run方法,这个时候的线程处于运行状态
当线程在运行的过程中出现了控制台打印、睡眠sleep等需要等待的操作,这个时候线程会处于阻塞状态,处于阻塞状态的线程,会释放CPU使用权
当一个线程执行完run方法中所有的代码就会跳出run方法,这个时候线程处于死亡状态 isAlive()方法
可以判断线程是否存活
xxxxxxxxxx
441package com.os467.thread;
2
3public class ThreadDemo04 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象,该线程处于新建状态
8 ThreadImpl02 threadImpl01 = new ThreadImpl02();
9 ThreadImpl02 threadImpl02 = new ThreadImpl02();
10
11 threadImpl01.setName("t1");
12 threadImpl02.setName("t2");
13
14 //该线程处于就绪状态
15 threadImpl01.start();
16 threadImpl02.start();
17
18 }
19
20}
21
22
23class ThreadImpl02 extends Thread{
24
25
26 public void run() {//当某一个线程争夺到cpu使用权后才会进入run方法
27
28 for (int i = 0; i < 10; i++) {
29
30 System.out.println(Thread.currentThread().getName() + "======>"+i);
31
32 //睡眠的方法,可以让程序处于阻塞状态
33 try {
34 Thread.sleep(500);
35 } catch (InterruptedException e) {
36 e.printStackTrace();
37 }
38
39 }
40
41 }
42
43 //当某一线程执行完了run方法,那该线程处于死亡状态
44}
Run方法和start方法的区别
Run是执行方法,start是启动方法
start启动不一定执行run方法
xxxxxxxxxx100 1package com.os467;23import com.os467.Annotion.Table;45import java.lang.annotation.Annotation;6import java.lang.reflect.Field;789public class ORM {1011 private ORM(){1213 }1415 public static String getSql(Object obj){1617 StringBuilder stringBuilder = new StringBuilder();1819 //获取obj字节码对象20 Class aClass = obj.getClass();2122 Table annotation = (Table)aClass.getAnnotation(Table.class);2324 //表的名称25 String table = null;2627 if (annotation != null){2829 //获取表的名称30 table = annotation.value();3132 stringBuilder.append("select * from "+ table +" where ");3334 }3536 //获取属性字节码对象37 Field[] declaredFields = aClass.getDeclaredFields();3839 for (Field declaredField : declaredFields) {4041 //打破封装42 declaredField.setAccessible(true);4344 com.os467.Annotion.Field annotation1 = declaredField.getAnnotation(com.os467.Annotion.Field.class);4546 //注解不为空的情况下拼接47 if (annotation1 != null){4849 try {5051 //获取属性值52 Object o = declaredField.get(obj);5354 //检查email属性是否有多个","隔开55 if (o != null && o.toString().contains(",")){5657 stringBuilder.append(annotation1.value() + " in(" + o + ") and");5859 }else if (o != null && !o.toString().equals("0")){6061 stringBuilder.append(annotation1.value() + " = ");6263 if (o instanceof String){6465 stringBuilder.append("'" + o + "'" + " and ");6667 }6869 if (o instanceof Integer){7071 stringBuilder.append(o + " and ");7273 }7475 }767778 } catch (IllegalAccessException e) {79 e.printStackTrace();80 }8182 }8384 }8586 String s = stringBuilder.toString();8788 //替换掉最后的and 89 s = s.substring(0,s.length() - 4);9091 if (s.contains("=")){9293 return s;9495 }9697 return null;98 }99100}java
哪个线程的优先级比较高,抢到的CPU时间片的概率就多一些,java采用的就是抢占式调度模型
平均分配CPU时间片,每个线程占用的CPU时间片时间长度一样,平均分配,一切平等,有一些编程语言,线程调度模型采用 的是这种方式
使用哪种方式创建线程比较好?
实现runnable接口,因为我们以后是面向接口开发
因为如果继承了Thread类,那么类的可扩展性就降低了
以后我们在写程序,尽量使用Runnable接口方式来创建线程对象,面向接口开发,程序的可扩展性会提高
问题:以下代码一共开启了几个线程?
xxxxxxxxxx
531package com.os467.thread;
2
3public class ThreadDemo05 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象
8 Thread thread1 = new Thread(new MyRunnable());
9 Thread thread2 = new Thread(new MyRunnable());
10 Thread thread3 = new Thread(new MyRunnable());
11
12 thread1.setName("t1");
13 thread3.setName("t3");
14
15 thread1.start();
16 thread2.run();
17 thread3.start();
18
19 for (int i = 0; i < 10; i++) {
20
21 System.out.println(Thread.currentThread().getName() + " ==> "+i);
22
23 }
24
25 }
26
27
28}
29
30
31class MyRunnable implements Runnable{
32
33
34 public void run() {
35
36
37 for (int i = 0; i < 10; i++) {
38
39 System.out.println(Thread.currentThread().getName() + " ==> "+i);
40
41 }
42
43 try {
44 Thread.sleep(100);
45 } catch (InterruptedException e) {
46 e.printStackTrace();
47 }
48
49
50 }
51
52
53}
答案:一共开启了4个线程:
两个普通线程 调用了start()
方法
守护线程(后台线程):垃圾回收线程
主线程(main线程
):JVM调用主函数
在主线程调用了start方法之后,会开辟分支栈,每个栈都会去执行不同的任务,栈与栈之间的资源是不共享的
堆的资源是共享的,因为JVM上只有一个堆,只要你去创建引用数据类型的实例,都是在堆中开辟空间
方法区中的资源也是共享的,因为内存中只能有一个方法区
获取、设置线程优先级方法
xxxxxxxxxx
21//获取某个线程的优先级,java默认情况下线程优先级是5
2int priority1 = thread1.getPriority();
xxxxxxxxxx
21//设置线程优先级方法,1-10,1优先级最低,10最高
2thread1.setPriority(1);
暂停当前正在执行的线程对象,并执行其他线程
yield()
方法的执行会让当前线程从"运行状态"回到"就绪状态"
Thread.yield();
join()
当前线程进入阻塞,指定线程执行,直到指定线程结束当前线程才可以继续
线程a{
线程b.join()
}
将线程b合并入线程a,线程a等待线程b结束后才继续运行
注意:线程合并必须要有两个不同的线程
suspend()
线程自己把自己挂起
resume
线程自己把自己唤醒
龟兔赛跑案例
兔子线程类
xxxxxxxxxx
511package com.os467.game;
2
3//兔子线程
4public class Rabbit extends Thread {
5
6 private String name;
7
8 public Rabbit(String name) {
9 //给当前的线程实例设置名称
10 super(name);
11 this.name = name;
12 }
13
14
15
16 public void run() {
17
18 //模拟整个赛道跑步的过程
19 for (int i = 0; i <= 900; i+=100) {
20
21 if(i == 800){
22
23 System.out.println(Thread.currentThread().getName()+"开始睡觉");
24
25 //自己把自己挂起
26 Thread.currentThread().suspend();
27
28 }
29
30 System.out.println(Thread.currentThread().getName() + "跑了"+(i+100)+"米");
31
32 try{
33
34 //模拟睡眠
35 Thread.sleep(1000);
36
37
38 }catch (Exception e){
39
40 e.printStackTrace();
41
42 }
43
44 }
45
46
47 System.out.println(Thread.currentThread().getName()+"跑完了全程,最终比赛失败");
48
49 }
50
51}
乌龟线程类
xxxxxxxxxx
561package com.os467.game;
2
3//乌龟线程类
4public class Tortoise extends Thread {
5
6 private String name;
7
8 //兔子的引用
9 private Rabbit rabbit;
10
11 public Tortoise(String name, Rabbit rabbit) {
12 //给当前的线程实例设置名称
13 super(name);
14 this.name = name;
15 this.rabbit = rabbit;
16 }
17
18 //唤醒兔子的方法
19 public void resumeRabbit(){
20
21 System.out.println(Thread.currentThread().getName()+" 唤醒了 "+rabbit.getName());
22
23 //调用唤醒的方法
24 this.rabbit.resume();
25
26 }
27
28
29 public void run() {
30
31 //模拟整个赛道跑步的过程
32 for (int i = 0; i <= 950; i+=50) {
33
34 System.out.println(Thread.currentThread().getName() + "跑了"+(i+50)+"米");
35
36 try{
37
38 //模拟睡眠
39 Thread.sleep(1000);
40
41
42 }catch (Exception e){
43
44 e.printStackTrace();
45
46 }
47
48 }
49
50 //乌龟唤醒兔子
51 this.resumeRabbit();
52
53 System.out.println(Thread.currentThread().getName()+"跑完了全程,取得了胜利");
54
55 }
56}
测试类
xxxxxxxxxx
191package com.os467.game;
2
3public class Test {
4
5 public static void main(String[] args) {
6
7 //创建兔子线程
8 Rabbit rabbit = new Rabbit("兔子");
9
10 //创建乌龟线程
11 Tortoise tortoise = new Tortoise("乌龟",rabbit);
12
13 //开启线程
14 rabbit.start();
15 tortoise.start();
16
17 }
18
19}
//tl.stop()
中断线程
//interrupt
中断睡眠
//tl.isAlive()
判断指定线程是否处于活动状态
用户线程
守护线程(后台线程)
一般守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
守护线程一般指的是后台线程,守护线程一般会去守护一个用户线程,用户线程启动,守护线程随之启动,用户线程结束,守护线程也结束,例如垃圾回收线程
设置守护线程方法:普通线程对象.setDaemon(true)
xxxxxxxxxx
501package com.os467;
2
3public class ThreadDemo01 {
4
5 public static void main(String[] args) {
6
7 //创建线程对象
8 Thread t1 = new Thread(new createThread());
9
10 //将一个普通线程设置为守护线程
11 t1.setDaemon(true);
12
13 //开启线程
14 t1.start();
15
16 for (int i = 0; i < 10; i++) {
17
18 System.out.println(Thread.currentThread().getName()+" ===> "+i);
19
20 try {
21 Thread.sleep(500);
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25
26 }
27
28 }
29
30}
31
32class createThread implements Runnable{
33
34
35
36 public void run() {
37
38 //守护线程一般就是一个死循环
39 while (true){
40
41 System.out.println("守护线程正在执行");
42
43 try {
44 Thread.sleep(1000);
45 } catch (InterruptedException e) {
46 e.printStackTrace();
47 }
48 }
49 }
50}
定时器
在java的类库中已经写好了一个定时器:java.util.Timer
FutureTask futureTask = new FutureTask(new Callable())
xxxxxxxxxx
541public class ThreadDemo02 {
2
3 public static void main(String[] args) {
4
5 //这个类官方称之为未来任务类,不是线程类
6 FutureTask futureTask = new FutureTask(new Callable() {
7
8 //作用类似于run方法
9
10 public Object call() throws Exception {
11
12 int num = 0;
13
14 for (int i = 0; i < 10; i++) {
15
16 num += i;
17
18 System.out.println(Thread.currentThread().getName() + "===>" + i);
19
20 Thread.sleep(500);
21
22 }
23
24 return num;
25
26 }
27 });
28
29 //创建线程对象
30 Thread thread = new Thread(futureTask);
31
32 //设置线程名称
33 thread.setName("t1");
34
35 //开启线程
36 thread.start();
37
38 //线程结束之后获取线程的返回值
39 try {
40
41 Object o = futureTask.get();
42
43 System.out.println("最终累加的结果为:"+o);
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 } catch (ExecutionException e) {
47 e.printStackTrace();
48 }
49
50
51
52 }
53
54}
缺点:由于需要获取返回值,此方法会造成原线程的阻塞
线程存在不安全问题条件
堆内存和方法区中的内存是数据共享的
栈内存中的数据是不共享的
synchronized(线程共享对象)
虽然常量也能作为线程共享对象,但是会约束到所有执行这块代码的线程,会降低没有线程安全问题的线程的运行效率,我们只保证具有线程安全问题的线程受到约束即可,即提供需要受到约束的线程的共享对象
修饰实例方法,锁住的对象就是this(调用此方法的实例),锁住的内容就是方法内容
public synchronized void method(){}
修饰静态方法,拥有的锁就是类锁,只要传入的对象是属于这个类的,就会使得线程同步(与给同步代码块提供类的字节码对象作为共享对象效果相同)
线程同步之后,每个线程在进入同步代码块之前就会发生等待
synchronized同步代码块
xxxxxxxxxx
321/**
2 * 当t1,t2,t3同时来到同步代码块前时
3 * 当线程卖出一张票的时候,其它线程等待
4 * 先检测对象是不是共享对象,如果是,则两个线程争夺对象锁
5 * 如果t1先抢到这把锁,则优先进入代码块,t2只能在锁池中等待
6 * 当t1执行完代码块中的内容之后,会释放对象锁,这个时候t2才能重新拥有对象锁
7 */
8
9 public void run() {
10
11 while (ticket.getNum() > 0){
12
13 synchronized (this.ticket){
14
15 if (ticket.getNum() > 0){
16
17 //票数-1
18 this.ticket.setNum(this.ticket.getNum() - 1);
19 System.out.println(Thread.currentThread().getName()+
20 "卖出了一张票,票数剩余:"+this.ticket.getNum());
21
22 }
23 }
24
25 try {
26 Thread.sleep(100);
27 } catch (InterruptedException e) {
28 e.printStackTrace();
29 }
30 }
31
32 }
线程A和线程B,各自执行各自的,A不管B,B不管A,谁也不需要等谁,这种编程模型叫做:异步编程模型,其实就是:多线程并发,效率较高
线程A和线程B,在线程A执行的时候,必须等待B线程执行结束,或者说在A线程执行的时候,必须等待B线程执行结束,两个线程之间发生了等待关系,这就是同步程模型,效率较低
锁池
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B,C想要调用这个对象的某个synchronized方法(或者块)之前必须获得该对象锁的拥有权,而恰巧该对象的锁目前正被A所占有,此时B,C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
假设线程A调用了某个对象的wait方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁
wait()
让正在该对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。wait()
方法的调用,会让正在改对象的当前线程进入等待状态此对象其实就是多线程中的共享对象
使用此方法必须保证当前多线程处于线程同步状态
当共享对象是类的实例的时候,可以直接在类的方法中写wait
和notify
方法
notify()方法作用
只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会
notifyAll()
方法的作用
会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会
需求:生产者和消费者关系:
仓库
仓库里面存放的就是生产者生产出来的商品,需要交给消费者去消费
要保证生产者与消费者均衡的状态:
如果仓库中没有产品了,这个时候消费者线程就不能再进行消费了,需要交给生产者线程去生产,如果仓库已经满了,仓库已经有产品的情况下,生产者线程就不用再生产了,要交由消费者线程去消费
生产者与消费者wait、notify
要想使用wait和notify方法,必须是在线程同步的前提下
Consumer
xxxxxxxxxx
541package com.os467.wait;
2
3public class Consumer extends Thread {
4
5 private House house;
6
7 public Consumer(String name,House house){
8
9 super(name);
10 this.house = house;
11
12 }
13
14
15 public void run() {
16
17 //模拟一直生产
18 while (true) {
19
20 synchronized (this.house) {
21
22 if (this.house.getObjects().size() == 0) {
23
24 //消费者不能再消费,要让这个消费者处于等待状态
25 try {
26
27 //让处于该对象上活跃的线程处于等待状态
28 this.house.wait();
29
30 } catch (InterruptedException e) {
31 e.printStackTrace();
32 }
33
34 }
35
36 //仓库中有产品,消费者就要去消费
37 Object obj = this.house.getObjects().remove(0);
38
39 System.out.println(Thread.currentThread().getName() + "消费了一个产品,产品实例为" + obj);
40
41 try {
42 //模拟睡眠
43 Thread.sleep(1000);
44 } catch (InterruptedException e) {
45 e.printStackTrace();
46 }
47
48 //唤醒生产者去生产
49 this.house.notifyAll();
50
51 }
52 }
53 }
54}
House
xxxxxxxxxx
221package com.os467.wait;
2
3import java.util.List;
4
5//仓库,用于存放产品的
6public class House {
7
8 private List<Object> objects;
9
10 public House(List<Object> objects) {
11 this.objects = objects;
12 }
13
14 public List<Object> getObjects() {
15 return objects;
16 }
17
18 public void setObjects(List<Object> objects) {
19 this.objects = objects;
20 }
21
22}
Producer
xxxxxxxxxx
641package com.os467.wait;
2
3/**
4 * 需求:
5 * 生产者线程在什么情况下不需要生产产品?
6 * 当仓库中的产品满足一定数量之后,就不用再生产了
7 *
8 * 只要当仓库中有产品,就要交由消费者去消费,消费者消费完了,仓库中没产品了,就继续交由生产者去生产
9 */
10public class Producer extends Thread {
11
12 //创建仓库的引用
13 private House house;
14
15 public Producer(String name,House house){
16
17 super(name);
18 this.house = house;
19
20 }
21
22
23 public void run() {
24
25 //模拟一直生产
26 while (true) {
27
28 synchronized (this.house) {
29
30 if (this.house.getObjects().size() > 0) {
31
32 //生产者不能再生产,要让这个生产者处于等待状态
33 try {
34
35 //让处于该对象上活跃的线程处于等待状态
36 this.house.wait();
37
38 } catch (InterruptedException e) {
39 e.printStackTrace();
40 }
41
42 }
43
44 //如果仓库中没有产品,生产者要继续生产
45 this.house.getObjects().add(new Object());
46
47 System.out.println(Thread.currentThread().getName() + "生产了一个产品,产品实例为" + this.house.getObjects().get(0));
48
49 try {
50 //模拟睡眠
51 Thread.sleep(1000);
52 } catch (InterruptedException e) {
53 e.printStackTrace();
54 }
55
56 //唤醒此对象的消费者线程去消费
57 this.house.notifyAll();
58
59 }
60 }
61
62 }
63
64}
WaitDemo
xxxxxxxxxx
251package com.os467.wait;
2
3import java.util.ArrayList;
4
5public class WaitDemo {
6
7 public static void main(String[] args) {
8
9 //创建仓库对象
10 House house = new House(new ArrayList<>());
11
12 //创建生产者线程
13 Producer producer = new Producer("生产者",house);
14
15 //创建消费者线程
16 Consumer consumer = new Consumer("消费者",house);
17
18 //启动线程
19 producer.start();
20 consumer.start();
21
22
23 }
24
25}
可以修饰属性,实现了多线程中的可见性,让线程之间及时的通知此属性的数据更新