程序、进程、线程
程序:为完成某些特定任务,用某种语言编写的一组指令的集合,代码
进程:程序的一次执行过程,正在运行的一个程序
【这是一个动态过程,产生,存在,完成某些功能,消亡】
线程:由进程创建的,是进程的一个实体,一个进程可以拥有多个线程
【举个例子:一个正在读书并同时听音乐的人正在努力学习】
【这个正在努力学习的这个行为对应进程,而读书、听英语对应线程】
单线程、多线程、并发、并行
单线程:同一个时刻,只允许执行一个线程
【读书不能听音乐,听音乐不能读书】
多线程:同一个时刻,可允许执行多个线程
【边听音乐边读书】
并发:同一个时刻,多个任务交替执行,形成“同时”的错觉
简单来说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行,多核CPU可实现
【一个CPU对应一个任务】
【可以在计算机的设备管理器中查看自己的电脑是几核几个处理器】
线程的基本使用
1)继承Thread类,重写run方法
2)实现Runnable接口,重写run方法(推荐)
【推荐原因,java是单继承,继承Thread类不能再继承,而接口可以多实现】
第一种方式:
class Cat extends Thread{
@Override
public void run() {
while(true){
int count = 0;
count++;
System.out.println("子线程");
try {
Thread.sleep(100); // 线程休眠 单位ms
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count == 50){
break;
}
}
}
}
public class Test1 {
public static void main(String[] args)
throws InterruptedException {
//main函数 --- 看作为主线程
Cat c = new Cat();
c.start(); // 开启一个独立的子线程
for (int i =0 ;i<50;i++){
System.out.println("主线程");
Thread.sleep(100); // 线程休眠 单位ms
}
// 主程序运行是否结束 --- 不会影响子线程
// 子线程和主线程都运行结束,程序才会停止
}
}
//注意:
//底层源码 --- 调start -> start0 --->调run 而不是直接调用run方法
//这里的run方法,由JVM机调用
//我们只需调用Thread中单start方法即可
第二种方式:
class Cat implements Runnable{
@Override
public void run() {
while(true){
int count = 0;
count++;
System.out.println("子线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (count == 50){
break;
}
}
}
}
public class Test2 {
public static void main(String[] args)
throws InterruptedException {
//main函数 --- 看作为主线程
Cat c = new Cat();
Thread T = new Thread(c);
//接口中没有start方法,但是可以通过创建Thread对象
//使用Thread的start方法调用
//本质上与方式一,没有太多区别
T.start();//开启一个独立的子线程
for (int i =0 ;i<50;i++){
System.out.println("主线程");
Thread.sleep(100);
}
// 主程序运行是否结束 --- 不会影响子线程
// 子线程和主线程都运行结束,程序才会停止
}
}
//注意: 无论方式一还是方式二,其中实现多线程的方式都不是run方法
// 而是start0方法,其底层是由C/C++实现的
// 在start方法调用start0方法后,该线程不一定立马执行
// 只是将线程变为可运行状态,什么时候执行,取决于CPU,由CPU统一调度
线程终止
当线程完成任务后会自动退出,可以通过使用变量来控制run方法的方式停止线程,即通知方式
将方式二中例子进行修改
public class Test2 {
public static void main(String[] args)
throws InterruptedException {
//main函数 --- 看作为主线程
Cat c = new Cat();
Thread T = new Thread(c);// 开启一个独立的子线程
T.start();
for (int i =0 ;i<10;i++){
System.out.println("主线程");
Thread.sleep(100);
}
// 在主线程中对子线程通知停止
c.setLoop(false);
System.out.println("program is over !");
}
}
class Cat implements Runnable{
private boolean loop = true;
public void setLoop(boolean loop) {
// 通过给loop赋值,是子线程停止运行,通知方式
this.loop = loop;
}
@Override
public void run() {
while(loop){ // 子线程是一个死循环 ---- 永远不会退出
System.out.println("子线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
用户线程、守护线程
用户线程:也叫工作线程,线程的任务执行完或由通知方式结束
守护线程:为工作线程服务,当所有的用户线程结束,守护线程自动结束
【垃圾回收机制 就是常见的守护线程】
// 守护线程
class Cat implements Runnable{
@Override
public void run() {
while(true){ // 子线程是一个死循环 ---- 永远不会退出
System.out.println("子线程");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Test2 {
public static void main(String[] args)
throws InterruptedException {
//main函数 --- 看作为主线程
Cat c = new Cat();
Thread T = new Thread(c);// 开启一个独立的子线程
// 我们希望当主线程结束后,子线程自动结束
// 在主线程中 将子线程设置为守护线程
T.setDaemon(true);
T.start();
for (int i =0 ;i<10;i++){
System.out.println("主线程");
Thread.sleep(100);
}
System.out.println("program is over !");
}
}
线程的状态
线程常用方法
1)setName 设置线程名称,可使其与参数name相同
2)getName 返回该线程的名称
3)start使线程开始执行 Java虚拟机底层调用start0方法
4)run 调用线程对象run方法
5)setPriority 更改线程优先级
6)getPriority 获取线程优先级
7)sleep 让当前正在执行的线程休眠(暂停执行)
8)interrupt 中断线程,中断不是终止线程,用于提前中断子线程的休眠
9)yield 线程的礼让,让出CPU,让其他线程先执行,但不一定成功
10)join 线程的插队,优先实现插队子线程的所有内容,完成后,再继续其他线程
线程同步机制(synchronized)
为什么需要线程同步?
// 三个窗口卖100票,出现了超卖现象
package com.TEST;
public class Test2 {
public static void main(String[] args) {
SellTicket s1 = new SellTicket();
Thread T1 = new Thread(s1);
Thread T2 = new Thread(s1);
Thread T3 = new Thread(s1);
T1.start();
T2.start();
T3.start();
}
}
//3个售票窗口,总售票100张,多线程实现
class SellTicket implements Runnable {
boolean loop = true;
Object obj = new Object();
private static int Ticket = 10;//总票数10张
@Override
public void run() {
while (loop) {
if (Ticket <= 0) {
System.out.println("售票结束~");
loop=false;
return;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"
+ Thread.currentThread().getName()
+ "售出一张票剩余"
+ (--Ticket)
+ "张票");
}
}
}
如何解决该问题,往下看
线程同步机制
1)在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就需要使用同步访问技术,保证任何时刻,最多有一个线程访问,以保证数据的完整性,当然了,它会牺牲一些访问效率
2)线程同步:当有一个线程在对内存操作时,其他线程不能对该内存操作,直到该线程完成操作,其他线程才能对该内存地址操作
线程同步具体方法
1)同步代码块
synchronized (对象) { // 得到对象的锁,才能操作同步代码
// 锁指的就是,对象,谁得到了这个对象,谁就可以访问
// 该区域的内容,且该对象只有一个
//需要同步的代码
}
//多个线程争夺唯一的一个锁,谁得到了锁,谁就可以访问
//简单来说就是,谁拿到了 对象(锁), 谁才能访问同步的代码
// 非静态同步方法、同步代码块(锁:默认为本类对象)
// 静态同步方法(锁:默认为类本身)
//始终只有一个线程可以得到该对象
2)同步方法
public synchronized void ok (传入参数){
// 需要同步的代码
}
【这种在任何时刻,只能有一个线程进行访问的状态,就是互斥锁的概念】
互斥锁
1)每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能由一个线程来访问对象
2)synchronized关键字来与对象的互斥锁练习,当一个对象用synchronized修饰时,表名该对象在任一时刻只能由一个线程访问
3)局限性:程序的执行效率会降低
4)非静态的同步方法的锁可以是this本类对象,也可以是其他对象
5)静态的同步方法的锁为当前类本身
使用互斥锁解决超卖现象的方式
// 利用同步方法 解决超卖现象
// 三个窗口卖100票,出现了超卖现象
package com.TEST;
public class Test2 {
public static void main(String[] args) {
SellTicket s1 = new SellTicket();
Thread T1 = new Thread(s1);
Thread T2 = new Thread(s1);
Thread T3 = new Thread(s1);
T1.start();
T2.start();
T3.start();
}
}
//3个售票窗口,总售票100张,多线程实现
class SellTicket implements Runnable {
boolean loop = true;
Object obj = new Object();
private static int Ticket = 10;//总票数10张
@Override
public void run() {
while (loop) {
sell();
}
}
public synchronized void sell(){
// 在任何时刻,只能有一个线程使用该方法 ,这也是互斥锁的体现
if (Ticket <= 0) {
System.out.println("售票结束~");
loop=false;
return;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("窗口"
+ Thread.currentThread().getName()
+ "售出一张票剩余"
+ (--Ticket)
+ "张票");
}
}
注意事项
1)同步方法如果没有使用static,默认锁对象为this
2)同步方法有static修饰,默认锁对象为当前类.class
3)多个线程锁的对象需为同一个
死锁
多个线程都占用了对方锁的资源,但不肯想让,导致了死锁
package com.TEST;
public class Test2 {
public static void main(String[] args) {
Dead d1 = new Dead(true);
Dead d2 = new Dead(false);
d1.start();
d2.start();
}
}
class Dead extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
private boolean flag;
public Dead(boolean flag){
this.flag = flag;
}
public void run(){
if (flag){
synchronized (o1){
System.out.println(Thread.currentThread().getName()
+ "1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName()
+ "2");
}
}
}else{
synchronized (o2){
System.out.println(Thread.currentThread().getName()
+ "3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName()
+ "4");
}
}
}
}
}
释放锁
1)当线程的同步方法、同步代码块执行结束时,自动释放锁
2)当前线程在循环中遇到了break或return
3)当前线程在循环中出现了未处理的error或Exception导致异常结束
4)当前线程执行了wait()方法,当前线程暂停,并释放锁
注意事项:
线程调用sleep()、yield()方法只是暂停当前线程的执行,不会释放锁。线程执行同步代码块时,其他线程调用了suspend()方法将该线程挂起,该线程不会释放锁。
【应尽量避免使用线程的suspend()和resume()方法,
且编译器也不在推荐使用,已过时】