本文章,系作者本人整理。参考狂神说JAVA 链接在此
一、多线程
概述:
一个程序就是一个进程,而一个程序中的多个任务则被称为线程。进程是表示资源分配的基本单位,又是调度运行的基本单位。,亦即执行处理机调度的基本单位。 进程和线程的关系:
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。
处理机分给线程,即真正在处理机上运行的是线程。
线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
1. 实现方法
-
继承Thread 类
例:
public _Thread extends Thread { @Override //线程入口点 public void run() { //线程体 //重写父类的 run方法 super.run(); //父类的run()方法 for (int i = 0; i < 30; i++) { System.out.println("我在学习多线程-----" + i); } } public static void main(String[] args) { //main线程,主线程 _Thread d = new _Thread(); //创建一个线程对象 d.start(); //调用start 方法 开启线程 for (int i = 0; i <30 ; i++) { System.out.println("我在学习java-----"+i); } } }
部分运行结果
-
实现Runnable 接口
例:
public class _Runnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("我在学习java---" + i);
}
}
public static void main(String[] args) {
// 创建对象
_Runnable r = new _Runnable();
//创建线程对象,通过线程来开启线程 , 代理
Thread t = new Thread(r); //把线程 对象放到 空的Thread 线程对象 里面。
t.start();
//也可以 直接用匿名对象。 new Thread(r).start;
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程---" + i);
}
System.out.println(Thread.currentThread().getName() + "的状态是:" + t.getState()+Thread.currentThread().isAlive());
}
}
Tips : 都要重写 Run()方法。
2. 生命周期
3. 线程控制
3.1 sleep
实现Runnable接口的 用Thread.sleep(毫秒数)
继承Thread类的直接用 sleep(毫秒数)
但要 用 try...catch 包围
try {// 休眠000毫秒,用来模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
3.2 优先级
线程有两种调度模型 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择个, 优先级高的线程 获取的CPU时间片相对多一些 Java使用的是抢占式调度模型 假如计算机只有一个CPU,那么CPU在某一个时刻只能执行条指令,线程只有得到CPU时间片,也就是使用权, 可以执行指令。所以说多线`程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的 Thread类中设置和获取线程优先级的方法 public final int getPriority:返回此线程的优先级 public final void setPriority(int newPriority):更改此线程的优先级
public class Youxianji implements Runnable{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
Main(){
Youxianji y = new Youxianji();
Thread t1 = new Thread(y, "高铁");
Thread t2 = new Thread(y, "飞机");
Thread t3 = new Thread(y, "火箭");
}
//设置线程优先级 最低是1 最高是10 , 默认是5
t1.setPriority(1);
t2.setPriority(1);
t3.setPriority(10); // 优先级高,仅表示 获取cpu时间片的 几率高
System.out.println(t1.getPriority()); //返回优先级
System.out.println(t2.getPriority());
System.out.println(t3.getPriority());
t1.start(); //启动线程
t2.start();
t3.start();
运行结果 可以看出, “火箭”线程优先级高,获取cpu时间片的概率就大一些。
可见,“火箭”线程 先运行结束
3.3 守护线程
守护线程--也称“服务线程”,在没有用户线程可服务时会自动离开。优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。
Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。
package Kongzhi;
//守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
public class _Daemon implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class _Daemo_Run {
public static void main(String[] args) {
_Daemon d = new _Daemon();
Thread t2 = new Thread(d, "老二:");
Thread t3 = new Thread(d, "老三:");
// 老二老三 要守护 大哥大, 大哥大线程运行完之后, 守护线程也会 随后死亡。 不是立即死亡、
Thread.currentThread().setName("大哥大");
t2.setDaemon(true);
t3.setDaemon(true);
t2.start();
t3.start();
//设置主线程为 大哥大
for (int i = 0; i < 10; i++) {
System.out.println("我是" + Thread.currentThread().getName() + i);
}
}
}
3.4 join
join (强制执行)
调用join方法的线程 执行完毕后,其他线程才会执行 。
就好比 插队 插到了第一个位置
也要被try...catch 围绕
public class _Join implements Runnable{
@Override
public void run() {//线程体
for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+i);
}
}
}
class Join_Run{
public static void main(String[] args) {
_Join j1 = new _Join();
Thread t1 = new Thread(j1, "老大:");
Thread t2 = new Thread(j1, "老二:");
Thread t3 = new Thread(j1, "老三:");
t1.start(); // 守护线程要 放到 前面
try {
t1.join(); //join t1 执行完毕后 其他线程才会执行其他线程, 其他线程会处于阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
运行结果 可以看出,t1线程(老大) 运行完之后, t2 和 t3 线程 才运行
3.5 start
顾名思义 , 启动线程的意思
继承Thread类时
_Thread d = new _Thread(); //线程对象 d.start; // 也可以直接 new _Thread().start;
实现Runnable接口时
_Thread d = new _Thread();//线程对象
Thread t = new Thread(d);
t.start;
//也可以直接 new Thread(d).start;
3.6 setName & getName
线程可以取名字
默认名为 Thread-0 -1 -2 .....
为了 提高可读性, 可以给线程命名
在继承 Thread类时
//创建线程对象
_Thread_get_and_set t = new _Thread_get_and_set();
t.setName("狗儿子"); //setName 赋予线程 名称
t.start();
在实现 Runnable接口时
Thread.currentThread().getName() 可以获取当前线程的 线程名
//Thread类的构造方法
public Thread(Runnable target, String name) {
this(null, target, name, 0);
}
public _Runnable_get_and_set{
public void run(){
for(int i = 1 ; i<10;i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
//创建线程对象
Main(){
_Runnable_get_and_set t = new _Runnable_get_and_set();
new Thread(t , "小张").start;
new Thread(t , "小李").start;
}
运行结果
3.7 线程同步
为了防止统一数据,被多个线程使用 出现数据重复 和 出现负数的问题
可以使用 synchronizde(同步) 解决此问题 . 或者写一个 被synchronizde 修饰的方法.
锁多条语句操作共享数据,可以使用同步代码块实现
好处:解决了多线程的数据安全问题 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的, 无形中会降低程序的运行效率
用法:
private Object obj = new Object(); synchronized(任意对象 obj){ // 就相当于给代码加锁了,任意对象就可以看成是一把锁 // 多条语句操作共享数据代码块 }用synchronizde 修饰 方法
public void run { sale(); //在重写run方法中调用 被synchronized 修饰的sale方法 } public synchronized void sale() { // 同步方法中的代码为关键代码 }
同步方法:就是把synchronized关键字加到方法上 ●格式: 修饰符 synchronized 返回值类型 方法名(方法参数) {
}
同步方法的锁对象是什么呢? ●this 同步静态方法:就是把 synchronized关键字加到静态方法上
●格式: 修饰符static synchronized返回值类型方法名(方法参数){ }
同步静态方法的锁对象是什么呢?
●类名.class
3.8 Lock锁
//Lock是接口,不能直接生成对象。 可以用多态的方式 Lock lock = new ReentrantLock();
3.9 yield 线程礼让
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让CPU重新调度,礼让不一定成功!看CPU心情
Thread.yield
4.0 wait 线程等待 和 notify 唤醒线程
1.wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
2.notify()和notifyAll()的作用:notify()则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
3.wait(long timeout)让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。