多线程
线程的定义
进程:当前正在执行的程序,代表一个应用程序在内存中的执行区域。
线程:是进程中的一个执行控制单元,执行路径。
一个进程中如果只有一个执行路径,这个程序称为单线程。
一个进程中有多个执行路径时,这个程序成为多线程。
多线程的好处:它的出现可以同时执行多条路径,让多部分代码同时执行,提高了效率。
线程状态图
开启新线程
1.继承Thread
定义类继承Thread
重写run方法
把新线程要做的事写在run方法中
创建线程对象
开启新线程, 内部会自动执行run方法
2.实现Runnable
定义类实现Runnable接口
实现run方法
把新线程要做的事写在run方法中
创建自定义的Runnable对象
创建Thread对象, 传入Runnable
调用start()开启新线程, 内部会自动调用Runnable的run()方法
两种方式的区别
区别一:
由于子类重写了Thread类的run(), 当调用start()时, 直接找子类的run()方法
构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable是否为空, 不为空则执行Runnable的run()
区别二:
继承Thread只能是单继承,如果自己定义的线程类已经有了父类,就不能再继承了
实现Runnable接口可以多实现,即使自己定义线程类已经有父类可以实现Runnable接口
总结:继承Thread的好处是:可以直接使用Thread类中的方法,代码简单
弊端是:如果已经有了父类,就不能用这种方法
实现Runnable接口的好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂
Thread类常用方法
1.设置名字
通过构造函数可以传入String类型的名字
通过setName(String)方法可以设置线程对象的名字
2.获取名字
通过getName()方法获取线程对象的名字
3.获取当前线程对象
Thread.currentThread(), 主线程也可以获取
4.休眠
Thread.sleep(毫秒,纳秒), 控制当前线程休眠若干毫秒1秒= 1000毫秒 1秒 = 1000 * 1000 * 1000纳秒
5.守护
setDaemon(), 设置一个线程为守护线程, 该线程不会单独执行, 当其他非守护线程都执行结束后, 自动退出
6.加入
join(), 当前线程暂停, 等待指定的线程执行结束后, 当前线程再继续
join(int), 可以等待指定的毫秒之后继续
线程之间的同步
当多线程并发, 有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作. 这时就需要同步.
如果两段代码是同步的, 那么同一时间只能执行一段, 在一段代码没执行结束之前, 不会执行另外一段代码.
同步(synchronized)代码块
格式:
synchronized(对象){//该对象可以是任意对象
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
同步方法
使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的
非静态同步方法默认使用当前对象this作为锁对象
同步的特点:
同步的前提:
同步需要两个或者两个以上的线程。
多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
同步的好处:
同步的出现解决了多线程的安全问题。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步函数和同步代码块区别
1,同步代码块使用的锁可以是任意对象。
2,同步函数使用的锁是固定对象 this
静态同步函数的锁
静态函数使用的锁肯定不是this,因为静态函数中不可以定义this
静态随着类的加载而加载,这时有可能内容还没有该类的对象,但是一个类加载进内存,会先将这个对应的字节码文件封装成对象,该对象的表示方式:类名.class
线程安全问题
多线程并发操作同一数据时, 就有可能出现线程安全问题
使用同步技术可以解决这种问题, 把操作数据的代码进行同步, 不要多个线程一起操作
线程之间的通信
多个线程并发执行时, 在默认情况下CPU是随机切换线程的
如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
怎么通信
如果希望线程等待, 就调用wait()
如果希望唤醒等待的线程, 就调用notify();
这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
多个线程通信的问题
notify()方法是随机唤醒一个线程
notifyAll()方法是唤醒所有线程
JDK5之前无法唤醒指定的一个线程
如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
wait()和sleep()有什么区别?
对时间的指定。
1,sleep方法必须指定时间。
2,wait方法有重载形式,可以指定时间,也可以不指定时间。
对于执行权和锁的操作.
1,sleep():释放执行权,不释放锁,因为肯定能醒,肯定可以恢复到临时阻塞状态。
2,wait():释放执行权,释放锁,因为wait不释放锁,如果没有时间指定,那么其他线程都进行不了同步中,无法将其唤醒。
JDK5之后的线程控制
1.同步
使用ReentrantLock类的lock()和unlock()方法进行同步
2.通信
使用ReentrantLock类的newCondition()方法可以获取Condition对象
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
总结:
在线程中安全的线程代表是同步的线程而不安全的线程代表不同步的线程
而在一般的情况下都只是用一条线程所以不用考虑线程安全的问题
一般定义同步时,建议使用 同步代码块. 如果对象是非静态的可以使用this。那么可以简化同步函数的形式。