重点
Lock 和 synchronized :在同一个jvm虚拟机
目前创建多线程的方式: (1)继承Thread类 (2) 实现Runnable接口 (3) 线程池 (4) Callable接口 (获取返回值Future)
Callable和Runnable主要区别:Callable里的call是有返回值,可以利用Future接口中get可以获取返回
Runnable 里的run方法是没有返回值
直接调用run()方法和调用start()方法有啥区别?
调用start()方法是在多线程中执行run()方法,直接调用run()方法是在当前线程中执行run()方法
基本状态: 初始状态 ----->就绪状态<--->运行状态-------阻塞、等待状态--->终止状态
join方法():线程加入,让加入的线程执行完毕后,当前线程才会执行
Thread.sleep():线程睡眠,会让出cpu资源
Thread.yield():线程礼让,会再次抢占cpu
线程同步
synchronized:锁的是成员方法,互斥锁对象是this
锁的是静态方法,互斥锁对象是Class对象
场景:
1:如何让某个线程先不执行,等待另外某个线程执行完后再执行?
join()
2:如何让两个线程交替执行?
wait() notify() notifyAll() (线程通信)
为什么要用线程池?
当需要用多线程执行多任务时,频繁创建和销毁线程会损耗性能,用线程池可以先开启多个线程,线程可以重用,不用每次都创建新的线程。
实现线程同步的方式: synchronized : 关键字
隐式获取锁,隐式释放锁
支持重入锁
只支持不公平锁,不支持公平锁
synchronized出现异常会自动释放锁
synchronized在1.6后支持锁升级
lock 锁 :类
显式获取锁,显式释放锁
支持重入锁
支持不公平锁和公平锁,默认不公平锁
支持tryLock 非阻塞式获取锁
可以实现读写锁,读写分离 ,提高效率
synchronized锁升级的四种状态:无锁 偏向锁 轻量级锁(CAS自选锁) 重量级锁
ConcurrentHashMap :线程安全,效率要比Hashtable高
锁一部分代码,锁的是数组一部分不是锁整个数组
Hashtable锁整个方法
进程和线程
进程:运行中的程序称之为进程
线程:
- 进程组成部分,线程用进程中,单一的执行的顺序控制流程。同时也是CPU的调度单位
- 进程是可以有多个线程的,他们之间独立运行。交替执行。称之为多线程
区别 - 进程是系统分配资源的单位,线程是CPU的调度单位
- 一个程序,至少包含一个进程
- 一个进程至少包含一个线程。线程是不能独立运行的,必须依附在进程中
- 进程之间是不能共享数据段地址,但是同一个进程下的线程是可以共享的
线程的组成部分 - CPU时间片
+ 操作系统会为每个线程分配时间 - 运行数据
+ 堆空间:存储线程需使用的对象。多个线程可以共享堆中的对象
+ 栈空间:存储线程需使用的局部变量。每个线程都拥有自己的栈空间 - 线程的逻辑代码
创建线程
创建线程的方式:1、继承Thread类 2、实现Runnable接口 3、线程池 4、实现callable接口
public class Demo1 {
public static void main(String[] args) {
//方式1 继承Thread类
A a = new A();
a.start();
//方式2 实现Runnable接口
B tast = new B();
Thread thread = new Thread(tast);
thread.start();
}
}
//方式1 继承Thread类
class A extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
//方式2 实现Runnable接口
class B implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
两种方式的区别
1、java只支持单继承,所以继承Thread类就没办法继承其他的类,所以使用Runnable接口更灵活
2、继承自Thread类,表示这个类就是一个线程类,可以直接启动线程。实现Runnable接口,表示这个类是一个线程任务,需要创建线程对象从而执行这个线程任务
启动线程需要注意的问题
1.不要调用run方法
2.一个线程只能调用一次start方法
线程常见的方法
1、设置线程名称 (setName、getName、Thread.currentThread获取当前线程对象)
如果没有设置线程名称,那么默认的名称为Thread-0 Thread-N
2、设置线程的优先级 (setPriority、getPriority)
如果没有设置线程的优先级,那么默认的优先级为5
线程有的优先级为1~10之间,设置优先级只是提高了抢占CPU的概率
3、线程休眠 (Thread.sleep(毫秒数)) //让出cpu使用权
让当前线程进入到休眠状态,并让出CPU使用权,直到休眠结束,才会继续抢占CPU
4、线程礼让(Thread.yeild())
让出CPU使用权,但是立马又会去重新抢占CPU
5、线程加入(join()) 控制线程执行顺序
在当前线程中加入另一线程,必须要将另一个线程执行完之后才会继续执行当前线程
线程安全
方式一:同步代码块
synchronized(互斥锁对象){ //互斥锁对象 (互斥锁是唯一的java对象就可以)
//原子代码
}
方式二:同步方法 锁的是成员方法,互斥锁对象是this ,锁的是静态方法,互斥锁对象是Class对象
public synchronized void sale(){ //互斥锁标记是this对象
//原子代码
}
互斥锁对象要求对象是唯一的,如果是多个对象,可以利用唯一的Class 对象
线程死锁
当第一个线程拥有A对象的锁标记,并等待B对象的所标记。同时第二个线程拥有B对象锁标记,同时等待A对象的锁标记时,产生死锁
public class Demo8 {
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
}
class AA{
static AA aa1=new AA();
static AA aa2=new AA();
}
class Thread1 extends Thread{
@Override
public void run() {
synchronized (AA.aa1){
System.out.println("获取aa1锁");
synchronized(AA.aa2){
System.out.println("尝试获取aa2锁");
}
}
}
}
class Thread2 extends Thread{
@Override
public void run() {
synchronized (AA.aa2){
System.out.println("获取aa2锁");
synchronized(AA.aa1){
System.out.println("尝试获取aa1锁");
}
}
}
}
线程通信
wait()
- 当前线程释放锁对象, 并处于阻塞状态,进入等待队列,直到有别人唤醒
notify()、notifyAll() - 随机唤醒一个正在等待的线程
- 唤醒所有等待队列中的线程
[注意:所有的等待、通知方法必须在对加锁的同步代码块中。]
线程池
- 如果有非常的多的任务需要多线程来完成,且每个线程执行时间不会太长,这样频繁的创建和销毁线程。
- 频繁创建和销毁线程会比较耗性能。有了线程池就不要创建更多的线程来完成任务,因为线程可以重用
- 线程池用维护者一个队列,队列中保存着处于等待(空闲)状态的线程。不用每次都创建新的线程。
线程池中常见的类
常用的线程池接口和类(所在包java.util.concurrent)。
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
Executors工厂类:通过此类可以获得一个线程池。
newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,无上限。
public class TestThreadPool1 {
public static void main(String[] args) {
//1、获取线程池对象 线程池数量为3
ExecutorService es = Executors.newFixedThreadPool(3);
//2、通过线程池提交并执行线程任务 (线程会自动启动线程并执行线程任务(执行run方法))
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
es.submit(new MyTask());
//3、关闭线程池 (当所有的线程任务都执行完成之后关闭)
es.shutdown();
}
}
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
public class TestThreadPool2 {
public static void main(String[] args) {
//1、创建线程池对象 动态个数的线程池 (如果线程任务执行完,会执行下一个线程任务)
ExecutorService es = Executors.newCachedThreadPool();
//2、通过线程池对象启动并执行线程任务
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
});
//3、关闭线程池
es.shutdown();
}
}
Callable接口
JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
Callable具有泛型返回值、可以声明异常。
public class TestCallable1 {
public static void main(String[] args) {
//1、创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(2);
//2、通过线程池提交线程并执行任务
es.submit(new MyCallable());
//3、关闭线程池
es.shutdown();
}
}
class MyCallable implements Callable{//此泛型规定了方法的返回值
@Override
public Object call() throws Exception {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
return null;
}
}
Future接口
Future接口表示将要执行完任务的结果。
get()以阻塞形式等待Future中的异步处理结果(call()的返回值)。
Lock锁
JDK5加入,与synchronized比较,显示定义,结构更灵活。
提供更多实用性方法,功能更强大、性能更优越。
public class TestLock {
public static void main(String[] args) {
//1、创建线程池对象
ExecutorService es = Executors.newFixedThreadPool(4);
//2、通过线程池提交线程任务
TicketTask task = new TicketTask();
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
//3、关闭线程池
es.shutdown();
}
}
class TicketTask implements Runnable{
static int ticket = 30;
//jdk1.5之后加入
Lock lock = new ReentrantLock();//重入锁
Object obj = new Object();
@Override
public void run() {
while(true) {
try {
lock.lock(); //上锁
if(ticket < 0) {
break;
}
//System.out.println(10/0);
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->卖出了"+ticket--+"号票");
}finally {
lock.unlock(); //释放锁
}
}
}
}
读写锁
ReentrantReadWriteLock: (读写锁)
一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
写-写:互斥,阻塞。
读-写:互斥,读阻塞写、写阻塞读。
读-读:不互斥、不阻塞。
在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
public class Demo4 {
public static void main(String[] args) {
long l = System.currentTimeMillis();
User1 u = new User1();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 2; i++) {
WritTask1 writTask = new WritTask1();
writTask.setUser(u);
executorService.submit(writTask);
}
for (int i = 0; i < 8; i++) {
ReadTask1 readTask = new ReadTask1();
readTask.setUser(u);
executorService.submit(readTask);
}
executorService.shutdown();
//判断线程池中的任务是否执行结束,如果结束了返回true
while (!executorService.isTerminated()){
}
System.out.println(System.currentTimeMillis()-l);
}
}
class User1{
String name;
ReadWriteLock rrw=new ReentrantReadWriteLock();
public String getName() {
Lock readLock=rrw.readLock();
readLock.lock();
try {
Thread.sleep(1000);
return name;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
}
return null;
}
public void setName(String name) {
Lock lock = rrw.writeLock();
try {
lock.lock();
Thread.sleep(500);
this.name = name;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class ReadTask1 implements Runnable{
User1 user;
public void setUser(User1 user){
this.user=user;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+user.getName());
}
}
class WritTask1 implements Runnable{
User1 user;
public void setUser(User1 user){
this.user=user;
}
@Override
public void run() {
user.setName("zhangsan");
System.out.println(Thread.currentThread().getName());
}
}
重入锁
重入锁也叫作递归锁,指的是同一个线程外层函数获取到一把锁后,内层函数同样具有这把锁的控制权限
synchronized和Lock锁都可以实现锁的重入
public class Demo5 {
public static void main(String[] args) {
new Thread(new Task2()).start();
}
}
class Task2 implements Runnable{
@Override
public void run() {
a();
}
public synchronized void a(){
System.out.println("aa");
b();
}
public synchronized void b(){
System.out.println("bb");
}
}
公平锁
非公平锁:优先使用上一个线程接着执行下面的线程任务
- synchronized是非公平锁的实现,无法实现公平锁
- lock锁默认是非公平锁,如果想要实现公平锁,那么需要在构造方法设置为true
公平锁:让每个线程都公平去执行线程任务 - lock锁可以实现公平锁
- synchronized无法实现公平锁
//Lock锁实现公平锁 参数为true表示是公平锁,默认是false表示非公平锁
Lock lock = new ReentrantLock(true);