有关多线程的一些笔记总结
------------恢复内容开始------------
多线程
1、创建线程
1、方式一:继承Thread
步骤1、创建一个继承于Thread类的子类
2、重写Thread类的run方法
3、创建Thread类的子类的对象
4、通过此对象调用start方法
其中,start方法的作用是:1、启动当前线程 2、调用该线程的run方法
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
//2、重写Thread类的run方法
public void run(){}
}
class ThreadTest{
public static void main(String[] args){
Thread t1 = new MyThread();
t1.start();
}
}
注意:
-
不能直接通过run方法启动线程!启动线程的是start方法!
-
不能让已经start()的线程去执行start()去再次启动线程
2、方式二:实现Runnable接口
步骤 1、 创建一个实现Runnable接口的类
2、 实现类去实现Runnable中的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start
class MyThread implements Runnable{
public void run(){}
}
class Test{
public static void main(String[] args){
MyThread thread = new MyThread();
Thread t = new Thread(thread);
t.start();
}
}
//在Thread中的源码其中target 是Runnable类型的变量//执行run方法
说明:t1.start();调用的确实是实现类覆写的run方法
3、比较两种创建方式
开发中:优先选择 实现Runnable接口方式
原因:1、实现的方式没有类的单继承的局限性(假如采用第一章创建方式,则首先要继承Thread类,那这样直接就把继承的唯一机会给用掉了,就不能再去继承了,有很大的局限性)
2、实现的方式更适合处理多个线程有共享数据的情况
当通过继承Thread类的方式创建线程,则当有多线程时,需要创建多个不同Thread子类,若有共享数据则需要讲共享数据修饰成static。
而通过实现Runnable接口来创建线程,当有多线程时,不需要创建多个不同的实现类,就可以使用共享数据。
联系:public class Thread implements Runnable
Thread类也实现了Runnable接口(run方法)
相同点:两种方式都要重写run方法,将线程要执行的逻辑声明在run方法
4、方式三:实现Callable接口 (jdk5后新增)
//1、创建一个Callable接口的实现类
class NumThread implements Callable{
//2、重写call方法
public Object call() throws Exception{
return null;
}
}
public class ThreadTest{
public static void main(String[] args){
//3、实现类进行实例化对象
NumThread n1 = new NumThread();
//4、引用FutureTask类也是Runnable的实现类
FutureTask f1 = new FutureTask(n1);
//5、将FutureTask的对象作为参数传入Thread构造器
Thread t1 = new Thread(f1);
t1.start();
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口
-
call方法可以有返回值
-
call方法可以跑出异常
-
Callable支持泛型
5、方式四:使用线程池 (jdk5后新增)
class NumTest implements Runnable{
public void run(){
}
}
public class ThreadTest{
public static void main(String[] args){
NumTest n1 = new Numtest();
//1、提供指定线程数量的线程池
ExecutorService e = Executos.newFixedThreadPool(10);
//2、执行指定的线程的操作,需要提供实现Runnable接口或Callable
e.execute(n1);//使用于Runnable
e.shutdown();
}
}
通过线程池创建线程好处:
-
提高响应速度
-
降低资源消耗
-
便于线程管理
2、Thread中常用方法
-
start():启动当前线程并调用当前线程的run方法
-
run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
-
currentThread():是静态方法,返回当前代码执行的线程
-
getName():获取当前线程的名字
-
setName():设置当前线程的名字
-
yield():释放当前cpu的执行权
-
join():在线程A中调用线程B的join方法,此时线程A进入阻塞状态,知道线程B完全执行完成以后,线程A才结束阻塞状态
-
stop():已过时,当执行此方法时,强制结束当前线程
-
sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒,在指定的millitime时间内,当前线程为阻塞状态
-
isAlive():判断当前线程是否存活
注意 :
wait()、notify()、notifyAll()是在Object类中声明
3、线程的优先级
1、MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 ----> 默认
2、如何获取和设置当前线程的优先级
getPriority():获取线程的优先级
setPriority():设置线程的优先级
说明:高优先级的线程会抢占低优先级线程的CPU执行权。但只是从概率上讲高优先级的线程优先被执行,并不意味着只有当高优先级的线程执行完后,低优先级的线程才执行。
4、解决线程的安全问题
1、方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明: 操作共享数据的代码,即为需要被同步的代码(不能包含代码多了,也不能包含少了)
共享数据:多个线程共同操作的变量
同步监视器: 俗称锁 ,任何一个类的对象都可以充当锁
同步机制的要求:多个线程必须公用同一把锁
2、方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步
public synchronized void test(){
}
3、方式三:Lock锁 ----- jdk5.0
class Num implements Runnable{
private ReentrantLock lock = new ReentrantLock();
public void run(){
try{
lock.lock();
//同步代码
}finally{
lock.unlock();
}
}
}
4、比较synchronized与lock异同
-
相同点:二者都可以解决线程安全问题
-
不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,而lock需要手动的启动同步(Lock()),同步结束时也需要手动的实现(unlock())
-
优先使用:Lock -> 同步代码块 ->同步方法
5、同步方法的总结
1、同步的方式解决了线程的安全问题 -------好处
2、操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低----坏处
总结:
1、同步方法仍然涉及到同步监视器,只是不需要我们显示声明
2、非静态的同步方法的同步监视器是 this
静态同步方法的同步监视器是当前类本身
5、线程通信
设计到的三个方法
1、wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器 ----> 只出现在同步代码块和同步方法中,不能用在lock
2、notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,则唤醒优先级高的那个线程
3、notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
说明:
-
wait()、notify()、notifyAll()都必须在同步方法和同步代码块中调用
-
wait()、notify()、notifyAll()调用者必须是同步方法和同步代码块中的同步监视器,否则会出现异常
-
wait()、notify()、notifyAll()定义java.lang.Object类中
sleep和wait方法有什么异同
1.相同点:一旦执行方法,都可以使得当前的线程进行入阻塞状态
2、不同点:
1)两个方法声明的位置不同:Thread类中声明sleep
Object类中声明wait
2)调用的要求不同:sleep可以在任何需要的场景下调用,wait必须使用在同步代码块中和同步方法中
------------恢复内容结束------------

浙公网安备 33010602011771号