创建线程的方式
方式一:继承Thread类
1.创建一个类继承Thread类
2.在子类中重写Thread类的run()方法
3.创建子类对象
4.通过子类对象调用start()方法 (start方法的作用:①启动线程②调用当前线程的run方法)
方式二:实现Runnable接口(建议用这种)
1.创建一个类实现Runnable接口
2.实现类中实现接口的抽象方法
3.创建接口的实现类对象
4.将这个对象作为参数传入Thread类的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()方法
方式一和方式二两种方式的比较
联系
Thread类底层也是实现的Runnable接口
都需要重写run方法
方式三:实现Callable接口(jdk1.5后新增)相比方式二更加强大
具体步骤:
1.创建一个类实现Callable接口
2.实现Callable接口中的call()方法
3.编写线程具体操作代码
4.创建Callable接口的实现类对象
5.将Callable接口的实现类对象传入FutureTask类的构造器中,
创建FutureTask类的对象
6.将FutureTask类的对象作为参数传入Thread类的构造器中,
创建Thread类的对象,并调用start()方法
相比实现Runnable接口这种方式,实现Callable的方式有什么强大之处?
1.call()方法中可以拥有返回值
2.该方式中可以处理异常
3.该方式中可以使用泛型
方式四:线程池
步骤
1.创建线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
2.执行线程
// execute 适用于实现Runnable接口
service.execute(new Runnable() {...}
// submit 适用于实现Callable接口
service.submit(new Callable<Object>() {...}
3.关闭线程池
service.shutdown();
线程 生命周期
新建
就绪
运行
阻塞
死亡
线程的同步
线程的安全问题
当一个线程在运行时,遇到了阻塞,这时另一个线程参与了进来,也进行操作,就会造成线程安全问题(如卖票)
如何解决
方式一:同步代码块
synchronize(同步监视器){代码:操作共享数据的代码}
同步监视器(通俗讲就叫锁)
任何一个类的对象,都可以充当锁(要求:多个线程必须共用同一把锁)
也可以使用this代表当前对象,但是只能在创建一个对象的情况下使用(思考)
缺点
操作同步代码块时,只能有一个线程参与,其他线程需要等待,就相当于一个单线程了
方式二:同步方法
在创建方法时使用synchronized
使用实现Runnable接口的方式
只需要创建一个实现类的对象,因此可以直接使用synchronized修饰run()方法
此时,同步监听器为:this
使用继承Thread的方式
因为会创建多个子类的对象,直接使用synchronized修饰run方法不起作用,还需将这个方法使用static关键字修饰
此时,同步监听器就变为:当前类本身
方式三:Lock锁
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源访问的工具。
使用同步锁:创建Lock接口的实现类ReentrantLock的对象
通过对象调用锁定方法lock()/解锁方法unlock()
synchronized与lock锁的异同
相同点
都是解决线程的同步安全问题
区别
synchronized同步方法/代码块的方式,每次执行完代码后自动解锁
而lock锁的方式需要手动锁定、手动解锁
线程的死锁
不同的线程互相占用了对方的资源不放弃,都在等待对方释放,就形成了线程的死锁(如两个人吃饭,只有一双筷子,都在等待对方将另一只筷子给自己)
解决死锁
算法、减少同步资源的定义、避免嵌套同步资源
线程的通信
wait()方法
当前线程进去阻塞状态
notify()方法
唤醒被wait的一个线程(如果有多个线程被wait),唤醒优先级较高的
notifyAll()方法
唤醒所有被wait的线程
这3个方法必须放在synchronized代码块中,这三个方法的调用中必须是同步代码块或同步方法的同步监视器
sleep()和wait()方法的区别
相同点
都可以让当前线程进入阻塞状态
不同点
1.声明的位置:
sleep方法在Thread类中
wait方法在Object类中
2.调用位置
sleep方法可以在任何需要的地方调用
wait方法必须在同步代码块/同步方法中
3.是否释放同步监视器
sleep方法不释放
wait方法释放同步监视器