多线程基础
注:File file = new File("a.txt");不是指这个文件或者文件夹,而是引用。
一 : 进程和线程
1.1 :进程
指的是一个动态过程,就是代码从加载到执行完毕的过程
特点:
1)独立性:每个进程是相互独立的,互不干涉
2)动态性:每个进程是一直活动的
3)并发性:多个进程在单个CPU上是并发进行的
1.2:线程
线程是进程的一部分,一个进程可以有多个线程,每个线程取执行特定的任务
1)线程是抢占式的,多个线程共用一个CPU,其实就是 CPU快速切换
1.3 :两者关系
1)线程是进程的一部分;
2)进程是不资源共享的,线程是资源共享
3)一个进程至少有一个线程
二:实现多进程的三种方法
2.1)继承Thread 实现Runnable 继承Callable
1.继承Thread:继承自Thread类,Thread类是所有线程类的父类,实现了对线程的抽取和封装继承Thread类创建并启动多线程的步骤:
a.定义一个类,继承自Thread类,并重写该类的run方法,该run方法的方法体就代表了线程需要完成的任务,因此,run方法的方法体被称为线程执行体
b.创建Thread子类的对象,即创建了子线程
c.用线程对象的start方法来启动该线程
注意:1 程序运行时会自动创建一个线程 ,这个线程叫主线程;可以通过主线程创建子线程。
2 启动线程使用start()方法,不要直接调用run()方法。
3主线程是JVM自动创建的
代码示例一
1 public class Demo1 { 2 public static void main(String[] args) { 3 /**因为Mythread是继承Thread,当然肯定继承了Thread的属性name, 4 * 但是没有继承他的构造方法,所以不能用构造方法是指线程名字 5 * thread.setName("11"); 6 * 设置优先级 7 * thread.setPriority(5); 8 * 设置是否后台运行 9 * thread.setDaemon(true); 10 */ 11 MyThread thread = new MyThread(); 12 thread.start(); 13 for (int i = 0; i < 10; i++) { 14 System.out.println("正在执行主线程"+i); 15 } 16 } 17 } 18 public class MyThread extends Thread{ 19 20 @Override 21 public void run() { 22 for (int i = 0; i < 10; i++) { 23 System.out.println("正在执行子线程"+i); 24 } 25 } 26 }
注:获取线程名字两个方法:1:Thread.currentThread.getName();这个在哪都可以准确定位目前线程名字 2:thread.getName();
代码示例二(经典卖票)
public class Demo2 { public static void main(String[] args) { /** * 有构造方法 * public Ticket(String name) { * super(name);} * 所以可以用构造方法定义线程名 */ Ticket ticket1 = new Ticket("窗口一"); Ticket ticket2 = new Ticket("窗口一"); Ticket ticket3 = new Ticket("窗口一"); Ticket ticket4 = new Ticket("窗口一"); ticket1.start(); ticket2.start(); ticket3.start(); ticket4.start(); } } public class Ticket extends Thread { int ticket = 100; public Ticket(String name) { super(name); } @Override public void run() { while (true){ if (ticket < 1)break; System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticket+"张票"); ticket--; } } }

内存图解析:因为Ticket类是创建出来的,每次创建一个对象w1,w2就会进入栈,相应的堆内存也会分配空间,有各自属性int Ticket = 100,但是w1 优势各自独立的线程,所以它们又会自己创建一个栈,都有各自的栈内存
所以出现总共有400张票,每个线程栈空间是独立的,堆空间是共享的。
2 实现Runnable接口:
步骤 : a.定义一个Runnable接口的实现类,并重写该接口中的run方法,该run方法的方法体同样是该线程的线程执行体
b.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
c.调用线程对象的start方法来启动该线程
代码案例(案例:模拟售票员售票:四个窗口共卖100张票)
public class Demo3 { public static void main(String[] args) { Ticket ticket = new Ticket(); Thread w1 = new Thread(ticket,"w1"); Thread w2 = new Thread(ticket,"w2"); Thread w3 = new Thread(ticket,"w3"); Thread w4 = new Thread(ticket,"w4"); w1.start(); w2.start(); w3.start(); w4.start(); } } public class Ticket implements Runnable{ int ticket = 100; @Override public void run() { while (true){ if (ticket < 1)break; System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticket+"张票"); ticket--; } } }
代码&源码分析
/** * 我们先看源码Run()方法 * @Override * public void run() { * if (target != null) { * target.run(); * } * } * run方法中有一个属性是 "target" 尤其重要的属性,它是为何四个线程公用100张票的原因 * 在看源码: * private Runnable target; * target 是Runnale类型的 * 源码416行 * this.target = target; * 这个target就是传过来的目标类,也就是票类 * 再看源码 Thread构造函数 * public Thread(Runnable target, String name) { * init(null, target, name, 0); * } * Thread构造函数调用的方法init() * private void init(ThreadGroup g, Runnable target, String name, * long stackSize, AccessControlContext acc, * boolean inheritThreadLocals) { * String name就是线程名字,target就是Ticket类,四个线程共用一个target对,即共同消费属性 int ticket=100; * */

