Java多线程基础
线程概念
-
线程由进程创建,是进程的一个实体
-
一个进程可以拥有多个线程
-
单线程:同一时刻,只允许执行一个线程
-
多线程:同一时刻,可以执行多个线程,如一个QQ进程,可以打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
-
如何通过Java程序查询电脑CPU个数:
Runtime runtime = Runtime.getRuntime(); int cpuNums = runtime.availableProcessors(); System.out.println(cpuNums);
线程的使用
线程的创建
Java中创建线程的两种方法:
- 继承 Thread 类 ,重写 run 方法
- 实现 Runnable 类,重写 run方法
继承 Thread 类:
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
Cat cat = new Cat();
cat.start();
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i="+i);
Thread.sleep(1000);
}
}
}
//当一个类继承了Thread类,该类就可以当作线程使用
class Cat extends Thread{
@Override
public void run() { //重写run方法,写上自己的业务逻辑
int times = 0;
while (true) {
System.out.println("喵喵,我是小猫咪" + (++times));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
if (times == 80){
break;
}
}
}
}
Thread.sleep()
方法用于使当前执行的线程休眠(暂停执行)一段时间。它接受一个以毫秒为单位的参数,表示线程要休眠的时间长度。在休眠期间,线程不会执行任何代码。- 当一个类继承了
Thread
类,该类就可以当作线程使用 cat.start()
启动线程,而如果cat.run()
则不会开辟新线程,而是在主线程中调用run方法- 当 main 线程启动了一个子线程,主线程不会阻塞,会继续执行
- 使用 JConsle 可以监控线程执行情况
- 将程序运行起来,然后再
Terminal
中输入 jconsole,连接当前的类,查看线程即可
- 将程序运行起来,然后再
- 所有线程都结束,进程才会结束
实现 Runnable 接口:
-
java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承 Thread 类方法创建线程显然是不可能的。
-
Java设计者提供了另一个方式创建线程,就是通过实现 Runnable 接口来创建线程
-
public class Thread02 { public static void main(String[] args) { Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); } } class Dog implements Runnable{ int count = 0; @Override public void run() { while(true){ System.out.println("小狗汪汪叫" + (++count)); try { Thread.sleep(1000); } catch (InterruptedException e) { e.getMessage(); } } } }
-
start() 方法只能由 Thread 来调用,所以需要将实现了 Runnable 的类传入 Thread 然后再调用 start() 开启线程
继承 Thread和实现 Runnable 接口的区别
- 从Java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
线程终止
Java线程终止有两种方式:
- 当线程完成任务后,会自动退出
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class ThreadExit {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.start();
Thread.sleep(10000);
t.setLoop(false);
}
}
class T extends Thread{
int count = 0;
private boolean loop = true;
@Override
public void run() {
while(loop){
System.out.println("这是一个线程" + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void setLoop(boolean loop){
this.loop = loop;
}
}
线程常用方法
常用方法一:
- setName //设置线程名称,使之与参数name相同
- getName //返回该线程的名称
- start //使线程开始执行;Java虚拟机底层调用该线程的start0方法
- run //调用线程对象的run方法
- setPriority //更改线程的优先级
- getPriority //获取线程的优先级
- sleep //在指定地点毫秒数内让当前正在执行的线程休眠(暂停执行)
- interrupt //中断线程
细节说明:
- start 底层会创建新的线程,调用run,而run就是一个简单的方法调用,不会启动新线程
- 线程优先级范围 常用:MIN_PRIORITY = 1, NORM_PRIORITY = 5, MAX_PRIORITY = 10;
- interrupt 是中断线程,并没有结束线程,所以一般用于中断正在休眠线程
public class ThreadMethod {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("jack");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi");
}
System.out.println("jack线程的优先级" + t.getPriority());
t.interrupt();
//主线程经过五秒后,主动中断子线程的休眠,使其继续工作
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "正在工作");
}
try {
System.out.println(Thread.currentThread().getName() + "休眠中");
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被interrupt");
}
}
}
}
常用方法二:
- yield :线程的礼让。让出CPU,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
- join :线程的插队。插队的线程一旦插队成功,则肯定先执行完插入到线程所有的任务
让子线程和主线程同时每隔一秒工作一次,共工作20次,当主线程工作5次后,让子线程完成后面所有工作,然后主线程再继续工作。 join()插队一定可以,yield()礼让不一定
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
TT tt = new TT();
tt.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "正在工作" + i);
Thread.sleep(1000);
if (i ==5){
tt.join();
//Thread.yield();
}
}
}
}
class TT extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "正在工作" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
守护线程:(setDaemon)
用户线程:也叫工作线程,当线程的任务执行完或以通知的方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
当用户线程结束后,如果子线程还没有执行完,那么将会一直执行下去,这时可以将子线程设置为守护线程,tt.setDaemon(true); ,那么当用户线程结束时,子线程也将随之而结束。
常见的守护线程:垃圾回收机制
线程的生命周期
线程七大状态
其中,Runnable 状态可以细分为 Ready 和 Running,就绪态和运行态
使用 t.getState() 可以查看当前线程所处状态
线程同步机制
- 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性
- 也可以理解为:线程同步,即当有一个线程对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对内存地址进行操作
同步具体方法——Synchronized
-
同步代码块
synchronized(对象){ //得到对象的锁,才能操作同步代码
// 需要被同步的代码块
}
-
synchronized 还可以放在方法声明中,表示整个方法为同步方法
public synchonized void m (String name){
//需要被同步的代码
}
public class syn_ {
public static void main(String[] args) {
SellTicket3 sellTicket3 = new SellTicket3();
new Thread(sellTicket3).start();
new Thread(sellTicket3).start();
new Thread(sellTicket3).start();
}
}
class SellTicket3 implements Runnable {
private int tickets = 100;
private boolean loop = true;
public /*synchronized*/ void sell() {
synchronized (this) {
if (tickets <= 0) {
System.out.println("售票结束....");
loop = false;
return;
}
System.out.println(Thread.currentThread().getName() + "卖出了一张票,剩余票数" + (--tickets));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
锁
互斥锁
基本介绍:
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应一个可称为“互斥锁”的标记,这个标记用来保证在任意时刻,只能有一个线程访问该对象
- 关键字 synchonized 来与对象的互斥锁联系。当某个对象使用 synchonized 修饰时,表明该对象在任意时刻只能由一个进程来访问
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)都锁为当前类本身
使用细节:
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求对个线程的锁对象为同一个即可(当使用继承Thread方法创建线程时,创建多个线程就不是同一个对象,即锁不住!)
线程的死锁
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁。
public class DeadLock {
public static void main(String[] args) {
DeadLockDemo deadLockDemo = new DeadLockDemo(true);
DeadLockDemo deadLockDemo2 = new DeadLockDemo(false);
deadLockDemo.start();
deadLockDemo2.start();
}
}
class DeadLockDemo extends Thread{
static Object o1 = new Object();
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){
this.flag = flag;
}
@Override
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() + "进入2");
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "进入1");
}
}
}
}
}
释放锁
下面操作会释放锁:
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
下面操作不会释放锁:
- 当前执行同步代码块或同步方法时,程序调用*Thread.sleep()、Thread.yield()*方法,暂停当前线程的使用,不会释放锁
- 线程执行同步代码块时,其他线程调用了该线程的*suspend()*方法将该线程挂起,该线程不会释放锁。
- 注意:应尽量避免使用 suspend() 和 resume() 来控制线程