创建多线程
第一种方式:
① 继承:继承Thread。
② 重写:重写Thread类的run()方法
③ 创建:创建一个子类的对象
④ 调用:调用线程的start()方法,启动此线程,调用run()方法
1 class Work extends Thread{ //① 继承 2 @Override 3 //② 重写 4 public void run() { 5 for (int i = 1 ;i < 5; i++) { 6 System.out.println(Thread.currentThread().getName()+":"+i); 7 } 8 } 9 } 10 public static void main(String[] args) { 11 //③ 创建 12 Work work = new Work(); 13 //④ 调用 14 work.start(); 15 for (int i = 1 ;i < 5; i++) { 16 System.out.println(Thread.currentThread().getName()+":"+i); 17 } 18 }
输出结果:
main:1 main:2 main:3 main:4 Thread-0:1 Thread-0:2 Thread-0:3 Thread-0:4
思考:把上面的start修改成run,想想会有什么结果呢?
main:1 main:2 main:3 main:4 main:1 main:2 main:3 main:4
走了两遍的main。因为Start是启动线程,run只是正常的调用了一下方法,和多线程没关系。
第二种方法:
① 实现接口:实现Runnable接口的类
② 实现抽象方法:实现接口的run的方法
③ 创建对象:创建一个Runnable接口实现类的对象
④ 放入构造器:将此对象作为形参传递给Thread的构造器,创建Thread对象
⑤ 启动线程:启动这个线程
1 class Work implements Runnable{ //① 实现接口 2 //② 实现抽象方法 3 public void run() { 4 for (int i = 11 ;i < 100; i++) { 5 System.out.println(Thread.currentThread().getName()+":"+i); 6 } 7 } 8 } 9 10 public static void main(String[] args) { 11 //③ 创建对象 12 Work work = new Work(); 13 //④ 放入构造器 14 Thread t1 = new Thread(work); 15 //⑤ 启动线程 16 t1.start(); 17 for (int i = 11 ;i < 100; i++) { 18 System.out.println(Thread.currentThread().getName()+":"+i); 19 } 20 }
Thread(类) VS Runnable(接口)
① Runnable避免了java类的单继承局限性,接口可以多继承。
② 如果多个线程操作同一份资源更适合使用Runnable的方式
线程Thread的常用方法:
① start():启动线程并执行相应的run()方法
② run():将子线程要执行的代码放入run()方法
③ currentThread():静态的,调取当前的线程
√ getName():获取此线程的名字
例如:Thread.currentThread().getName()
√ setName():设置线程的名字
④ yield():强制释放当前cpu执行权,(例如子线程和主线程都循环输出100次的数字,当主线程%10==0的时候,就调用主线程yield方法Thread.currentThread().yield(),强制主线程释放CPU执行权)需要说明的是释放线程的CPU执行权不代表其他线程就一定能抢到CPU的执行权。也可能释放的线程再次抢到资源。
⑤ join():在A线程中调用B线程join(参与进来的意思)方法,表示当执行到此方法,A线程停止执行,B执行完毕后,A再执行。
⑥ sleep():显式的让当前线程睡眠1毫秒
设置线程的优先级:优先级高只能说明抢到的几率高,不代表一定先完成
① getPriority():获取线程的优先级
② setPriority():设置线程的优先级
一共是10个等级.默认是等级5,Thread里的属性就是等级级别
Thread属性:
√ MAX_PRIORITY:最高的线程优先级
√ MIN_PRIORITY:最低的线程优先级
√ NORM_PRIORITY:默认的线程优先级
线程分为两类
①守护线程
用来服务用户的,垃圾回收就是一个典型的守护线程
若JVM都是守护线程,当前JVM将退出
②用户线程
用户自己创建的线程
用户线程-->守护线程:
通过在start()方法前调用thread.setDaemon(True)就可以
线程的生命周期:枚举status代表了状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
线程安全性
原因:
由于一个线程在操作共享数据过程中未执行完的情况下,另外的线程参与进来,
导致共享数据存在安全问题。
共享数据:多个线程共共同操作同一个数据(变量)
解决:
必须让一个线程操作共享数据完毕之后,其他线程才有机会共享数据的操作
java如何解决的呢?
方式一:同步代码块
synchronized(同步监视器){
//需要同步的代码
}
同步块包住谁呢?谁操作共享数据就包谁
注意:在实现的方式中,可以使用this充当锁,但是在继承的方式中,慎用this
方式二:同步方法
同步方法的锁:this
public synchronize void 方法(){ }
单例模式:线程安全
1 class Singleton{ 2 private Singleton(){ 3 4 } 5 private static Singleton instance = null; 6 public static Singleton getInstance(){ 7 if(instance == null){ 8 synchronize(Singleton.calss){ 9 if(instance == null){ 10 instance = new Singleton(); 11 } 12 } 13 } 14 return instance; 15 }
面试题:
银行有一个账号,有两个储户分别向一个账户存入3000元,每次存1000,存3次
,每次存完后打印账户余额
分析:
共享资源是什么?显然是一个账号。
是否需要用多线程?显然是用的,因为有两个储户
1 class Account { 2 double balance = 0; 3 public synchronized void cunqian(double crm){ 4 balance += crm; 5 System.out.println(Thread.currentThread().getName() + ":" + balance); 6 } 7 } 8 class Customer implements Runnable{ 9 Account account; 10 public Customer(Account account) { 11 this.account = account; 12 } 13 @Override 14 public void run() { 15 for (int i = 0; i < 3; i++) { 16 account.cunqian(1000); 17 } 18 } 19 } 20 public static void main(String[] args) throws Exception { 21 Account account = new Account(); 22 Customer customer = new Customer(account); 23 Thread t1 = new Thread(customer); 24 t1.start(); 25 Thread t2 = new Thread(customer); 26 t2.start(); 27 t1.setName("储户1"); 28 t2.setName("储户2"); 29 }
上面代码需要需要的是:由于是两个类,一定要保证共享资源类千万不要被多次实例化
所以一定要让第一个类实例化完成后当成形参出入到第二个中构造(看红色标记部分)
线程通信:
① wait():令当前线程挂起并放弃CPU、同步资源。让别的线程可访问并修改共享资源,而当前前程排队等候再次对资源的访问
② notify():唤醒正在排队等候同步资源的线程中优先级最高的锁
③ notifyAll();唤醒所有正在排队的等待的所有线程结束等待