0
点赞
收藏
分享

微信扫一扫

多线程总结

少_游 2022-01-24 阅读 48

1. 多线程

1.1 程序,进程,线程

程序 : 一组命令的集合,为了完成指定的功能,程序是静态概念,一般保存在硬盘当中。
进程 : 正在运行的程序,是一个动态概念,需要保存在内存当中,操作系统会分配对应的PID,当我们直接关闭某个进程的时候,该进程会在运行内存中被销毁。
线程 : 一个程序中,不同的执行分支,如果同一个时间节点允许多个线程同时执行的时候,我们称为支持多线程。
在Java中,main方法开始执行,就是一个线程,称为主线程。

1.2 并行和并发

并行 : 多个CPU,同时执行多个任务
并发 : 一个CPU,同时执行多个任务
多线程并行必须CPU要大于等于2 才行
单核CPU是没有多线程的

1.3 单核CPU和多核CPU

a)单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务.
b)如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
c)一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

1.4 多线程优缺点和应用场景

多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改.
场景:
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序时。

1.5 线程创建

1.5.1 Thread

第一种:创建一个类,继承Thread类,并覆写run方法
run方法==新线程中的main方法
class Processor extends Thread {
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(“测试线程–>”+i);
}
}
}
public static void test_01(){
//创建线程类对象
Thread t1=new Processor();
//调用start方法,启动线程
t1.start();
for(int i=0;i<10;i++){
System.out.println(“main线程–>”+i);
}
}

1.5.2 Runnable

第二种:创建一个类,实现Runnable接口,并覆写run方法
run方法==新线程中的main方法
class Processor_01 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(“测试线程–>”+i);
}
}
public static void test_02(){
//创建实现类对象
Processor_01 p = new Processor_01();
//创建线程类对象
Thread t1=new Thread§;
//启动线程
t1.start();
for(int i=0;i<10;i++){
System.out.println(“main线程–>”+i);
}
}

1.5.3 继承和实现的区别

区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

1.6 优先级和常用方法

1.6.1 优先级概述

线程的优先级等级
d)MAX_PRIORITY:10
e)MIN _PRIORITY:1
f)NORM_PRIORITY:5
涉及的方法
g)getPriority() :返回线程优先级
h)setPriority(int newPriority) :改变线程的优先级
说明
i)线程创建时继承父线程的优先级
j)低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

1.6.2 常用方法

getName:获取线程的名字
setName:设置线程的名字,如果不设置,默认是Thread-0开始 依次递增
setPriority():设置优先级,java中有1-10,10个优先级等级
MIN_PRIOROTY=1
NORM_PRIORITY=5
MAX_PRIORITY=10
getPriority():获取优先级等级
static currentThread():获取当前线程对象
static sleep():让当前线程进入睡眠状态
currentThread和sleep是静态方法,意味着和哪个对象调用无关
currentThread:出现在哪个线程中,就获取哪个线程的对象
sleep:出现在哪个线程,就睡眠哪个线程,参数为long类型的毫秒数

1.6.3 使用方式

