多线程
一、多线程中的几个概念
1.程序:静态的代码
2.进程:正在运行的一个程序 正在使用的QQ,Android Studio。进程用于管理所有的资源,不进行实际的任务
3.线程:完成具体任务,QQ运行起来就是线程(一个进程里面可以有多个线程)。运行QQ,聊天、视频、QQ游戏同时运行,这就是一个个线程
4.主线程:Java里面,main方法里面的代码就在主线程中运行。在手机里面,我们看到的主界面,就是一个主线程
5.子线程:除了主线程之外的线程
二、为什么使用多线程
在主线程里面,任务的执行是从上至下的,如果其中一个任务需要耗费大量时间。那么这个任务后面的任务就必须等这个任务结束后才能被执行,就会形成阻塞。这个时候就需要将这个任务放在另一个线程里面去执行(子线程)
*注:不管是主线程还是子线程都有自己独立的执行路径
三、如何开启一个线程
1.写一个类继承于Thread
步骤:
(1)创建类继承于Thread,具体执行的任务放在run()里面
(2)创建类的对象
(3)调用start()方法执行
注*线程的执行是通过抢占时间片来获取执行机会的,时间片是由操作系统来分配的
所以有多个线程的时候,每次执行的结果可能不一样
class TestThread extends Thread{
//1.创建一个类继承于Thread
//可以通过重写构造方法给子线程命名
public TestThread(@NonNull String name) {
super(name);
}
@Override
//子类必须实现父类的run方法,这个线程执行的任务在run方法里面
public void run() {
System.out.println(getName());
//也可以用Thread.currentThread(),获取当前线程的名字
System.out.println("Hello World");
}
}
public class MyClass {
public static void main(String[] args){
//2.创建具体的对象
TestThread testThread = new TestThread("子线程");//给子线程命名
//3.启动线程,不调用start无法启动线程
testThread.start();
}
}
public static void testRunnable(){
//2.创建具体对象
TestRunnable testRunnable = new TestRunnable();
//3.创建一个Thread对象 让这个线程去执行testRunnable的任务
Thread thread = new Thread(testRunnable);
thread.start();
}
}
2.写一个类实现Runnable接口
步骤:
(1)创建一个类实现Runnable接口,但它并不能分配线程
(2)创建一个该类的对象
(3)创建Thread类的对象来创建线程
(4)调用start方法开启线程
class TestRunnable implements Runnable{
//1.创建一个类实现Runnable方法
//这个类不能开启线程,也需要通过Thread来开启
@Override
public void run() {
System.out.println("Hello World");
}
}
public class MyClass {
public static void main(String[] args){
//2.创建具体对象
TestRunnable testRunnable = new TestRunnable();
//3.创建一个Thread对象 让这个线程去执行testRunnable的任务
Thread thread = new Thread(testRunnable);
thread.start();
}
}
3.两种启动方式的对比
第一种创建方法简单一些,但是无法实现多继承
第二种灵活性更强,因为接口可以实现多继承
四、线程的生命周期
线程的5种形态
new创建状态->start就绪状态-><-抢到时间片,运行状态(失去时间片进入就绪状态)->run死亡状态
运行状态中,可能遇到阻塞状态
1.创建状态
new Thread()
2.就绪状态
(1)调用start()
(2)阻塞条件结束
(3)正在运行的线程时间片被其他线程所抢夺
3.运行状态
从就绪状态到运行状态是由操作系统进行,外部无法干预
4.死亡状态
(1)run方法结束
(2)手动让线程暂停 stop(不建议使用,通过其他方式暂停)
5.阻塞状态
阻塞状态分为3种,同步阻塞synchronized,等待阻塞wait,其他阻塞sleep,join
五、如何让一个线程结束
(1)使用stop()方法;(此方法不建议使用)
(2)写一个变量来标识线程结束
class TestThread extends Thread{
boolean stop = true;
@Override
public void run() {
while (stop) {
System.out.println("子线程");
}
}
public void terminated(){
stop = false;
}//写一个终止方法
}
public class MyClass {
public static void main(String[] args){
TestThread t = new TestThread();
t.start();
for(int i = 0;i<20;i++){
if(i==10){
t.terminated();
}
}
}
}
六、线程礼让和线程插队
线程礼让:yield()
线程插队:join()
礼让的线程会直接进入就绪状态,如果这个线程再次获得时间片,它还会执行,所以可能礼让失败
TestRunnable testRunnable = new TestRunnable();
Thread t1 = new Thread(testRunnable,"子线程1");
t1.start();
for(int i = 0;i<100;i++){
System.out.println("主线程");
if(i == 20){
Thread.yield();
}
}
public class MyClass {
public static void main(String[] args){
TestThread t = new TestThread();
t.start();
}
}
插队同理,插队后的线程执行完后再执行原线程
七、多线程的利弊
1.优点
(1)提高执行的效率
(2)不会阻塞主线程
2.缺点
(1)多个线程操作同一个资源时,有可能会出错
class BuyTickets extends Thread{
static int total = 10;
@Override
public void run() {
for(int i = 1;i<11;i++){
if (total == 0) {
stop();
}
}
total--;
System.out.println("第"+(10-total)+"张票购成功");
}
}
}
public class MyClass {
public static void main(String[] args){
BuyTickets Passenger1 = new BuyTickets();
BuyTickets Passenger2 = new BuyTickets();
BuyTickets Passenger3 = new BuyTickets();
Passenger1.start();
Passenger2.start();
Passenger3.start();
}
}
3.克服缺点的办法
(1)Lock锁,代码块必须使用同一把锁
(2)2.synchronized锁
class BuyTickets extends Thread{
Object object = new Object();//创建一个临时对象
static int total = 10;
@Override
public void run() {
for(int i = 1;i<11;i++){
synchronized (object) {
if (total == 0) {
stop();
}
}
total--;
System.out.println("第"+(10-total)+"张票购成功");
}
}
}
public class MyClass {
public static void main(String[] args){
BuyTickets Passenger1 = new BuyTickets();
BuyTickets Passenger2 = new BuyTickets();
BuyTickets Passenger3 = new BuyTickets();
Passenger1.start();
Passenger2.start();
Passenger3.start();
}
}
*注:锁的对象必须相同,每一个对象都维护一把锁
不管是锁代码块还是锁方法,尽量让锁的范围变小
八、线程间的通信
1.实现线程间通信的三个方法
(1)wait()让某个线程等待
(2)notify()唤醒某个线程
(3)notifyAll()唤醒所有线程
*注:这三个方法必须由同步监视器(必须被Lock或synchronized包装的代码块)来调用
public class MyClass {
static intersection section = new intersection();
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
section.printnum();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
section.printlet();
}
}).start();
}
}//匿名对象和匿名内部类快速创建对象和调用方法
class intersection{
int number = 1;
char letter = 'a';
int state = 1;//通过变化state的值来改变线程
public synchronized void printnum(){
while(true){
if(state != 1){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(number);
number++;
if(number == 27){
break;
}
state = 2;
this.notify();
}
}
public synchronized void printlet(){
while(true){
if(state != 2){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(letter);
letter++;
if(letter == ('z'+1)){
break;
}
state = 1;
this.notify();
}
}
}