线程概念
- 进程:计算机中特定功能的程序在数据集上的一次运行
- 线程:线程是进程的一个单元
- 多线程:一个进程中有多个线程在同时运行,如迅雷下载,迅雷软件的一次运行就是一个进程,那么在迅雷中可以同时下载多个电影,这就是多线程(每一个下载都是一个线程)
- Jvm是多线程的,在我们运行jvm的时候后台会运行垃圾回收的线程,来清理没有被引用的对象
一、线程的实现
(一)继承 Thread 类
两个小问题:
- 为什么要重写 run() 方法?
因为 run() 方法是用来封装被执行的代码 - run() 方法和 start() 方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程,然后有JVM调用此线程的 run() 方法
步骤:
- 自己定义一个类 继承 Thread
- 重写 run() 方法
- 调用 start() 方法,进入就绪状态
- 开启线程
案例代码
/**
* 线程的第一种实现方法 继承Thread类
*/
public class Demo1 extends Thread {
/**
* 该线程的名称
*/
private String name;
/**
* 有参构造器
* @param name
*/
public Demo1(String name) {
this.name = name;
}
/**
* 线程的业务逻辑
* 重写了 run() 方法
*/
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("下载了" + i + "%");
}
}
}
测试类
public class Demo1Test {
public static void main(String[] args) {
//1.创建我们自己的线程类
Demo1 name = new Demo1("肖申克的救赎");
//2.调用线程的 start() 方法 状态:就绪状态
name.start();
System.out.println("程序结束!!!!!");
}
}
(二)实现 Runnable 接口
步骤:
- 自己定义一个类 实现 Runnable 接口
- 重写 run() 方法
- 调用 start() 方法,进入就绪状态
- 开启线程
案例代码
/**
* 线程的第二种实现方法 实现 Runnable 接口
*/
public class Demo2 implements Runnable{
private String name;
public Demo2(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("下载了" + i + "%");
}
}
}
测试类
public class Demo2Test {
public static void main(String[] args) {
/**
* 实现 Runnable,不能直接创建,需要用 Thread 的构造方法
*
* Thread(Runnable target) 分配一个新的 Thread对象
*/
Thread name = new Thread(new Demo2("喜羊羊与灰太狼"));
name.start();
Thread name2 = new Thread(new Demo2("斗破苍穹"));
name2.start();
System.out.println("程序结束!!!!");
}
}
二、线程的生命周期
1.新建: 线程被new出来
2.准备就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利
3.运行:具备执行的资格和具备执行的权利
4.阻塞:没有执行的资格和执行权利
5.销毁: 线程的对象变成垃圾,释放资源
三、线程的并发同步
当多个线程同时操作一个共享资源的时候容易造成并发问题
(一)同步代码块解决数据安全问题
语法:
synchronized(锁对象){
//操作共享资源的代码
}
同步代码加在什么地方?
- 1.代码被多个线程访问
- 2.代码中有共享的数据
- 3.共享数据被多条语句操作
好处和弊端:
- 好处:解决了多线程的大户局安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很浪费资源的,无形中降低了程序的运行效率
实例代码
1.第一种同步处理
/**
* 互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。
*
* 范例:火车站有100张票,4个窗口同时买票。
* 分析:4个窗口是4个线程同时在运行,100票是4个线程的共享资源
*/
public class Demo1 extends Thread {
private String name;
/**
* 定义一个共享变量
*/
static int tickets=100;
//定义一个共享所对象
static Object obj=new Object();
public Demo1(String name) {
this.name = name;
}
@Override
public void run() {
while (true){
//同步锁
synchronized (obj){
if (tickets>0){
System.out.println(name + "卖了的票是" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("票全部卖完了!!!");
}
}
测试类
public class Demo1Test {
public static void main(String[] args) {
Demo1 d1 = new Demo1("窗口一");
Demo1 d2 = new Demo1("窗口二");
Demo1 d3 = new Demo1("窗口三");
Demo1 d4 = new Demo1("窗口四");
d1.start();
d2.start();
d3.start();
d4.start();
}
}
2.第二种同步处理
/**
* 互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。
*
* 范例:火车站有100张票,4个窗口同时买票。
* 分析:4个窗口是4个线程同时在运行,100票是4个线程的共享资源
*/
public class Demo2 implements Runnable{
//定义共享数据
int tickets=100;
//定义同步对象锁
Object obj=new Object();
//重写 run() 方法
@Override
public void run() {
while (true){
//同步锁
synchronized (obj){
if (tickets>0){
System.out.println(Thread.currentThread().getName()+"卖了的票是" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("票已经全部卖完了!!!");
}
}
测试类
public class Demo2Test {
public static void main(String[] args) {
//创建一个买票对象
Demo2 demo2 = new Demo2();
Thread t1 = new Thread(demo2, "窗口一");
Thread t2 = new Thread(demo2, "窗口二");
Thread t3 = new Thread(demo2, "窗口三");
Thread t4 = new Thread(demo2, "窗口四");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
(二)同步方法解决数据安全问题
synchronized是可以加在方法上:
- 1.如果是静态方法Synchronized的所对象就是类的类对象
/**
* 如果是静态方法Synchronized的所对象就是类的类对象
*
* 互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。
*
* 范例:火车站有100张票,4个窗口同时买票。
* 分析:4个窗口是4个线程同时在运行,100票是4个线程的共享资源
*/
public class Demo4 extends Thread{
private String name;
/**
* 定义一个共享变量
*/
static int tickets=100;
//定义同步对象锁
//Object obj=new Object();
public Demo4(String name) {
super(name);
this.name = name;
}
@Override
public void run() {
while (true){
if (saleTickets()){
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name+"票已经全部卖完了!!!");
}
//如果是静态方法Synchronized的所对象就是类的类对象
public static synchronized boolean saleTickets(){
boolean is=false;
if (tickets >0){
System.out.println(Thread.currentThread().getName() + "卖了的票是" + tickets-- + "号");
}else {
is=true;
}
return is;
}
}
测试类
public class Demo4Test {
public static void main(String[] args) {
Demo4 d1 = new Demo4("一号窗口");
Demo4 d2 = new Demo4("二号窗口");
Demo4 d3 = new Demo4("三号窗口");
Demo4 d4 = new Demo4("四号窗口");
d1.start();
d2.start();
d3.start();
d4.start();
}
}
- 2.如果不是静态的方法,Synchronized如果加在对象方法上,那么他的锁是this
/**
* 如果不是静态的方法,Synchronized如果加在对象方法上,那么他的锁是this
*
*
* 互联网的项目中存在着大量的并发的案例,如卖火车票,电商网站。
*
* 范例:火车站有100张票,4个窗口同时买票。
* 分析:4个窗口是4个线程同时在运行,100票是4个线程的共享资源
*
*/
public class Demo3 implements Runnable{
/**
* 定义一个共享变量
*/
int tickets=100;
/**
* 定义一个锁对象
*
* Object obj=new Object();
*/
/**
* 重写 run() 方法
*/
@Override
public void run() {
while (true){
if (saleTickets()){
break;
}
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序结束!!!!");
}
/**
* 在方法上实现同步,在方法上写同步,那么那个共享的锁对象就是调用该方法对象默认的 this
* @return
*/
public synchronized boolean saleTickets(){
boolean is=false;
if (tickets>0){
System.out.println(Thread.currentThread().getName() + "卖了的票是" + tickets-- + "号");
}else {
is=true;
}
return is;
}
}
测试类
public class Demo3Test {
public static void main(String[] args) {
Demo3 demo3 = new Demo3();
Thread t1 = new Thread(demo3, "一号窗口");
Thread t2 = new Thread(demo3, "二号窗口");
Thread t3 = new Thread(demo3, "三号窗口");
Thread t4 = new Thread(demo3, "四号窗口");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
四、线程间的通信
1.通信间的方法
2.生产者与消费者
生产者生成水果,如果水果没有被买走那么就不生产处于等待状态,如果水果被消费者买走就的时候消费者会通知生产者告诉他我们已经把水果买走了请生产,消费者同理,如果水果已经生产出来那么就买走,买走之后再通知生产者水果已经没了请生产。
注意:
- 1.线程间的通信共享数据一定要有同步代码块synchronized
- 2.一定要有wait和notify,而且二者一定是成对出现
- 3.生产者和消费者的线程实现一定是在while(true)里面
案例代码:
/**
* 定义一个共享水果篮子实体类
* javaBean
*/
public class Basket {
//判断属性是否为空
private boolean isEmpty;
//提供 get() set() 方法
public boolean isEmpty() {
return isEmpty;
}
public void setEmpty(boolean empty) {
isEmpty = empty;
}
}
---------------------------------------------------------------------
/**
* 水果生产者(线程一)
*/
public class Producer extends Thread{
private Basket basket;
public Producer(Basket basket) {
this.basket = basket;
}
/**
* 线程一(生产者需要生产水果)
*/
@Override
public void run() {
while (true){
//同步锁
synchronized (basket){
try {
//判断篮子是否为空
if (!basket.isEmpty()){
//篮子不是空的,进行等待状态
basket.wait();
}
//消费端做完消费,通知我再进行生产的时候我就生产
System.out.println("生产水果");
//将篮子设置为不为空
basket.setEmpty(false);
basket.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
---------------------------------------------------------------------
/**
* 消费者(线程二)
*/
public class Consumer extends Thread{
private Basket basket;
public Consumer(Basket basket) {
super();
this.basket = basket;
}
/**
* 线程二(消费者消费水果)
*/
@Override
public void run() {
while (true){
//定义一个同步代码块
synchronized (basket){
try {
//判断篮子里有没有水果,如果没有我就等待通知
if (basket.isEmpty()){
//线程等待状态
basket.wait();
}
//生产者 生产去,生产完告诉我
System.out.println("消费水果");
//将篮子设置成空
basket.setEmpty(true);
//通知在这个共享对象上等待的线程
basket.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
-------------------------------------------------------------------
测试类
public class Test {
public static void main(String[] args) {
Basket basket = new Basket();
Producer producer = new Producer(basket);
Consumer consumer = new Consumer(basket);
producer.start();
consumer.start();
}
}
五、线程的优先级
(一)线程有两种调度模型:
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU ,如果线程的优先级相同,那么会随机选一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型
假如计算机只有一个 CPU ,那么 CPU 在某一时刻只能执行一条指令,线程只有得到 CPU 时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有 随机性,因为谁抢到 CPU 的使用权是不一定的
(二)线程优先级的最大优先级和最小优先级
1.可以返回线程可拥有的最大优先级
static int MAX_PRIORITY
线程可以拥有的最大优先级 最大优先级为10
2.可以返回线程可拥有的最小优先级
static int MIN_PRIORITY
线程可以拥有的最小优先级 最小优先级为1
3.可以返回线程拥有的默认优先级
static int NORM_PRIORITY
分配给线程的默认优先级 默认优先级为5
(三)Thread 类中设置和获取线程优先级的方法
1.获取当前线程优先级方法
public final int getPriority()
返回此线程的优先级
2.设置方法
public final void setPriority(int newPriority)
更改此线程的优先级
- 线程优先级高仅仅表示线程获取的 CPU 时间片的几率高。但是要在次数比数多,或者多次运行的时候才能看到你想要的效果
六、线程控制
1.线程休眠
方法:
static void sleep(long millis)
使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性
注意:
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep 存在异常 InterruptedException
- sleep 时间到达后线程进入就绪状态
- 可以模拟网络延迟,放大问题的发生性
- 每一个对象都有一个锁,sleep 不会释放锁
2.加入线程
方法:
void join()
等待这个线程死亡
注意:
- Join 合并线程,待此线程执行完毕 后,再执行其他线程,其他线程阻塞
- 加入线程必须要在第一个执行的线程的start下面来执行
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程的对象
MyThread mt = new MyThread("肖申克的救赎");
//创建一个线程的对象
MyThread mt1 = new MyThread("当幸福来敲门");
MyThread mt2 = new MyThread("魔戒1");
mt.start();
try {
//加入线程必须要在第一个执行的线程的start下面来执行
mt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
mt1.start();
mt2.start();
}
}
3.让出线程
方法:
static void yield()
对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器
注意:
- 让出线程,让当前正在执行的线程暂停,但不会阻塞
- 将线程从运行状态转为就绪状态
- 让 CPU 重新调度,礼让不一定成功!看 CPU 心情
public class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
/**
* 这就是线程执行的逻辑体
*/
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(name+"下载了"+i+"%");
//让出线程
Thread.yield();
}
}
}
4.守护(daemon)线程
方法:
void setDaemon(boolean on)
将此线程标记为 daemon线程或用户线程
注意:
- 线程分为 用户线程 和 守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 守护线程会随着主线程的结束而结束
- 如,后台记录操作日志,监控内存,垃圾回收等待…
public class ThreadTest {
public static void main(String[] args) {
//创建一个线程的对象
MyThread mt = new MyThread("肖申克的救赎");
//设置守护线程
mt.setDaemon(true);
mt.start();
System.out.println("主线程结束");
}
}
6.Lock锁
概述:
虽然我们理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5 以后提供了一个新的锁对象 Lock
方法:
1.void lock()
获得锁
2.void unlock()
释放锁
注意:
- Lock 是接口,不能直接被实例化
- 我们一般用常用子类 ReentrantLock 来实例化
构造方法:ReentrantLock() 创建一个 ReentrantLock的实例
案例代码:
synchronized 和 Lock 的区别:
7.死锁
什么是死锁:
死锁产生的条件:
代码案例:
七、多线程另外两种实现方式
目前用不到,作为了解
(一)实现 Callable 接口
步骤:
- 实现 Callable 接口,需要有返回值类型
- 重写 call() 方法,需要抛出异常
- 创建目标对象
- 创建执行服务器:线程池对象
- 提交执行
- 获取结果
- 关闭服务
实例代码:
public class Callable1 implements Callable {
private String name;
/**
* 定义一个共享变量
*/
static int tickets=100;
public Callable1(String name) {
this.name = name;
}
@Override
public Object call() throws Exception {
while (true){
if (tickets>0){
System.out.println(name + "卖了的票是" + tickets-- + "号");
}else {
break;
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程结束!!!";
}
}
------------------------------------------------------------------
测试类
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建对象
Callable1 c1 = new Callable1("窗口一");
//创建执行服务器:线程池对象
//3 指的是线程池容量
ExecutorService ex = Executors.newFixedThreadPool(3);
//提交执行
Future submit = ex.submit(c1);
//获取结果
Object o = submit.get();
//输出
System.out.println(o);
//关闭服务
ex.shutdownNow();
}
}
(二)线程池
1.线程池的优势
总体来说,线程池有如下的优势:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
2.使用线程池
代码案例:
实现 Runnable
public class ThreadPoolServiceRunnable {
public static void main(String[] args) {
//创建目标对象
TestRunnable test = new TestRunnable();
TestRunnable test1 = new TestRunnable();
//创建服务,创建线程池, newFixedThreadPool 的参数是:线程池大小
ExecutorService ex = Executors.newFixedThreadPool(10);
//执行
ex.execute(test);
ex.execute(test1);
//关闭连接池
ex.shutdown();
}
}
class TestRunnable implements Runnable{
//重写 run() 方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我爱Java!!!");
}
}
}
实现 Callable 接口
public class ThreadPoolServiceCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建目标对象
TestCallable test = new TestCallable();
TestCallable test1 = new TestCallable();
//创建服务,线程池对象, newFixedThreadPool 的参数是:线程池大小
ExecutorService pool = Executors.newFixedThreadPool(10);
//执行
Future submit = pool.submit(test);
Future submit1 = pool.submit(test1);
//获取结果
Object o = submit.get();
Object o1 = submit1.get();
//输出
System.out.println(o);
System.out.println(o1);
//关闭连接池
pool.shutdown();
}
}
class TestCallable implements Callable{
//重写 call() 方法
@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("我爱Java!!!");
}
return "程序结束!";
}
}