多线程学习

多线程的实现方式

  1. 继承Thread方法
  2. 实现Runable接口
  3. 实现Callable接口

1.继承Thread方法 

创建一个类继承Thread类,重写run方法

通过多态创建一个子线程,子线程调用start方法

//创建线程方式一:继承Thread类,重现run方法
public class TestThread1 extends Thread{
    @Override
    public void run(){
        for (int i=0;i<20;i++){
            System.out.println(currentThread().getName()+"执行"+i);
        }
    }

    public static void main(String[] args) {
        //创建一个子线程
        Thread thread = new TestThread1();
        //线程启动
        thread.start();


        for (int i=0;i<200;i++){
            System.out.println(currentThread().getName()+"执行"+i);
        }
    }
}

2.实现Runnable接口

创建一个类实现Runnable接口

重写run方法

创建runnable接口实现类对象

创建Thread类对象,通过线程对象来开启我们的线程

// 创建线程的方式2:实现runanable接口,重新写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread3 implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i=0 ; i<100; i++){
            System.out.println(currentThread().getName()+":"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        TestThread3 testThread3 = new TestThread3();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(testThread3,"子线程1");
        thread.start();
        currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            System.out.println(currentThread().getName()+":"+i);
        }
    }
}

小结

  • 继承Thread类
    • 子类继承Thread类具有多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用:避免OOP单继承局限性
  • 实现Runnable接口
    • 实现接口Runnable具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

 并发线程遇到数据紊乱问题

//多个线程同时操作一个对象
//买火车票的例子

//发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
public class TestThread4 implements Runnable{

    //票数
    private int sum = 20;

    @Override
    public void run() {
        while (true){
            if (sum<=0){
                break;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+sum--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread4 testThread4 = new TestThread4();
        new Thread(testThread4,"线程1").start();
        new Thread(testThread4,"线程2").start();

    }
}


线程1拿到了第19票
线程2拿到了第20票
线程1拿到了第17票
线程2拿到了第18票
线程1拿到了第16票
线程2拿到了第15票
线程2拿到了第14票
线程1拿到了第13票
线程1拿到了第12票
线程2拿到了第11票
线程1拿到了第9票
线程2拿到了第10票
线程1拿到了第8票
线程2拿到了第8票
线程2拿到了第7票
线程1拿到了第6票
线程2拿到了第4票
线程1拿到了第5票
线程1拿到了第3票
线程2拿到了第2票
线程1拿到了第1票
线程2拿到了第0票

3.实现CallAble接口 

  1. 创建执行服务:创建线程池
  2. 提交执行
  3. 获取结果
  4. 关闭服务

重写call方法,创建线程池,提交线程子类

/**
 * 可以拥有返回值
 * 可以抛出异常
 */
public class TestCallable implements Callable<Boolean> {

    @Override
    public Boolean call() throws Exception {
        String inputurl = "mvnw.cmd";
        String outputurl = Thread.currentThread().getName()+".cmd";
        InputStream inputStream = new FileInputStream(inputurl);
        OutputStream outputStream = new FileOutputStream(outputurl);
        byte[] bytes = new byte[1024];
        while (inputStream.read(bytes)!=-1){
            outputStream.write(bytes);
        }
        return Boolean.TRUE;
    }

    public static void main(String[] args) throws Exception {
        TestCallable t1 =new TestCallable();
        TestCallable t2 =new TestCallable();
        TestCallable t3 =new TestCallable();

        //创建执行服务:创建线程池
        ExecutorService ser = Executors.newFixedThreadPool(3);

        //提交执行
        Future<Boolean> submit1 = ser.submit(t1);
        Future<Boolean> submit2 = ser.submit(t2);
        Future<Boolean> submit3 = ser.submit(t3);

        //获取结果
        Boolean aBoolean1 = submit1.get();
        Boolean aBoolean2 = submit2.get();
        Boolean aBoolean3 = submit3.get();

        //关闭服务
        ser.shutdown();
    }
}

线程的状态

  • 创建状态
  • 就绪状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

方法说明
setPriority(int newPriority)更改线程优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程
void interrrupt()中断线程(不用)
boolean isAlive测试线程是否处于活动状态

线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数;
  • sleep存在异常InterruptedException;
  • sleep时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时,倒计时等;
  • 每一个对象都有一个锁,sleep不会释放锁;

线程礼让

yield不一定成功

/**
 * 测试yield
 * 礼让不一定成功
 */
public class TestYield{
    public static void main(String[] args) {
        MyYield myYield = new MyYield();
        Thread t1 = new Thread(myYield,"a线程");
        Thread t2 = new Thread(myYield,"b线程");
        t1.start();
        t2.start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"执行结束");
    }
}

线程优先级