public class Thread_02_Priority {
public static void main(String[] args){
//创建线程对象
Thread t1=new Processer();
//设置名字
t1.setName(“t1”);
//设置优先级为10
t1.setPriority(10);
//设置main的优先级为1
Thread.currentThread().setPriority(1);
//启动线程
t1.start();
for(int i=0;i<10;i++){
try{
//睡眠500毫秒
Thread.sleep(500);
}catch(InterruptedException e){
e.printStackTrace();
}
//获取线程对象并获取线程名字
System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}
}
class Processer extends Thread {
public void run(){
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}
}

1.7 生命周期

JDK中用Thread.State类定义了线程的几种状态:
新建,就绪,运行,阻塞,死亡。

1.8 线程控制在这里插入图片描述

1.8.1 线程停止

stop:终止某个线程执行,该方法已过时,不推荐使用,因为有可能导致死锁,所以一般使用标识符解决
public static void main(String[] args){
Processer_03 p=new Processer_03();
Thread t1=new Thread§;
t1.setName(“t1”);
t1.start();
try {
Thread.sleep(5000);
p.flag=true;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Processer_03 implements Runnable{
//加一个标识符,标识是否要终止线程
boolean flag=false;
@Override
public void run() {
for(int i=0;true;i++){
//判断是否要终止
if(flag){
System.out.println(Thread.currentThread().getName()+“线程已被终止”);
return;
}
try {
Thread.sleep(1000); System.out.println(Thread.currentThread().getName()+"–>"+i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

1.8.2 线程合并

join:线程合并,让当前线程等待指定线程执行完,再继续执行。
public static void main(String[] args) throws InterruptedException{
Thread t1=new Process_04();
t1.setName(“t1”);
t1.start();
//到这里,main就要等着t1线程执行完之后,再继续执行
t1.join();
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} System.out.println(Thread.currentThread().getName()+ “–>” + i);
}
}
}
class Process_04 extends Thread{
public void run(){
for(int i=0;i<10;i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//获取线程对象并获取线程名字
System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}

1.8.3 Yield

yield:静态方法,暂停当前正在执行的线程对象,并执行其他等待中的线程
1.静态方法:意味着跟那个对象调用没有关系,写在哪个线程,哪个线程就让位
2.给同优先级让位,不同优先级不让位
public static void main(String[] args){
//创建线程
Thread t1=new Thread(new Process_05());
t1.setName(“t1”);
//设置t1线程和main线程优先级一致
t1.setPriority(5);
Thread.currentThread().setPriority(5);
//启动
t1.start();
for(int i=0;i<10;i++){
//让位
Thread.yield();
System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}
}
class Process_05 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+"–>"+i);
}
}
}

1.9 线程同步

1.9.1 概述

多个线程执行的不确定性引起执行结果的不稳定
线程同步:当多个线程有可能同时操作同一个数据的时候,为了保证数据一致性,需要进行同步执行
本质:同步数据,是一种安全机制
异步编程:线程之间是完全独立的,相互没有影响
同步编程:线程之间不是完全独立的,相互可能有影响
同步场景:1.必须是多线程(必须有并发性,才有可能出错)
2.多个线程有可能在同一时间操作同一个数据的可能性
3.尤其是同时对数据进行更改操作,查询无所谓

1.9.2 不同步带来的问题

//实体类
class Account{
//余额
private double balance;
public void withDraw(double money){
System.out.println(Thread.currentThread().getName() + " 执行了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 余额减去取钱金额
double after = balance - money;
//新余额复制给余额
balance=after; System.out.println(Thread.currentThread().getName()+ " 取钱成功,取款 : "
+ money + "元,剩余 : " + balance + " 元 ");
}
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(double balance) {
super();
this.balance = balance;
}
public Account() {
super();
}
}
//线程类
class Processor_06 extends Thread{
//账户
Account act;
public Processor_06(Account act){
this.act=act;
}
@Override
public void run() {
//取1000
act.withDraw(1000);
}
}
public static void main(String[] args){
//创建账户,余额为3000
Account act=new Account(3000);
//两个线程,每个取1000
Thread t1=new Processor_06(act);
Thread t2 = new Processor_06(act);
t1.setName(“t1”);
t2.setName(“t2”);
t1.start();
t2.start();
}
在这里插入图片描述

1.9.3 解决方案

1.9.3.1 方法锁

方法使用synchronized之后,该方法只能有一个线程执行
public synchronized void withDraw(double money){
//如果该方法加了synchronized,那么该方法就只能有一个线程执行 System.out.println(Thread.currentThread().getName() + " 执行了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 余额减去取钱金额
double after = balance - money;
//新余额复制给余额
balance=after; System.out.println(Thread.currentThread().getName()+ " 取钱成功,取款 : "
+ money + "元,剩余 : " + balance + " 元 ");
}

1.9.3.2 语句块锁

假如该方法中,只有部分代码需要同步的时候,如果通过synchronized修饰,效率会大大折扣
所以我们可以通过语句块锁,只锁对应的代码,这样的话该方法中其他的代码还是可以同时执行,效率有所提升。
//静态语句块
synchronized(this){
// 余额减去取钱金额
double after = balance - money;
//新余额复制给余额
balance=after; System.out.println(Thread.currentThread().getName()+ " 取钱成功,取款 : "
+ money + "元,剩余 : " + balance + " 元 ");
}

1.9.4 Synchronized

synchronized(对象){}成员语句块锁
当访问一个对象中加锁的成员方法或者成员语句块锁的时候,则该对象中所有加锁的成员方法和成员语句块全部锁定
synchronized(类名.class){}静态语句块锁
当访问一个类中,加锁的静态方法或者静态语句块锁的时候,则该对象中所有加锁的静态方法和静态语句块全部锁定
//业务类
class MyClass_01{
public synchronized void m1() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(“加锁的m1方法”);
}
public synchronized void m2() {
System.out.println(“加锁的m2方法”);
}
public void m3() {
System.out.println(“未加锁的m3方法”);
}
}
class Processor_08 implements Runnable{
MyClass_01 mc;
public Processor_08(MyClass_01 mc) {
super();
this.mc = mc;
}
@Override
public void run() {
//获取线程的名字
String name = Thread.currentThread().getName();
if (name.equals(“t1”)) {
mc.m1();
} else if (name.equals(“t2”)) {
mc.m2();
}
if (name.equals(“t3”)) {
mc.m3();
}
}
}
public static void main(String[] args) {
MyClass_01 mc = new MyClass_01();
Thread t1 = new Thread(new Processor_08(mc));
Thread t2 = new Thread(new Processor_08(mc));
Thread t3 = new Thread(new Processor_08(mc));
t1.setName(“t1”);
t2.setName(“t2”);
t3.setName(“t3”);
t1.start();
// 保证t1先执行,进入m1方法执行睡眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
// 1秒后 m3执行,3秒后 m1和m2执行
}

1.10 Lock

lock 是显示锁,需要手动开启和关闭 synchronized是隐式锁,自动开启,执行完自动关闭
lock只有代码块锁 , 而 synchronized支持方法和代码块锁
lock锁,需要JVM花费较少的时间来进行资源调度.性能相对较好,而有很好的扩展性
使用顺序 : Lock锁 —> 同步代码块锁 —> 方法锁
// 余额
private double balance;
//创建锁对象
Lock lock=new ReentrantLock();
public void withDraw(double money) {
System.out.println(Thread.currentThread().getName()+" 进来了");
// 开始同步
lock.lock();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 余额减去取钱金额
double after = balance - money;
// 新余额复制给余额
balance = after; // 2000
System.out.println(Thread.currentThread().getName() + " 取钱成功,取款 : "
+ money + "元,剩余 : " + balance + " 元 ");
// 解锁
lock.unlock();

1.11 定时器任务

定时器 : 计划任务
只要有一个计划任务,就会开启一个线程,进行计时,到达指定时间后,由该线程来完成这个任务。
//创建任务
class LogTimerTask extends TimerTask {
@Override
public void run() {
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
String time = sdf.format(date);
System.out.println(time);
}
}
public static void main(String[] args) {
// 1 要做的事,也就是任务对象
// 2 什么时候开始做 , 10005 就是5秒之后开始
// 3 间隔时间,每隔多久做一次 1000
3 每3秒执行一次
Timer t = new Timer();
t.schedule(new LogTimerTask(), 10005,10003);
}

举报

相关推荐

0 条评论