一、什么是多线程
进程:应用程序执行实例
线程:进程中执行运算的最小单元
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
多个线程交替占用CPU资源,而非真正的并行执行
多线程好处
充分利用CPU的资源
简化编程模型
带来良好的用户体验
同步和异步
同步是阻塞模式,一个进程执行一个线程,需等待这个线程结束,才能执行下一个线程,否则只能一直等待
异步是非阻塞模式,一个进程执行一个线程,若这个线程未完成,仍能执行另外的线程,相互之间互不影响
二、线程的状态(生命周期)
共分为五个状态:创建状态、就绪状态、阻塞状态、运行状态、死亡状态
三、创建线程的方式
(1)继承java.lang.Thread类
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MyThreadTest {
public static void main(String[] args) {
MyThread thread1 = new Mythread();
MyThread thread2 = new Mythread();
thread1.start();
thread2.start();
}
}
(2)实现java.lang.Runnable接口
public class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class RunableTest {
public static void main(String[] args) {
MyRunable r1 = new MyRunable();
MyRunable r2 = new MyRunable();
Thread thread1 = new Thread(r1);
Thread thread2 = new Thread(r2);
thread1.start();
thread2.start();
}
}
注意:
(1)多个线程交替执行,不是真正的“并行”
(2)线程每次执行时长由分配的CPU时间片长度决定
(3)main方法会启动一个主线程,也叫守护线程
两种创建线程的方式的区别:
(1)继承Thread类
编写简单,可直接操作线程
适用于单继承
(2)实现Runnable接口
避免单继承局限性
便于共享资源
线程中比较run()和start()两者的区别
(1)单独调用run()就是普通方法,跟线程无关
(2)调用start()方法会启动一个线程
四、线程的常用方法
(1)void setPriority(int newPriority) 更改线程的优先级
线程的优先级的范围是:1~10,1为最低,线程包括主线程的默认优先级都为5;
优先级只表示线程获得CPU资源的概率大小,并不代表不会运行
(2)static void sleep(long millis) 线程休眠指定毫秒数,参数为毫秒
线程休眠是让线程进入阻塞状态,休眠时间过去线程进入就绪状态,重新运行
(3)void join() 中断正在运行的线程,执行join 的线程,直到该线程全部运行完毕,再执行其他未完成的线程
(4)static void yield() 线程礼让
该线程处于就绪状态,不转为阻塞状态
暂停当前线程,允许其他具有相同优先级的线程获得运行机会,但只是允许,让不让看心情,并不绝对
(5)void interrupt() 线程中断,中断当前线程的阻塞状态
// 中断线程
Thread.currentThread().interrupt();
//给线程打上中断标签
Thread.currentThread().interrupted();
//判断线程是否中断
Thread.currentThread().isInterrupted();
(6)boolean isAlive () 测试线程线程是否处于活动状态
(7)void notify() 唤醒当前线程
(8)void notifyAll() 唤醒所有线程
(9)void wait() 让当前线程进入阻塞状态,同时释放当前线程所持有的锁
void wait(long millis) 线程等待指定时间,自动被唤醒
synchronized(),wait(),notify() 对象一致性,wait和notify方法都应该在synchronized中使用
五、共享数据引起的并发问题
多个线程共享一个数据会引起并发问题
public class Site implements Runnable {
// 记录剩余的票数
private int count = 10;
// 记录买到第几张
private int num = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
if (count <= 0) {
break;
}
count--;
num++;
}
System.out.println(Thread.currentThread().getName() + "抢到第" + num + "张票,剩余" + count + "张票!");
}
}
}
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread t1 = new Thread(site,"黄牛党");
Thread t2 = new Thread(site,"抢票代理");
Thread t3 = new Thread(site,"张三");
System.out.println("**********开始抢票**************");
t1.start();
t2.start();
t3.start();
}
}
使用synchronized
(1)锁对象,所代码块本质上也是锁对象
多个并发线程访问同一资源的同步代码块时,同一时刻只能有一个线程进入synchronized(this)同步代码块
当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码块
(2)锁方法
线程安全的效率
当使用锁时,线程安全,但因为多个线程共享一把锁,会造成效率较低