  • Java提供一个线程调度器来监控程序中启动进入就绪状态的所有线程,线程调度器按照优先级决定的那个应该调度哪个线程来执行
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY=1;
    • Thread.Max_PRIORITY=10;
    • Thread.NORM_PRIORITY=5;
  • 使用一下方式改变或获取优先级
    • getPriority.setPRiority(int xxx);
public class TestPriority {

    public static void main(String[] args) {
        Mypriority mypriority = new Mypriority();
        Thread t1 = new Thread(mypriority, "t1");
        Thread t2 = new Thread(mypriority, "t2");
        Thread t3 = new Thread(mypriority, "t3");
        t2.setPriority(4);
        t3.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
        t3.start();
        System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority());
    }
}


class Mypriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority());
    }
}

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必去确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕,守护线程等待最后一个用户线程结束结束
  • 守护线程:操作日志,监控内存,垃圾回收等待。
//测试守护线程

public class TestDaemon {
    public static void main(String[] args) {
        Thread human = new Thread(new Human());
        Thread god = new Thread(new God());
        Thread human2 = new Thread(new Human2());
        god.setDaemon(true);//开启守护线程
        human.start();
        god.start();
        human2.start();
    }
}

class Human implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("用户进程"+i);
        }
    }
}

class God implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("守护进程");
        }
    }
}

class Human2 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("用户线程2"+i);
        }
    }
}

线程的同步机制

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被i访问时的正确性,在访问时加入锁机制sychronized,当一个线程获得对象的排他锁,独占资源,其他线程必须的等待,

使用后释放锁即可,存在下列问题:

  • 一个线程持有锁会导致其他所有拥有此锁的线程挂起:
  • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

同步方法 

  • 由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法:synchronized放啊和synchronized块
  • 同步方法:public synchronized void method(int args){}
  • synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
  • 缺陷:若将一个大的方法申明为synchronized将会影响效率

同步块 

  • 同步块:synchronized(OBJ){}
  • Obj称之为同步监视器
    • Obj可以是任何对象,到那时推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class【反射】
  • 同步监视器的执行过程
    • 第一个线程访问,锁定同步监视器

死锁

死锁:多个线程互相抱着对方需要的资源,然后形成僵持

产生死锁的四个必要条件:

  • 互斥条件:一个资源每次只能被一个进程使用。
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
package cn.cslg.kuangshen.lock;

public class DeadLock extends Thread{

    public static void main(String[] args) {
        Thread t1 = new DeadLock(0);
        Thread t2 = new DeadLock(2);
        t1.setName("t1");
        t2.setName("t2");
        t1.start();
        t2.start();
    }
    private int choose = 0;
    private String lipstick ="a";
    private String mirror ="b";
    @Override
    public void run() {
        if (choose == 0){
            synchronized (lipstick){
                System.out.println(Thread.currentThread().getName()+"lipstick");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (mirror){
                    System.out.println(Thread.currentThread().getName()+"mirror");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(Thread.currentThread().getName()+"lipstick");
                synchronized (lipstick){
                    System.out.println(Thread.currentThread().getName()+"mirror");
                }
            }
        }

    }

    public DeadLock(int choose){
        this.choose = choose;
    }
}

Lock(锁) 

package cn.cslg.kuangshen.lock;

import java.util.concurrent.locks.ReentrantLock;

//测试Lock锁
public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();
        Thread t1 = new Thread(testLock2,"t1");
        Thread t2 = new Thread(testLock2,"t2");
        t1.start();
        t2.start();
    }
}

class TestLock2 implements Runnable{
    private int ticketNums =10;
    //定义lock锁
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            //加锁
            lock.lock();
            while (ticketNums>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName()+ticketNums--);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }finally {
            //解锁
            lock.unlock();
        }

    }
}

生产者消费者问题 略过

使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

好处:

  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理
    • corePoolSize:核心池大小
    • maxinmumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长事件后会终止
//测试线程池
public class TestPool {

    public static void main(String[] args) {
        //创建服务,创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        //执行
        executorService.submit(new TestThread());
        executorService.submit(new TestThread());
        executorService.submit(new TestThread());
        //关闭连接
        executorService.shutdown();
    }
}

class TestThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值