一、进程与线程的概念
进程: 进程是应用程序的执行实例,有独立的内存空间,占用独立的系统资源。
线程:包含在进程之内的,有主线程与子线程的区分。是CPU调度和分派的基本单位,每个子线程都可以独立的完成一个功能。
什么是多线程?
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多个线程交替占用CPU资源,而非真正的并行执行
多线程好处:充分利用CPU的资源,简化编程模型,带来良好的用户体验
并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
二、主线程
Thread类 Java提供了java.lang.Thread类支持多线程编程
主线程:
main()方法即为主线程入口
产生其他子线程的线程
必须最后完成执行,因为它执行各种关闭动作
public static void main(String[] args) {
Thread thread = Thread.currentThread(); //线程对象
String name = thread.getName();
System.out.println("线程名称为:"+name); //主线程
thread.setName("mythread");
System.out.println("线程名称为:"+thread.getName());
}
三、创建线程
在Java中创建线程的三种方式:
-
继承java.lang.Thread类
-
实现java.lang.Runnable接口
-
实现Callable接口和Future接口
使用线程的步骤:
1、定义线程
2、创建线程对象
3、启动线程
4、终止线程
3.1 继承Thread类实现创建线程
步骤:
-
定义MyThread类继承Thread类
-
重写run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
示例
public class MyThread extends Thread {
@Override
public void run() {
String name = Thread.currentThread().getName();
switch (name){
case "xc1":
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
break;
case "xc2":
for (int i = 0; i < 10; i++) {
System.out.println("好好学习java"+i);
}
break;
}
}
}
创建线程对象:
public static void main(String[] args) {
MyThread myThread1=new MyThread(); //创建线程对象
myThread1.setName("xc1");
myThread1.start(); //启动线程
MyThread myThread2=new MyThread(); //创建线程对象
myThread2.setName("xc2");
myThread2.start(); //启动线程
}
多个线程交替执行,不是真正的“并行”,线程每次执行时长由分配的CPU时间片长度决定
3.2 实现Runnbale接口创建线程
步骤:
-
定义MyRunnable类实现Runnable接口
-
实现run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
示例
public class MyRunable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
创建线程对象
public class Test1 {
public static void main(String[] args) {
Runnable myRunable=new MyRunable(); //1、实例化对象
Thread thread=new Thread(myRunable); //2、构建一个线程对象
thread.setName("线程1");
thread.start(); //3、启动线程
Thread thread1=new Thread(myRunable);
thread1.setName("线程2");
thread1.start();
}
}
3.3 实现Callable接口和Future接口
定义线程
步骤:
1、创建Callable接口的实现类,并重写call()方法,该方法具有返回值
2、实例化实现类对象,通过实现类对象进行入参来构建一个FutureTask对象,FutureTask对象实现了Runable接口,Future也是Runable和Future接口的实现类
3、通过FutureTask对象入参构建一个线程对象,并调用start()方法来启动执行线程
4、适用FutureTask对象的get()方法来获得线程执行的结果
import java.util.concurrent.Callable;
/**
@author:mengshujun
@createTime: 2024-03-20 10:38:41 星期三
*/
public class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int num1=11;
int num2=22;
int sum=num1+num2;
return sum;
}
}
创建线程并调用
public class Test2 {
public static void main(String[] args) {
MyThread3 m3=new MyThread3(); //实例化对象
FutureTask<Integer> futureTask=new FutureTask(m3); //Runable对象
//创建线程
Thread t1=new Thread(futureTask);
t1.setName("线程一");
t1.start();
//new Thread(futureTask,"线程一").start();
try {
System.out.println("线程的执行结果为:"+futureTask.get()); //获得线程的返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
适用于线程有返回值的情况,可以适用这种方式来创建线程,接收结果。
三种创建线程的区别:
1、继承Thread类,编写简单,可直接操作线程,适用于单继承
2、实现Runnable接口,避免单继承局限性,便于共享资源。
3、实现Callable接口,可以接收线程运行的结果。
推荐使用实现Runnable接口方式创建线程
四、线程的五种状态
创建类
package com;
public class MyRunnable implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + ",现在处于就绪状态");
for (int i = 0; i < 30; i++) {
System.out.println("线程" + name + ",现在处于运行状态");
try {
Thread.sleep(3);
System.out.println("线程" + name + ",现在处于阻塞状态");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程" + name + ",现在处于死亡状态");
}
}
创建线程
public static void main(String[] args) {
MyRunnable myRunnable=new MyRunnable();
Thread thread=new Thread(myRunnable);
thread.setName("线程一");
System.out.println("线程"+thread.getName()+",现在处于创建状态");
thread.start();
}
五、线程调度
线程调度指按照特定机制为多个线程分配CPU的使用权
线程优先级:线程优先级由1~10表示,1最低,默认优先级为5,优先级高的线程获得CPU资源的概率较大
线程休眠:让线程暂时睡眠指定时长,线程进入阻塞状态,睡眠时间过后线程会再进入可运行状态
线程礼让:暂停当前线程,允许其他具有相同优先级的线程获得运行机会,该线程处于就绪状态,不转为阻塞状态
线程中断:使当前线程暂停执行,等待其他线程结束后再继续执行本线程
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
millis:以毫秒为单位的等待时长
nanos:要等待的附加纳秒时长
需处理InterruptedException异常
六、线程使用同一资源的数据安全问题
存在问题的代码
package net;
/**
* 抢票线程类
*/
public class BuyThread implements Runnable {
public int num = 10; //总票数
public int count = 0; //记录当前的用户(线程)抢到了第几张票
@Override
public void run() {
while (true) {
if (num == 0) {
System.out.println("票已被抢完,抢票活动结束!");
break;
}
num--;
count++;
System.out.println("当前用户(线程)," + Thread.currentThread().getName() + "抢到了第" + count + "张票,总票数还剩余" + num + "张");
}
}
}
创建线程类
public static void main(String[] args) {
BuyThread buyThread = new BuyThread();
Thread t1 = new Thread(buyThread, "徐磊");
Thread t2 = new Thread(buyThread, "贾恒广");
Thread t3 = new Thread(buyThread, "袁晓波");
t1.start();
t2.start();
t3.start();
}
七、解决线程安全问题有两种解决方案
7.1、使用synchronized (this) 代码块来解决
同步代码块锁 ,this就代表的是当前进来的线程对象,进行锁定,本线程执行完操作后才会释放锁。
package net;
/**
* 抢票线程类
*/
public class BuyThread implements Runnable {
public int num = 10; //总票数
public int count = 0; //记录当前的用户(线程)抢到了第几张票
@Override
public void run() {
//同步代码块锁 ,this就代表的是当前进来的线程对象
//1
while (true) {
synchronized (this) {
if (num == 0) {
System.out.println("票已被抢完,抢票活动结束!");
break;
}
num--;
count++;
System.out.println("当前用户(线程)," + Thread.currentThread().getName() + "抢到了第" + count + "张票,总票数还剩余" + num + "张");
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.2、定义synchronized方法来解决
package net;
/**
* 抢票线程类
*/
public class BuyThread implements Runnable {
public int num = 10; //总票数
public int count = 0; //记录当前的用户(线程)抢到了第几张票
@Override
public void run() {
while (true) {
if (!cale()) {
break;
}
}
}
//同步方法
public synchronized boolean cale() {
boolean isFlag = true;
if (num == 0) {
isFlag = false;
System.out.println("票已售完!");
return isFlag;
}
num--;
count++;
System.out.println("当前用户(线程)," + Thread.currentThread().getName() + "抢到了第" + count + "张票,总票数还剩余" + num + "张");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return isFlag;
}
}
八、线程安全的类型
ArrayList list=new ArrayList(); //动态的数组
list.add(1);
Vector vector=new Vector(); //线程安全
vector.add(100);
HashMap map1=new HashMap();
map1.put("id","111");
Hashtable table1=new Hashtable(); //线程安全
table1.put("id","222");
StringBuilder sb1=new StringBuilder();
sb1.append("ddd");
StringBuffer sb=new StringBuffer(); //线程安全
sb.append("abc");