多线程(Thread)
- 线程简介
- 线程实现
- 线程状态
- 线程同步
- 线程通信问题
- 高级主题
核心概念
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。
线程的创建
Thread 、Runnable、Callable
Thread
- 自定义线程类继承Thread
- 重写run()方法,编写线程执行体
- 创建新线程对象,调用start()方法启动线程
**总结:**线程开启不一定立即执行,由CPU调度执行
Runnable
- 定义MyRunnable类实现Runnable接口
- 实现run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
Comparing Thread And Runnable
总结:
继承 Thread 类:
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用,避免OOP单继承局限性
实现Runnable接口:
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
并发
Callable
实现callable接口:
- 实现callable接口,需要返回值类型
- 实现call方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行: Future result1 = ser.submit(t1);
- 获取结果: boolean r1 = result1.get()
- 关闭服务: ser.shutdownNow();
静态代理
- 真实对象好代理对象都要实现同一个接口
- 代理对象要实现真实的角色
- 代理对象可以做很多真实的对象做不了的事情
- 真实对象专注于做自己的事情
线程状态
5大状态
主要方法
Eg 1:倒计时
public class test2 {
public static void main(String[] args) throws InterruptedException {
tenDow();
}
public static void tenDow() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num <= 0){
break;
}
}
}
}
Eg2 :获取当前时间
public class test2 {
public static void main(String[] args) throws InterruptedException {
tenDow();
// 打印当前时间,获取当前系统时间
Date startTime = new Date(System.currentTimeMillis());
while (true){
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("hh:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}
}
public static void tenDow() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num <= 0){
break;
}
}
}
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常interruedException
- sleep时间达到后线程进入就绪状态
- sleep可以模拟网络延时,倒计时等,倒计时详见上面的例子
- 面一个对象都有一个锁,sleep不会释放锁
线程礼让
- 线程礼让,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让CPU重新调度,但不一定礼让成功,看CPU的心情
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
new Thread(myYield,"c").start();
new Thread(myYield,"d").start();
}
}
class MyYield implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
// 礼让线程
Thread.yield();
System.out.println("线程暂停执行");
}
}
小栗子
Join
Join合并线程,待此线程执行完成后,再执行其他线程
public class TestJoin implements Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程来了" + i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread();
thread.start();
for (int i = 0; i < 500; i++) {
if(i == 200){
thread.join();
}
System.out.println("main " + i);
}
}
}
线程状态
public class TestState {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("==========");
});
// 观察装填
Thread.State state =thread.getState();
System.out.println(state);
// 观察启动后的状态
thread.start();
state = thread.getState();
System.out.println(state);
while (state != Thread.State.TERMINATED){
Thread.sleep(1000);
// 更新线程状态
state = thread.getState();
System.out.println(state);
// 线程死亡后便不能再开启
thread.start();
}
}
}
public class TestPrioriy {
public static void main(String[] args) {
// 主线程的优先级
System.out.println(Thread.currentThread().getPriority());
Mypriority mypriority = new Mypriority();
Thread thread = new Thread(mypriority);
Thread thread2 = new Thread(mypriority);
Thread thread3= new Thread(mypriority);
Thread thread4 = new Thread(mypriority);
Thread thread5 = new Thread(mypriority);
Thread thread6 = new Thread(mypriority);
// 先设置优先级,再启动
thread.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(Thread.MAX_PRIORITY);
thread3.start();
thread4.setPriority(Thread.MIN_PRIORITY);
thread4.start();
thread5.setPriority(5);
thread5.start();
thread6.setPriority(Thread.NORM_PRIORITY);
thread6.start();
}
}
class Mypriority implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getState());
}
}
守护(daemon)线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不必等待守护线程执行完毕
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); // 默认是false表示用户线程,正常线程是用户线程
thread.start(); // 上帝守护线程
new Thread(you).start(); // 你 启动用户线程
}
}
// 上帝
class God implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
while (true){
System.out.println("上帝永生");
}
}
}
// 你自己
class You implements Runnable{
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
for (int i = 0; i < 3000; i++) {
System.out.println("人生不过三万天");
}
System.out.println("上帝保佑着你");
}
}
线程同步
多个线程操作同一个资源
**并发:**同一个对象被多个线程同时操作
注:
处理多线程时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时,我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
队列和锁
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized ,当一个线程获得对象的排它锁,独占资源﹐其他线程必须等待,使用后释放锁即可。
存在以下问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起;
在多线程竞争下﹐加锁﹐释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置﹐引起性能问题。
同步方法
synchronized方法和synchronized块
同步块
死锁
多个线程各自占有一些共享的资源,并且等待其他线程占有资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况,某一个同步块同时拥有“两个以上对象的锁”时,就可能发生“死锁”的问题
**产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系
上面四个条件破解1个,即可破解死锁
lock(锁)
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(0,"李亚东");
Makeup g2 = new Makeup(1,"臭屁东子");
g1.start();
g2.start();
}
}
class Lipstick{
}
class Mirror{
}
class Makeup extends Thread{
static Lipstick lipstick =new Lipstick();
static Mirror mirror = new Mirror();
int chose ;
String girlName;
Makeup(int chose,String girlName){
this.chose = chose;
this.girlName = girlName;
}
@Override
public void run() {
// 化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if(chose == 0){
synchronized (lipstick){
// 获得口红的锁
System.out.println(this.girlName + "获得了口红锁");
Thread.sleep(100);
}
synchronized (mirror){
System.out.println(this.girlName + "获得了镜子的锁");
}
}else {
synchronized (mirror){
// 获得口红的锁
System.out.println(this.girlName + "获得了镜子锁");
Thread.sleep(200);
}
synchronized (lipstick){
System.out.println(this.girlName + "获得了口红的锁");
}
}
}
}
synchronized和Lock 的对比
- Lock是显式锁(手动开启和关闭锁,不能忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好地扩展性(提供更多的子类)
- 优先使用顺序:
Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
线程通信
线程池
public class TestPool {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 关闭
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}