分析内存图:main方法的Ticket先加载,在栈中创建Ticket引用“ticket”,在堆开辟内存空间,有属性 int ticket = 100;Thread在栈中压入引用,堆中分配内存空间属性target,四个引用都有相应的堆空间,target放入的就是目标类对象
target的地址,即都指向堆空间的target实行int target= 100;四个线程消费也都是消费同一个target
案例二(你和你女朋友公用一张银行卡,你向银行卡中存钱、你女朋友取钱!)
public class Demo1 { public static void main(String[] args) { BankCard card = new BankCard(); AddMoney addMoney = new AddMoney(card); SubMoney subMoney = new SubMoney(card); Thread w1 = new Thread(addMoney); Thread w2 = new Thread(subMoney); w1.start(); w2.start(); } } public class BankCard { //银行卡里有钱 private int money; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } } public class AddMoney implements Runnable { //因为需要car所以私有化bankcard private BankCard card; public AddMoney(BankCard card) { this.card = card; } @Override public void run() { for (int i = 0; i < 10; i++) { card.setMoney(card.getMoney()+1000); System.out.println("增加1000,余额"+card.getMoney()); } } } public class SubMoney implements Runnable { //因为需要car所以私有化bankcard private BankCard card; public SubMoney(BankCard card) { this.card = card; } @Override public void run() { for (int i = 0; i < 10; i++) { card.setMoney(card.getMoney()-1000); System.out.println("钱少了,余额"+card.getMoney()); } } }
三 : 两种方式的比较
1 继承Thread类的方式
a.没有资源共享,编写简单如果要访问当前线程,除了可以通过Thread.currentThread()方式之外,还可以使用getName()获取线程名字。
b.弊端:因为线程类已经继承了Thread类,则不能再继承其他类【单继承】
2 实现Runnable接口的方式
a.可以多个线程共享同一个资源,所以非常适合多个线程来处理同一份资源的情况
b.资源类实现了Runnable接口。如果资源类有多个操作,需要把功能提出来,单独实现Runnable接口。
c.弊端:编程稍微复杂,不直观,如果要访问当前线程,必须使用Thread.currentThread()
总结:实际上大多数的多线程应用都可以采用实现Runnable接口的方式来实现【推荐使用匿名内部类】
【问题】调用start() 和 run()区别?
当调用start()方法时将创建新的线程,并且执行run()方法里的代码,但是如果直接调用run()方法,不会创建新的线程。
3)第三种 实现 Callable接口
import java.util.concurrent.FutureTask; /** * 1.有返回值 * 2.需要借助FutureTask */ public class Demo1 { public static void main(String[] args)throws Exception { myCallable callable = new myCallable(); FutureTask<Integer> task = new FutureTask<Integer>(callable); Thread thread = new Thread(task); thread.start(); Integer integer = task.get(); System.out.println(integer); } } import java.util.concurrent.Callable; public class myCallable implements Callable<Integer> { int sum = 0; @Override public Integer call() throws Exception { for (int i = 1; i <=100 ; i++) { sum +=i; } return sum; } }
3.1)获取设置线程名字
设置: 1.使用构造方法
2.set.name("");
获取:1.thread.getName();
2.Thread.currentThread.getName();
使得当前正在执行的线程休眠一段时间,释放时间片,导致线程进入阻塞状态
Thread.sleep(5000),5000的单位是毫秒,设置了sleep就相当于将当前线程挂起5s,这个操作跟线程的优先级无关,当对应的时间到了之后,还会再继续执行
3.3)设置线程优先级
可以通过设置优先级来改变线程抢到时间片的概率,优先级高的线程获得较多的执行机会。
默认情况下,每个线程的优先级都与创建它的父线程具有相同的优先级,例如:main线程具有普通优先级,则由main线程创建的子线程也有相同的普通优先级
注意:
优先级范围1~10,默认为5,对应的数值越大,说明优先级越高,这个方法的设置一定要在start之前
线程的优先级低并不意味着争抢不到时间片,只是抢到时间片的概率比较低而已
public class JoinThread extends Thread{ @Override public void run() { for(int i=0;i<50;i++) { System.out.println(Thread.currentThread().getName()+"....."+i); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) throws Exception { //1创建线程对象 JoinThread joinThread=new JoinThread(); //2启动 joinThread.start(); //加入线程(阻塞了主线程,等join执行完完毕才继续执行) joinThread.join(); //3for for(int i=0;i<30;i++) { System.out.println("主线程--------------"+i); Thread.sleep(20); } }
public class DeamonThread extends Thread { @Override public void run() { for(int i=0;i<100;i++) { System.out.println(Thread.currentThread().getName()+"...."+i); try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) throws Exception{ //1创建线程对象 DeamonThread deamonThread=new DeamonThread(); //设置线程为后台线程 deamonThread.setDaemon(true); //2启动线程 deamonThread.start(); for(int i=0;i<20;i++) { System.out.println("主线程:"+i); Thread.sleep(20); } }
3.6)线程让步(虚伪的让步)
可以让当前正在执行的线程暂停,但它不会阻塞该线程,他只是将该线程转入就绪状态,完全可能出现的情况是:当某个线程调用了yield方法暂停之后,线程调度器又将其调度出来重新执行。
注意:
实际上,当某个线程调用了yield方法暂停之后,只有优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程才会获得执行的机会。
public class YieldFunctionDemo01 { public static void main(String[] args) { YieldThread t0 = new YieldThread("线程000"); //t0.setPriority(8); t0.start(); YieldThread t1 = new YieldThread("线程111"); t1.start(); } } class YieldThread extends Thread { public YieldThread(){} public YieldThread(String name) { super(name); } @Override public void run() { for(int i = 0;i < 50;i++) { System.out.println(Thread.currentThread().getName() + " " + i); if(i==20) { //线程让步,不会让线程进入阻塞状态 Thread.yield(); } } } }
6)声明周期
五个状态
新生--->就绪---->运行---->阻塞----->死亡

浙公网安备 33010602011771号