一.概念
程序
在idea躺着代码就是程序就那么简单
进程
当躺着的代码被加载到虚拟机中跑起来了,就变成了进程。官方话还是要说说,指在系统中能独立运行并作为资源分配的基本单位
线程
线程是进程中的一个实体,作为cpu调度和分派的基本单位
二.线程的创建
1.继承Thread方法
直接创建一个类继承Thread,重写run方法,主线程中用start(),启动线程
package com.wq.thread0;
public class TestThread {
public static void main(String[] args) {
Thread myThread = new MyThread();
myThread.setName("孤寡线程");
myThread.setPriority(5);
myThread.start();
}
}
package com.wq.thread0;
public class MyThread extends Thread {
@Override
public void run() {
int i = 0 ;
while (i<100){
System.out.println(Thread.currentThread().getName() + "线程 孤寡 孤寡 " );
i++;
}
}
}
2.实现Runnable接口
创建一个类实现Runnable接口的重写run方法,主线程中使用Thread(Runnable)的构造方法创建多线程,用start(),启动线程
package com.wq.thread1;
public class TestThread {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread myThread = new Thread(myRunnable);
myThread.setName("孤寡线程");
myThread.setPriority(5);
myThread.start();
}
}
package com.wq.thread1;
public class MyRunnable implements Runnable {
@Override
public void run() {
int i = 0 ;
while (i<100){
System.out.println(Thread.currentThread().getName() + "线程 孤寡 孤寡 " );
i++;
}
}
}
3.实现Callable接口
创建一个类实现Callable接口,重写call()方法,然后在主线程new Thread(),发现没有Callable构造方法的创建,我们使用runnable的构造方法,用Runnable的实现类FutureTask来创建与Callable关联的对象。然后我们使用runnableTask .get()来返回值
package com.wq.thread2;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<String> myCallable = new MyCallable();
FutureTask<String> runnableTask = new FutureTask<String>(myCallable);
Thread myThread = new Thread(runnableTask );
myThread.start();
System.out.println(runnableTask .get());
}
}
package com.wq.thread2;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return Thread.currentThread().getName() + "线程 孤寡 孤寡 " ;
}
}
三种线程的比较
三.同步线程
先说说为什么会有同步线程的概念
假设王二和赵四同时对一个账户进行取钱,账户有600元,因为我们的线程是由cpu调度的,当线程的时间片使用完了,cpu去就绪队列里运行其中的一个线程,在不能保证调度的是之前的线程的情况,就可以出现下面的情况
解决方法:同步线程
同步关键字:synchronized
同步监视器(锁)
- 只能是引用数据类型
- 只能改变值,不能改变其引用地址
- 一般使用同步资源作为同步监视器
- 建议使用final保证安全性
同步执行过程
王二在查余额前会对查询余额,取钱操作做加锁操作,如果王二在查完余额后cpu切换,此时我们的查询和取钱操作是上锁了的状态,所以即使赵四得到了cpu的调度,但是也无法进入被锁住的操作,所以又会等时间消耗完返回,此时王二再次得到cpu就会继续完成取钱操作,然后释放锁。待锁释放后赵四再次得到cpu,此时查询和取钱操作没被加锁,所以操作再次被加锁并进入执行。
同步线程:同步代码块,同步方法
同步代码锁:
- 同步监视器
同步方法锁:
- 普通方法 ->同步监视器是this
- 静态方法 ->同步监视器是 类.class
Lock
对象Lock也可以实现同步线程。因为它是jdk提供的对象,所以对于关键字synchronized来说可以实现的功能更多。
lock特点
- 只能锁代码块
- 需手动加锁手动解锁
- 可重入锁
- 性能 lock > synchronized代码块 > synchronized方法
四.线程通信
先说说什么是线程通信吧。线程通信就是我们最经常说到的生产者消费者模型,就是当有生产者生产了商品,消费者就会来消费商品。在生产和消费的同时会进行通信来保证供需的平衡
代码的几个问题
现在我们有一只会叫孤寡的青蛙和一只会叫布谷的布谷鸟
问题:生产了一只会叫布谷的青蛙
解决:线程同步,注意生产者和消费者都需要加锁,并且加的是同一把锁。
问题:消费者去得的商品属性为空
解决:要对同一资源进行生产消费
问题:生产和消费如何交互
解决:使用线程通信,object的wait(),notify(),notifyAll()
package com.wq.xctx;
public class TestXctx {
public static void main(String[] args) {
//创建商品类,保证生产和消费的是同一个商品
Product product = new Product();
//创建生产线程
Produce produce = new Produce(product);
Thread produceThread = new Thread(produce);
produceThread.start();
//创建消费线程
Consume consume = new Consume(product);
Thread consumeThread = new Thread(consume);
consumeThread.start();
}
}
package com.wq.xctx;
public class Product {
public String animal;
public String sound;
public boolean flag = false; //是否有动物在叫
public String getAnimal() {
return animal;
}
public void setAnimal(String animal) {
this.animal = animal;
}
public String getSound() {
return sound;
}
public void setSound(String sound) {
this.sound = sound;
}
}
package com.wq.xctx;
public class Produce implements Runnable{
private Product product ;
public Produce(Product product) {
this.product = product;
}
@Override
public void run() {
//生产商品
int i = 0 ;
while (true){
synchronized (product){
if (product.flag){
try {
product.wait(); //释放cpu 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i%2==0){
product.setAnimal("青蛙");
try {
Thread.sleep(10); //1.暴露问题,可能产生线程同步问题 解决 上锁 2.释放CPU 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setSound("孤寡");
}else {
product.setAnimal("布谷鸟");
try {
Thread.sleep(10); //1.暴露问题,可能产生线程同步问题 解决 上锁 2.释放CPU 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
product.setSound("布谷");
}
System.out.println("发现了一个"+product.getAnimal()+", 发出了"+product.getSound()+"的叫声");
product.flag=true;
product.notify();//唤醒等待队列的线程
i++;
}
}
}
}
package com.wq.xctx;
public class Consume implements Runnable{
private Product product ;
public Product getProduct() {
return product;
}
public Consume(Product product) {
this.product = product;
}
public void setProduct(Product product) {
this.product = product;
}
@Override
public void run() {
while (true){
synchronized (product){
if (!product.flag){
try {
product.wait();//释放cpu 释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("收到了一个"+product.getAnimal()+", 发出了"+product.getSound()+"的叫声");
product.flag=false;
product.notify();//唤醒等待队列的线程
}
}
}
}
线程通信的几个细节
1.进行线程通信的多个线程要使用同一个同步监视器
我们点开Object类可以看到对应的wait(),notify(),notifyAll()都是对应的类对象的方法,在下面的生命周期也会提到,同一个类的wait()会进入同一个等待队列,notify()也是唤醒对应的线程(这就是之前为什么建议使用同步资源作为同步监视器)
2.通信方法
wait()
线程等待,直到同步监视器使用notify()或者notifyAll()唤醒才结束等待。也可以使用wait(time)来对线程在超过指定时间进行唤醒
notify()
随机唤醒一个指定同步监视器在wait()等待队列的的线程
notifyAll()
唤醒同步监视器里的全部的wait()等待队列里的线程
3.线程的生命周期
4.sleep和wait的区别
- sleep调用后线程进入的是阻塞状态,wait进入的是等待队列
- sleep和wait都会让出CPU,但是sleep不释放锁(同步线程),wait会释放锁(可做线程通信)
- sleep可以在任何地方使用,wait只能在同步代码块或者同步方法中使用
五.结束
第一次了解线程,以为他是一种很牛批牛批的东西,因为涉及到线程就难不了扯到并发,宕机,祭天这些词,但现在在学一次还是认为他是很牛批的东西,这一节只是讲到线程的基本的东西,太多了,线程池就放在下章了。