多线程(基础篇)–Part1
本章内容(重要程度)
- 并行和并发 ★★★
- 线程基础概念 ★★★★★
- 实现多线程的两种方式★★★★★
- 锁的概念和使用 ★★★★★
- 生产消费者模型 ★★★★★
- volatile变量 ★★★
- 线程池的概念和使用 ★★★
并行和并发
并行:多个cpu实例或者是多台机器同时处理一段逻辑
并发:一个CPU或一台机器,通过CPU调度算法,让用户看上去同时去执行,实际上从CPU操作层面并不是真正的同时。
进程和线程
进程:可以看作是程序,有独立的运行内存空间
线程:程序中运行的任务,线程是共享内存的
QQ和钉钉就是两个进程,但是二者内部的对话框都是线程
Java实现多线程的方式
方式一:继承于Thread类
创建一个继承于Thread类的子类
重写Thread类的run( )方法
创建子类的对象
通过子类对象调用父类的 start( )方法
作用:
- 启动当前线程
- 调用当前线程的run方法
为什么不直接执行run方法?
如果直接利用子类对象使用run方法
仍然能够执行重写过后的run方法
但是此时和主函数处在同一个线程
不会新开一个新的线程去执行
ps:如果想让该线程多次执行,不能重复用同一个子类对象调用start方法,应该创建不同的子类对象,分别调用start方法
//Code1
public class ThreadDemo1 extends Thread {
//1 继承了Thread类
//遍历10以内所有的偶数
@Override
public void run() {
//2 重写run方法====将此线程要执行的操作写入run方法
for (int a = 2;a<=10;a++){
if (a%2==0){
System.out.println(a);
System.out.println(Thread.currentThread().getName());
//查看该线程的名字
}
}
}
public static void main(String[] args) {
ThreadDemo1 thread1 = new ThreadDemo1();
//3 创建子类对象
thread1.start();
//4 调用start方法
//thread1.run(); 直接调用run方法则会显示该方法仍在main线程下执行
System.out.println("hello wordl");
//与上一行不在同一线程
}
}
利用匿名子类简便写法
public class ThreadDemo1Add {
//创建Thread匿名子类的方式快速开辟新线程
public static void main(String[] args) {
//如果这么调用start方法,执行的是父类的方法
//new Thread().start();
new Thread(){
//放入要开辟的新线程执行的方法体
@Override
public void run() {
for (int a=0;a<=10;a++){
if (a%2==0){
System.out.println(a);
System.out.println(Thread.currentThread().getName());
}
}
}
}.start();
//线程2
new Thread(){
@Override
public void run() {
System.out.println("hello world");
}
}.start();
}
}
Thread类的常用方法
-
start( )
-
run( )
-
currentThread( )
-
getName( )
获取主函数:可以在main中书写
Thread.currentThread( ).getName( );
-
setName( )
- 两种方式给线程改名
-
yield( )//释放执行权
-
join( )//在A线程中写入线程B子类.join( )此时执行B线程直到结束才执行A
-
sleep( long millionTime) 多少毫秒之后结束睡眠开始继续执行
-
isAlive( )判断该方法是否还存活
线程优先级(1-10)
- getProiority( )
- setProiority(int i )
方式二: 实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现抽象方法run
- 创建实现类的实例对象
- 将此对象当作参数传递掉Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start( )
class Windows implements Runnable{
private int ticnum = 100;
@Override
//2. 重写run方法
public void run() {
while (true){
if (ticnum>0){
System.out.println(Thread.currentThread().getName()+"==Sold+Code=="+ticnum);
ticnum--;
}else {
System.out.println("ALL SOLD OUT");
System.exit(0);
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
//3. 创建实现类的实例
Windows w1 = new Windows();
//4. 将实例当作参数传递到Thread类的构造器中 创建Thread类的对象
Thread t1 = new Thread(w1);
Thread t2 = new Thread(w1);
Thread t3 = new Thread(w1);
t1.setName("线程==1==");
t2.setName("线程==2==");
t3.setName("线程==3==");
//5. 通过Thread类对象调用start方法
t1.start();
t2.start();
t3.start();
/*
* 此时调用start方法执行的是Thread类中的start的run方法
* 但是Thread方法中的run方法为
* @Override
public void run() {
if (target != null) {
target.run();
}
}
target的声明为: private Runnable target;
* 而Thread构造器的有参构造恰好传入target所以最后调用的仍然是子类重写后的run方法
* */
}
}
//此时三个窗口共享一个Windows对象实例,所以ticnum也共用,进而实现数据共享
比较创建线程的两种方式
- 开发中优先选择Runnable接口实现的方式
- 实现的方式没有 Java类单继承的局限性
- 实现的方式更有利处理多个线程处理数据的情况
- 相同点:
- Thread类本身也实现了Runnable接口
- 二者都需要重写run 方法