线程基础
文章目录
1.概念
程序
存储在硬盘上的代码,特点:静止的文件,占用外存而不是内存。
进程
运行硬盘上的代码,特点:运行中的文件,占用内存,实例:启动IDEA,就启动了一个进程,操作就会为该进程分配内存空间。
进程是程序的一次执行过程,或者是正在运行中的文件。是一个动态的过程:有自己的生命周期:产生、存在、销毁。
线程
线程是由进程创建,是进程的一个实体。
一个进程可以有多个线程。
其它
单线程:同一个时刻,进程(程序)只允许执行一个线程。
单线程:同一个时刻,进程(程序)可以执行多个线程。
并发:同一个时刻,进程(程序)交替执行多个线程,单核cpu使用并发。
并行:同一个时刻,进程(程序)同时执行多个线程,多核cpu可以使用并行。
2.线程的使用
2.1 继承Thread类
Thread实现了Runnable接口
快速入门
public class QuickStart extends Thread{
@Override
public void run() {//在run方法中定义新线程所要执行的任务
// 该方法是来自Runnable接口,Thread类仅仅是重写该方法
for (int i = 0; i < 100; i++) {
System.out.println("新线程"+Thread.currentThread().getName()+"的任务正在执行中!");
}
//线程休眠
try {
Thread.sleep(1000);//单位ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new QuickStart().start();//start方法是开启新线程并执行新线程任务run方法
//主线程
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+Thread.currentThread().getName()+"的任务正在执行中!");
}
}
}
当程序(进程)启动时,首先执行main方法(开启一个线程),在main方法中又开启了一个新的线程,程序(进程)使用并发的方法交替执行两个线程。
Thread类实现了Runnable接口,重写了run方法,run方法就是定义当前线程所要执行的任务;我们的线程类只需要继承Thread类,重写run方法定义自己的线程的任务,然后通过start方法开启新的线程即可。
run方法:定义线程所要执行的任务。
start方法:开启一个新的线程。
sleeep方法:使当前线程休眠一定的时间,程序(进程)在休眠的期间不会执行该线程。
进程的生命周期:产生、存在、销毁,在快速入门中当两个线程全都结束任务后,进程销毁。
注意点
//直接调用run方法
new QuickStart().run();
如果直接调用run方法,此时就是普通的方法执行,因为并没有开启一个新的线程,仍然只有程序(进程)刚启动时所创建的main线程。
开启一个新的线程,自动执行run方法。
重点
使用start方法开启一个新的线程的底层是继续调用start0方法,这个start0方法是一个本地方法,由本地去执行。
start方法调用start0方法后,新的线程并不一定会立即执行,只是将线程变成了可运行状态,具体的调用时机,取决于本地的调度算法,也就是取决于cpu,由cpu统一调度管理。
2.2 重写Runnable接口
快速入门
public class QuickStart implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("新线程"+Thread.currentThread().getName()+"的任务正在执行中!");
}
//线程休眠
try {
Thread.sleep(1000);//单位ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Thread(new QuickStart()).start();//使用Thread的有参构造传入线程类
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+Thread.currentThread().getName()+"的任务正在执行中!");
}
}
}
设计模式:代理
使用静态代理模式
模拟静态代理
-
创建Person接口
public interface Person { void eat(); }
-
创建ChildrenProxy代理类实现Person接口,并添加特有方法
public class ChildrenProxy implements Person { private Children children; public ChildrenProxy(Children children) { this.children = children; } @Override public void eat() { children.eat(); System.out.println("孩子们喜欢喝饮料!"); } //特有的方法 public void proxy(){ eat(); } }
-
创建接口实现类,并调用代理对象特定方法
public static void main(String[] args) { //1.原始对象 Children children = new Children(); //2.代理对象,增强了原始对象,但是需要原始对象的部分代码 new ChildrenProxy(children).proxy(); }
代理:就是对原有对象的增强。
2.3 Runnable接口和Thread类
- Runnable接口可以解决Java的单继承模式
- 实现Runable接口更加适合多个线程共享一个资源的情况,因为在Runnable接口仅仅定义了如果开启线程要执行的任务,而开启线程依旧需要Thread类,我们可以使用同一个类去创建两个线程,两个线程共享同一个资源类
3.经典多线程买票
Thread类
public class SellTicket01 extends Thread{
private static int tickets = 100;
@Override
public void run() {
while (true){
if(tickets <= 0) {
break;
}else {
System.out.println("窗口"+Thread.currentThread().getName()+"售票成功,剩余票数:"+tickets);
tickets--;
}
}
}
public static void main(String[] args) {
new SellTicket01().start();//开启线程1
new SellTicket01().start();//开启线程2
new SellTicket01().start();//开启线程3
}
}
问题:3个线程剩余票数不同,且票数不能递减,超卖。
分析:产生这些问题的原因是程序(进程)是并发执行,如果已经经过了判断,但是在其它线程已经将票卖完,再次来到这个线程时则不会再判断,直接卖票,造成了超卖。
Runnable接口
public class SellTickets02 implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (true){
if(tickets <= 0) {
System.out.println("售票结束,由"+Thread.currentThread().getName()+"结束");
break;
}else {
System.out.println("窗口"+Thread.currentThread().getName()+"售票成功,剩余票数:"+tickets);
tickets--;
}
}
}
public static void main(String[] args) {
SellTickets02 target = new SellTickets02();
new Thread(target).start();
new Thread(target).start();
new Thread(target).start();
}
}
问题:超卖
4.线程终止
- 自然终止
- 设置某个变量或条件控制终止
public class ThreadExit extends Thread{
private boolean loop;
public ThreadExit(){
this.loop = true;
}
@Override
public void run() {
while (loop){
System.out.println(Thread.currentThread().getName()+"线程正在运行中!");
}
}
public void setLoop(boolean loop){
this.loop = loop;
}
public static void main(String[] args) throws InterruptedException {
ThreadExit threadExit = new ThreadExit();
threadExit.start();
Thread.sleep(5000);
threadExit.setLoop(false);
}
}
5.线程常用方法
常用一
- 获取线程名
- 修改线程名
- 线程休眠
- 线程中断
- 获取线程优先级
- 修改线程优先级
public class Group01 extends Thread{
@Override
public void run() {
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+"正在运行中!");
try{
System.out.println(Thread.currentThread().getName()+"正在休眠中!");
Thread.sleep(5000);
}catch (InterruptedException interruptedException) {//中断异常
System.out.println(Thread.currentThread().getName()+"中断休眠!");
}
}
}
public static void main(String[] args) {
Group01 thread = new Group01();
thread.setName("灰二");
thread.start();
System.out.println("新线程名字:"+thread.getName());
// thread.setPriority(Thread.MAX_PRIORITY);//10
// thread.setPriority(Thread.NORM_PRIORITY);//5
// thread.setPriority(Thread.MIN_PRIORITY);//1
System.out.println("新线程优先级:"+thread.getPriority());//1 5 10(常量)
for(int i = 0;i < 100;i++){
System.out.println(Thread.currentThread().getName()+"正在运行中!");
if(i % 10 == 0){
thread.interrupt();
}
}
thread.interrupt();//中断线程,但是并没有结束线程,一般用来中断正在休眠的线程(中断会抛出异常
// ,可以捕获进行后续操作)
}
}
注意点:设置线程优先级并不一定会生效,最终解释权由cpu归属;设置线程名必须要在线程启动之前;中断线程并不会造成线程停止,只是中断当前的操作,多用来中断休眠。
常用二
- 线程让步
- 线程插队
public class Group02 extends Thread{
@Override
public void run() {
for(int i = 0;i < 20;i++){
System.out.println(Thread.currentThread().getName()+"正在运行中!");
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
if(i == 5){
Thread.currentThread().yield();//线程让步,让出cpu的控制权,可能不会成功
}
}
}
public static void main(String[] args) throws InterruptedException {
Group02 thread = new Group02();
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"正在运行中!");
Thread.sleep(2000);
if(i == 5){
thread.join();//线程插队,直到thread线程执行完毕,主线程继续执行
}
}
}
}
注意点:yield是线程让步,表示让出自己的cpu控制权,cpu去执行其它线程,可能不会成功;join是线程插队,表示让线程插入当前线程,只有插入线程执行完毕后,当前线程才能继续执行。
6.用户线程和守护线程
**用户线程:**工作线程,当线程的任务执行完毕或者以通知的方式结束。
**守护线程:**工作线程服务,当所有的用户线程结束,守护线程自动结束。垃圾回收机制就是常见的守护线程
public class QuickStart extends Thread{
@Override
public void run() {
while (true){
System.out.println("守护线程执行中!");
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
public static void main(String[] args) {
QuickStart thread = new QuickStart();
thread.setDaemon(true);//设置为守护线程
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("用户线程运行中!");
}
System.out.println("用户线程结束!");
}
}
注意点:设置守护线程应该在线程启动之前。
7.线程的生命周期
官方文档显示:Java线程的生命周期是6个。
- new:尚未启动的线程
- runnable:运行
- ready:就绪,一切就绪,只待cpu
- running:运行,cpu正在执行中
- 使用yield方法,可以从运行->就绪,可能不会成功,因为并没有跳出运行区
- blocked:阻塞,排队
- 等待锁到达同步区
- 获得锁到达就绪状态
- waiting:等待
- wait,join,LockSupport.park()到达等待状态
- notify,notifyAll,LockSupport.unpark()到达就绪状态
- time waiting:超时等待
- sleep,wait,join,LockSupport.parkNanos(),LockSupport.parkUntill()进入超时等待状态
- 时间结束到达就绪状态
- terminated:终止
public class ThreadState extends Thread{
@Override
public void run() {
while (true){
System.out.println(Thread.currentThread().getName()+"运行中!");
try {
Thread.sleep(2000);
} catch (InterruptedException interruptedException) {
System.out.println(Thread.currentThread().getName()+"休眠中!");
}
}
}
public static void main(String[] args) throws InterruptedException {
ThreadState threadState = new ThreadState();
System.out.println(threadState.getName()+"当前状态是:"+threadState.getState());
threadState.start();
while (State.TERMINATED != threadState.getState()){
System.out.println(threadState.getName()+"当前状态是:"+threadState.getState());
Thread.sleep(1000);
}
}
}
8.线程同步机制
使用同步机制解决买票的超卖问题。
线程同步机制
在多线程中,一些数据不允许被多个线程同时操作,此时就可以使用线程同步机制保证在任何时刻只能有一个线程操作该数据。
线程同步:操作数据时,一次只能由一个线程操作。
形象比喻:
{
数据:一个单间厕所
同步:一次只能进入一个人上厕所,其它人在门外等待
}
同步方法
- 同步代码块:synchronized(对象){}
- public synchronized void method(){}
public class SellTicket01 extends Thread{
private static int tickets = 100;
private static boolean loop = true;
@Override
public void run() {
while (loop){
sell();
}
}
public synchronized void sell(){
if (tickets <= 0) {
System.out.println("售票结束,由" + Thread.currentThread().getName() + "结束");
loop = false;
return;
} else {
System.out.println("窗口" + Thread.currentThread().getName() + "售票成功,剩余票数:" + --tickets);
try {
Thread.sleep(50);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
public static void main(String[] args) {
new SellTicket01().start();//开启线程1
new SellTicket01().start();//开启线程2
new SellTicket01().start();//开启线程3
}
}
注意点:票数减1时,最好直接使用自减,耗时小,测试更加精确。
public class SellTickets02 implements Runnable{
private int tickets = 100;
@Override
public void run() {
while (true){
synchronized (this){
if(tickets <= 0) {
System.out.println("售票结束,由"+Thread.currentThread().getName()+"结束");
break;
}else {
System.out.println("窗口"+Thread.currentThread().getName()+"售票成功,剩余票数:"+--tickets);
}
}
}
}
public static void main(String[] args) {
SellTickets02 target = new SellTickets02();
new Thread(target).start();
new Thread(target).start();
new Thread(target).start();
}
}
同步方法的局限性
程序的执行效率降低。
同步方法的锁
非静态方法的锁:this,或其它唯一标识对象
静态方法的锁:类本身 类.class
同步的原则
- 尽可能使用同步代码块
- 多个线程的锁必须是同一个
- 划定公用资源,即为公共区域
9.死锁
public class QuickStart extends Thread{
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
private boolean flag;
public QuickStart(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag){
synchronized (lock1){
System.out.println("进入A");
synchronized (lock2){
System.out.println("进入B");
}
}
}else {
synchronized (lock2){
System.out.println("进入C");
synchronized (lock1){
System.out.println("进入D");
}
}
}
}
public static void main(String[] args) {
QuickStart t = new QuickStart(true);
QuickStart f = new QuickStart(false);
t.start();
f.start();
while (t.getState() != State.TERMINATED){
System.out.println(t.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
while (f.getState() != State.TERMINATED){
System.out.println(f.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
}
10.释放锁
- 线程的同步方法执行结束。
- 线程在同步代码块,同步方法中遇到break、return。
- 线程在同步代码块发生异常。
- 同步方法中执行了wait方法,当前线程暂停并释放锁。
11.不释放锁
- sleep、yield
- suspend