Java基础——多线程
1.线程的创建:
方式一:继承java.lang.Thread类
方式二:通过实现的方式
继承的方式 VS 实现的方式:
1.联系:public class Thread implements Runnable
2.实现的方式优于继承的方式
1.避免了Java单继承的局限性
2.如果多个线程要操作同一份资源(或数据),更适合使用实现的方式
package test; //创建线程的第一种方式:继承java.lang.Thread类 //1.创建一个继承Thread的子类 class SubThread extends Thread { //2.重写run()方法,方法内实现此子线程要完成的任务 public void run() { for(int i = 0; i < 100; ++i) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } public class TestThread { public static void main(String[] args) { //3.创建一个子类的对象 SubThread st = new SubThread(); //4.调用线程的start()方法:启动此线程;并且调用相应的run()方法 //一个线程只能执行一次start(); //不能通过直接调用Thread实现类对象的run()方法去启动一个线程 st.run();//错误的,相当于主线程内启动的 st.start(); for(int i = 0; i < 100; ++i) { System.out.println(Thread.currentThread().getName() + ": " + i); } } }
package test;
//创建线程的第二种方式:通过实现的方式 //1.创建一个实现了Runnable接口的类 class SubThread1 implements Runnable{ //2.实现接口的抽象方法 public void run() { //子线程执行的代码 for(int i = 0; i < 50; ++i) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } public class TestThread1 { public static void main(String[] args) { //3.创建一个Runnable接口实现类的对象 SubThread1 p = new SubThread1(); //要想启动多线程,必须调用start() //4.将此方法作为形参传递给Thread类的构造器中,创建Thread类的对象,此对象即为一个线程 Thread t1 = new Thread(p); //5.调用start()方法,启动线程并执行run() t1.start(); //再创建一个线程 Thread t2 = new Thread(p); //不用再重新创建p,直接传入Thread即可 t2.start(); } }
2.Thread的常用方法:
1.void start(): 启动线程,并执行对象的run()方法
2.run(): 线程在被调度时执行的操作
3.String getName(): 返回线程的名称
4.void setName(String name):设置该线程名称
5.static currentThread(): 返回当前线程
6.static void yield():线程让步 调用此方法的线程释放当前CPU的执行权
7.join() :A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,知道B线程执行完毕,A线程再接着join()之后的代码执行
8.static void sleep(long millis):(指定时间:毫秒) 不会释放锁
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
抛出InterruptedException异常
9.stop(): 强制线程生命期结束
10.boolean isAlive():返回boolean,判断线程是否还活着
11.线程通信:wait()(会释放锁) notify() notifyAll()
3.设置线程的优先级:(1 - 10)
MAX_PRIORITY(10);
MIN _PRIORITY (1);
NORM_PRIORITY (5);
涉及的方法:
getPriority() :返回线程优先值
setPriority(int newPriority) :改变线程的优先级
注:线程创建时继承父线程的优先级
多线程程序的优点:
提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
提高计算机系统CPU的利用率
改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
线程安全:多线程中,存在共享数据
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了安全问题
解决:必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作
4.线程的同步机制:解决线程安全问题
方式一:同步代码块 保证所有线程共用同一把锁
synchronized(同步监视器:即锁){
//需要被同步的代码块(即为操作共享数据的代码)
}
1.共享数据:多个线程共同操作的同一个数据(变量)
2.同步监视器(锁):可以由任何一个类的对象来充当。哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁
可以这样 Object obj = new Object; (把obj放进去都可以),但是不能是局部变量(不能放在run方法内)
注:所有线程共用同一个锁,在实现的方式中,考虑同步的话,可以使用this来充当锁。
但是在继承方式中,慎用this(因为会创建多个对象,this每次指代的不是同一个对象)static Object obj = new Object;
对于静态方法而言,使用当前类本身充当锁(Singleton.class)
方式二:同步方法 public synchronized void show(){//操作共享数据的代码}
将操作共享数据的部分封装为一个方法,且声明为synchronized,即此方法为同步方法,能够保证当其中一个线程执行此方法时,其他线程在外等待,直至此线程执行完此方法
1.对于非静态方法,同步方法的锁:默认为this。如果使用继承的方式实现多线程,慎用
2.对于静态方法,如果使用同步,默认锁为:当前类本身 以单例的懒汉式为例,Class clazz = Singleton.class
线程同步的弊端:由于同一时间只能有一个线程访问数据,效率变低了。
释放锁的操作:wait()
当前线程的同步方法、同步代码块执行结束
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的操作:sleep()、yield()、suspend()
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
死锁:
线程通信:wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll():唤醒正在排队等待资源的所有线程结束等待.
注:Java.lang.Object提供的这三个方法
只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常