虽然我觉得详细但是别人不一定觉得详细的,关于Java多线程的知识点。
文章目录
一、概述(程序、进程、线程
1.程序是一段静态的写好的代码
以软件为例,下载下来的文件就是一段代码(即程序
2.进程是运行的程序
通过程序的入口(如.exe) 可以运行程序,得到一个进程。多次运行同一个程序,得到多个不同的进程。
3.线程是独立运行的最小单元
早期操作系统拥有资源和独立运行的最小单位是进程。随着发展,发明了线程。
二、Java实现多线程
多线程通过java.lang.Thread类来实现
- 每个线程都是通过某个特定Thread对象的run()方法来实现的。run方法通常被称为线程体。
- 通过Thread对象的start()方法来启动线程。直接调用run()方法,只是运行了一个普通方法。
2.1 继承Thread类实现多线程
//定义MyThread类继承Thread
public class MyThread extends Thread{
@Override //重写run方法,线程中要做的事情都写在这里。
public void run() {
System.out.println("我的线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();//创建实例
myThread.start();//通过start方法启用线程
}
优点:实现简单 | 缺点:扩展性差
2.2 实现Runnable接口实现多线程
//实现Runnable接口
public class MyThread implements Runnable{
@Override
public void run() {
System.out.println("我的线程");
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();//仍然是创建实例(但是可以理解成创建一个任务
Thread thread = new Thread(myThread);//注意到这里把实例传入到了Thread的构造方法中(相当于把任务提交给thread去处理
thread.start();//thread开始干活
}
优点:扩展性好 | 缺点:实现复杂
2.3 Thread生命周期和常用方法
生命周期顾名思义就是从诞生到消亡
- 新建(New: 实例创建的时候,线程就诞生了。
通过start()方法进入就绪状态
- 就绪(Runnable: 这个时候具备了执行资格,但是还没有执行权。会不停的去抢占CPU。
抢到手以后进入执行状态
- 运行(Running: 线程开始执行。
运行完毕,寿终正寝,进入死亡状态 | 被其他线程抢走了CPU的执行权进入就绪状态 | 阻塞方法,进入阻塞状态
- 阻塞(Blocked: 被中止了运行,又回不到就绪状态。
阻塞方法结束,进入就绪状态(只能进入就绪状态,运行状态的唯一入口是就绪状态
- 死亡(Dead: 程序运行完毕或者被迫终止
String getName() //返回线程的名字 默认名字是Thread-X,X是第几个从0开始。
void setName(String name) //设置线程的名字,也可以用构造方法
static Thread currentThread() //获取当前线程的对象
static void sleep(long time) //让线程休眠指定时间,单位毫秒
setPriority(int newPriority) //设置线程的优先级
final int getPriority() //获取线程的优先级
final void setDaemon(boolean on) //设置为守护线程 //守护线程在非守护结束后也会陆续结束
public static void yield() //出让线程/礼让线程 //扔出后还会再抢
public static void join() //插入线程/插队线程
start()//设置要在开始前
三、线程安全
多个线程同时访问一个共享数据时,容易出问题,这个就是线程不安全
//这样子写,共享数据是test
public int test = 1;
public void run() {
while(true){
if(test > 0){//如果只有一个线程,那么什么问题都没有
try {
Thread.currentThread().sleep(20);//不管睡不睡,都会有其他线程也通过判断(因为这时候还没有对test进行修改。睡只是为了让效果更容易出现。
test --;//每个通过判断的线程都会进行--操作,最终test的值小于0,和单线程的情况不一致
System.out.println(test);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread_1 = new Thread(myThread);
Thread thread_2 = new Thread(myThread);
thread_1.start();
thread_2.start();
}//输出结果有-1确实越界了(如果test代表唯一标识,还可能会出现重复。因此是不安全的
3.1 同步代码块synchronized
为了线程安全,对共享数据保证同一时刻只有一个进程进行访问
synchronized(Object o){ 需要控制的代码块儿 }//需要一个唯一的对象来当锁,可以用类名.class来当
public void run() {
while(true){
//将共享数据放进同步代码块,此时线程是安全的。(但是由于只能一个线程来访问,所以会降低运行效率
synchronized (MyThread.class){
if(test > 0){
try {
Thread.currentThread().sleep(20);
test --;
System.out.println(test);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
3.2 同步方法
修饰符 synchronized 返回值类型 方法名(方法参数){方法体;}
//将同步代码块中的代码写在同步方法中,方法略有改动
public void run() {
while(true){
if(!shell()){
break;
}
}
}
public synchronized Boolean shell(){//注意到和同步代码块的区别,不需要手动加锁了。
boolean flag = true;
if(test > 0){
try {
Thread.currentThread().sleep(20);
test --;
System.out.println(test);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
flag = false;
}
return flag;
}
创作不易,叹一会儿气