第八章:Thread线程类-------->多线程解析
一:进程和线程
进程:就是一个应用程序,JVM也是一个进程,进程和进程的内存是独立不共享的
线程:线程是一个程序的执行单元,一个进程可以有多个线程;堆内存和方法区是线程共享的内存
栈内存是独立的,一个线程一个栈!
二:实现线程的三种方式
(1)第一种方法:继承Thread
- 一个类继承了Thread线程类,那么他就是一个线程对象,此时我们直接New这个对象就是创建了一个新的线程,它操作的数据是本类的属性,New几个线程实例就是开启了几个单独的线程
- 继承线程类重写run方法,因为我们开启线程** start()后即代表开启成功 ,这句开启代码就结束了。启动成功的线程会自动调用Run方法**,run方法结束代表线程结束
- 因为单继承的特性,这种方法会破坏原有的类,所以不常用
class Thread01 extends Thread{
int count = 1;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() +
"------->"+count++);
}
}
}
//创建线程:三条线程,线程类开启的线程数据不共享
Thread01 t = new Thread01();
t.start();
t.start();
t.start();
//匿名内部类:
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "------->"+count++);
}
}
}.start();
(2)第二种方法:实现Runnable
- 编写一个类继承Runnable接口,重写其run方法
- 创建这个对象,将这个对象封装成一个线程对象,Thread t = new Thread(对象)
- 创建多个线程,同时操作同一个对象,实现多线程操作同对象
- 实现接口的方式还可以继续继承其他类,所以一般情况用这种方式
class runnable01 implements Runnable{
int count = 1;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "------->"+count++);
}
}
}
//需要先实现继承Runnable接口的类,再将其封装为线程,多个线程同时操作一个类,数据共享
//2.第二种:实现Runnable接口
runnable01 tt = new runnable01();
new Thread(tt).start();
new Thread(tt).start();
new Thread(tt).start();
public class Test09 {
public static void main(String[] args){
Runnable r = new Runnable() {
int count = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-------->"+ count--);
}
}
};
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
(3)第三种方法:实现Callable
和Runnable一样的用法,但是这个实现类有返回值
三:关于线程对象的生命周期
线程的五个状态:
新建状态:刚刚New出来的状态
就绪状态:当前线程具备抢夺CPU时间片(执行权)的权利
运行状态:run方法执行标记着进入运行状态,当时间片结束后,重新进入就绪状态
阻塞状态:当遇到阻塞时间线程会放弃当前时间片(执行权),阻塞解除需要重新进入就绪状态
死亡状态:run方法结束,即线程结束
多线程并发即在就绪状态和运行状态来回调度的结果!!!
四:线程的几个常用method的用法
(1)sleep()
- 线程的休眠,这个是静态方法,它出现在哪个线程就作用在哪个线程。
- 线程的休眠时间的单位是毫秒值
- 休眠的作用是让当前线程进入阻塞状态,此线程放弃当前执行权,并在阻塞结束后重新进入就绪状态等待再次被挑选到,但是其不会放弃当前拿到的锁
- 可以用作定时器的方法(过时)
- Sleep会抛出异常,其作用在run的方法内时,只能try…catch,不能Throws,因为子类不能抛出比父类更多的·异常
- 休眠可以被打断,即调用线程.interrupt()中断方式。这个机制是利用异常的方法打断的
(2)stop(过时)
- 这是停止线程的方法,这个方法直接结束当前线程
- 这个方法容易造成数据丢失,因为线程直接被杀死了,这是非正常的结束线程的方法
- 这个方法已经过时了
(3)正常的线程终止是让run方法结束
我们正常的操作是:在线程内部定义一个布尔标记,当我们想结束线程时,
主动将标记赋值为false即可结束run方法,即结束当前线程
(4)setDaemon()守护线程
线程分两大类:
一类是:用户线程
一类是:守护线程(后台线程),代表:垃圾回收程序
守护线程的特点:
一般守护线程是死循环,配合定时器起作用,所有用户进程结束,守护线程自动结束
(5)定时器Timer时间类下的schedule()
定时器作用还是特定的时间做特定的事。
三种类型设计定时器:
一:sleep方法设置睡眠时间(不建议使用)
二:定时器类Timer(很少使用),因为有高级框架实现
三:框架
(6)yieth()线程礼让
线程的礼让是暂停当前线程对象,并执行其他线程
它是对其他线程的让位,并是当前线程重新回到就绪状态
(7)setPriority()设置优先级
优先级的设置:是将当前的线程修改其优先级,只是让其执行的概念更高而已
最高级别:Thread.MAX_PRIORITY = 10
默认优先级:Thread.NORM_PRIORITY = 5;
最低优先级:Thread.MIN_PRIORITY =1;
五:多线程的同步(重点)
(1)什么时候并发会存在安全问题
三个条件:
条件一:多线程并发下
条件二:有共享数据
条件三:共享数据有修改的行为
(2)怎么解决线程安全问题
使用“线程同步机制”解决线程安全问题
在多线程并发环境下有共享数据需要被修改,那么就会出现线程安全问题,此时我们考虑让线程
排队,即不能实现并发。虽然会丢失一些效率,但是安全是第一考虑要素
- 异步编程模型
异步编程其实就是多线程并发,各自执行各自的,这种编程模型效率1高,但是容易出现安全问题
- 同步编程模型
在有一个线程在执行时,其他线程必须等待这个线程执行结束释放锁才能执行,
这种设计模型效率低,线程需要等待,但是,数据安全
(3)实现同步的方式:加同步锁(synchronized)
synchronized写法有三种:
第一种:同步代码块
第二种:同步方法(实例方法上)
第三种:静态方法上使用
1. 同步代码块
Runnable t =new Runnable(){
int tickets =100;
@Override
public void run() {
while (true){
synchronized (this) {
if (tickets > 0) {
try {
System.out.println(
Thread.currentThread().getName() +
"----窗口卖出第" + tickets-- +"张!");
Thread.sleep(100);//一个线程卖出一张票就休眠,释放当前锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}else
break;
}
}
}
};
//3个线程
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
- 同步代码块中的锁可以是任意的对象,只要是所有线程共享的对象就能充当锁
- 当有线程拿到锁后,进入代码块中,其他线程过来寻找锁没找到会释放执行权然后在锁池中等待寻找锁。
- 当释放锁后,线程进入就绪阶段继续抢夺执行权
2. 同步方法(实例方法)
class synchronized window {
private int tickets =100;
@Override
public void run() {
while (tickets >0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "----窗口卖出第" + tickets-- +"张!");
}
}
}
- synchronized出现在实例方法上,后面无需手动添加锁,它的锁就是this,
- 放在方法体上时,表示整个方法体都需要同步,这样扩大了同步范围,导致程序执行效率降低。不常用
3.静态方法上的synchronized
表示找类锁,这种锁永远只有一个,每个调用静态方法的线程对象都需要拿到这个锁才会运行
4.开发中怎么解决线程安全问题
java中三大变量
1.实例变量:堆中
2.静态变量:方法区中
3.局部变量:栈中
局部变量永远不存在线程安全问题,因为是栈内存,一个栈一个局部变量,不会出现变量修改问题
静态变量和实例变量是线程共享的区域,所以会涉及线程安全问题
- synchronized会使程序的执行效率变低,降低用户体验。最好用其他解决方案
- 第一种方案:尽量使用局部变量代替“实例变量和静态变量”
- 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
- 第三种:如果不能使用第一第二中才使用synchronize线程同步机制解决
六:生产者与消费者模式
什么是生产者消费者模式
1.生产者负责生产,消费者负责消费
2.生产者和消费者线程要求达到均衡
3.这是一种特殊的业务需求,这种情况需要使用wait和notify
(1)Object下的wait方法和notify方法
1.wait和notify是java对象的方法
2.wait和notify是建立在线程同步的基础上的
3.wait作用:o.wait()让正在o对象上活动的线程t进入到等待阶段,
并且释放t线程之前占用的o对象的锁
4.notify作用:o.notify()让正在o对象上等待的线程唤醒,
只是唤醒,不会释放o对象上占有的锁的线程
(2)生产者与消费者代码实现
①:传统wait()、notify()
//生产线程
class Producer1 extends Thread {
//1.1) 定义共享数据,储存数据的集合
private Stack stack;
public Producer1(Stack stack) {
this.stack = stack;
}
@Override
public void run() {
Random r = new Random();
while (true) {
synchronized (stack) {
if (stack.size() > 0) {
try {
stack.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
int i = r.nextInt(10);
stack.push(i);
System.out.println(Thread.currentThread().getName() + "-------->" + i);
stack.notify();
}
}
}
}
//消费线程
class Consumer1 extends Thread {
//1.1) 定义共享数据,储存数据的集合
private Stack stack;
public Consumer1(Stack stack) {
this.stack = stack;
}
@Override
public void run() {
while (true) {
synchronized (stack) {
if (stack.size() == 0) {
try {
stack.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object pop = stack.pop();
System.out.println(Thread.currentThread().getName() + "-------->" + pop);
stack.notify();
}
}
}
}
public class Test02 {
public static void main(String[] args) {
//1.1) 创建仓库
Stack<Integer> stack = new Stack<>();
//1.2) 创建线程对象
Producer1 producer1 = new Producer1(stack);
Consumer1 consumer1 = new Consumer1(stack);
//1.3) 给线程改名
producer1.setName("生产者");
consumer1.setName("消费者");
//1.4) 启动线程
producer1.start();
consumer1.start();
}
}
②:sleep
//1.生产者
class Producer extends Thread{
//1.1) 定义共享数据,即仓库
private Stack stack;
//1.2) 创建线程将对象传递过来
public Producer(Stack stack) {
this.stack = stack;
}
//1.3)重写run方法,线程启动其实就是运行这个方法
@Override
public void run() {
//1.4) 定义随机数类
Random r = new Random();
//1.5) 循环,无限循环启动模拟生产者行为
while (true){
//1.6) 如果栈集合中的元素少于50个,那么就一直生产
if (stack.size() < 1000){
//1.7) 生产一个随机数产品
int i = r.nextInt(100);
// try {
// sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
//1.8) 压入栈集合顶部
stack.push(i);
//1.9) 打印生产信息:
System.out.println(Thread.currentThread().getName() + "------->"+ i);
}else {
//1.9) 当集合中有100个产品后,此生产者线程进入休眠状态,进入阻塞状态,释放执行权
try { // 只能try...catch因为子类不能抛出比父类更多的异常
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//2.消费者
class Consumer extends Thread{
//2.1) 定义共享数据,即仓库
private Stack stack;
//2.2) 创建线程将对象传递过来
public Consumer(Stack stack) {
this.stack = stack;
}
//2.3)重写run方法,线程启动其实就是运行这个方法
@Override
public void run() {
//2.5) 循环,无限循环启动模拟消费者行为
while (true) {
//2.6) 如果栈集合中的元素大于0个,那么就一直消费
if (stack.size() > 0) {
//2.7) 弹出栈集合顶部元素
// try {
// sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
Object pop = stack.pop();
System.out.println(Thread.currentThread().getName() + "----->" + pop );
} else {
//2.8) 当集合中没有产品后,此生产者线程进入休眠状态,进入阻塞状态,释放执行权
try { // 只能try...catch因为子类不能抛出比父类更多的异常
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test01 {
public static void main(String[] args) {
//1.1) 创建仓库
Stack<Integer> stack = new Stack<>();
//1.2) 创建线程对象
Producer producer = new Producer(stack);
Consumer consumer = new Consumer(stack);
//1.3) 给线程改名
producer.setName("生产者");
consumer.setName("消费者");
//1.4) 启动线程
producer.start();
consumer.start();
}
}