Java多线程编程

 


1.创建线程的方法

1.1通过继承Thread类创建线程

通过继承Thread类来创建线程的一般流程:继承Thread类,重写run()方法,调用start()开启线程

需要注意:线程开启不是立即执行,而是要看CPU的调度

还需要注意,调用run()方法和调用start()方法的区别

             

package com.ztece.ThreadDemo;

//创建线程方式一
//继承Thread类,重写run()方法,调用start()开启线程

import java.net.SocketOption;

/**
 * 总结!!!
 * 注意,线程开启不一定立即执行,由CPU调度执行
 * 要注意调用run()和调用start()的区别
 */
public class ThreadDemo01 extends Thread {//继承Thread类
    @Override
    public void run(){//重写run()方法
        //run方法线程体
        for (int i = 0; i < 500; i++) {
            System.out.println("=======执行run线程");
        }
    }

    public static void main(String[] args) {//main线程,主线程

        //创建一个线程对象
        ThreadDemo01 threadDemo01 = new ThreadDemo01();
        //调用start()方法开启线程
        //threadDemo01.run();
        //这里如果调用run()一定是先把run()线程体执行完,再执行main()线程体
        threadDemo01.start();

        //main方法线程体
        for (int i = 0; i < 1000; i++) {
            System.out.println("执行main线程=========");
        }
    }

}

 1.2 通过实现Runnable接口创建线程

通过继承Thread类来创建线程的一般流程:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类作为参数,调用start()方法

实现Runnable接口的方式

  • 1.创建一个实现了Runnable接口的类
  • 2.实现类去实现Runnable中的抽象方法:run()
  • 3.创建实现类的对象
  • 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  • 5.通过Thread类的对象调用start()
package com.ztece.ThreadDemo;
/**
 * 建议使用实现Runnable接口的方法
 */
//创建线程方式2
// 
public class ThreadDemo02 implements Runnable{//实现runnable接口
    @Override
    public void run(){//重写run()方法
        //run()线程体
        for (int i = 0; i < 200; i++) {
            System.out.println("========我在看代码"+i);
        }
    }

    public static void main(String[] args) {
        //创建runnable接口的实现类对象
        ThreadDemo02 threadDemo02 = new ThreadDemo02();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(threadDemo02);//把runnable接口实现类丢入执行线程中
        thread.start();//调用start()方法

        for (int i = 0; i < 300; i++) {
            System.out.println("我在学习多线程======="+i);
        }
    }
}

在开发过程中优先选择实现Runnable接口的方式创建线程:

1.实现的方式没有类的单继承的局限性

2.实现的方式更适合来处理多个线程共享数据的情况


2.静态代理模式

静态代理模式总结
1.真实对象和代理对象都要实现同一个接口
2.代理对象要代理真实角色

 静态代理的好处
1.代理对象可以做到很多真实对象做不了的事情
2.真实对象专注做自己的事情

package com.ztece.ThreadDemo;

public class staticProxy {
    public static void main(String[] args) {
        You you = new You();//你要结婚
        //you.HappyMarry();
        //这里不通过这个调用,而是通过代理调用
        WeddingCompany weddingCompany = new WeddingCompany(you);
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    void HappyMarry();
}
//真实角色,你去结婚
class You implements Marry{
    @Override
    public void HappyMarry(){
        System.out.println("You类被执行到了!!!");
    }
}
//代理角色,助你结婚
class WeddingCompany implements Marry{
    //代理谁,真实目标角色
    private Marry target;

    public WeddingCompany(Marry target){
        this.target = target;
    }
    @Override
    public void HappyMarry(){
        before();
        this.target.HappyMarry();//这就是真实对象
        after();
    }
    private void before(){
        System.out.println("结婚之前,布置现场");
    }
    private void after(){
        System.out.println("结婚之后,收取礼金");
    }

}

3.Lamda表达式

总结
1.lamda表达式只能有一行代码的情况下才能简化为一行,如果有多行,那么就用代码块包裹
2.前提是接口为函数式接口
3.多个参数也可以去掉参数类型,要去就都去掉,必须加上括号

什么是函数式接口?

任何接口,如果只包含唯一一个抽象方法,那么他就是一个函数式接口。

Lamda表达式的优点:

1.避免匿名内部类定义过多

2.可以让你的代码看起来很简洁

3.去掉了一堆没用意义的代码,只留下核心的逻辑。

package com.ztece.ThreadDemo;

import java.security.PublicKey;

public class LamdaDemo {

    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void lamda(){
            System.out.println("Lamda Lamda");
        }
    }
    public static void main(String[] args) {
        ILike like = new Like();//注意这里的like是ILike类型的
        like.lamda();

        like = new Like2();
        like.lamda();

        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void lamda(){
                System.out.println("Lamda Lamda Lamda");
            }
        }
        like = new Like3();
        like.lamda();
        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like = new ILike(){
            @Override
            public void lamda(){
                System.out.println("Lamda Lamda Lamda Lamda");
            }
        };
        like.lamda();
        //6.用lamda简化
        like = ()->{
            System.out.println("Lamda Lamda Lamda Lamda Lamda");
        };
        like.lamda();
        //7.简化lamda(去掉参数类型,去掉小括号,去掉大括号)
        like = ()->System.out.println("Lamda Lamda Lamda Lamda Lamda Lamda");
        like.lamda();
    }
}

//1.定义一个函数式接口
interface ILike{
    void lamda();
}
//2.实现类
class Like implements ILike{
    @Override
    public void lamda(){
        System.out.println("Lamda");
    }
}

4.对于线程的操作

4.1 停止

测试Stop
1.建议线程正常停止,利用次数,不建议使用死循环
2.建议使用标志位flag
3.不建议使用stop()或者destroy()等已经被淘汰的方法
package com.ztece.ThreadDemo;

public class TestStop implements Runnable{
    //设置标志位
    private boolean flag = true;

    @Override
    public void run(){//重写run()
        int i = 0;
        while(flag){
            System.out.println("run()正在执行"+i++);
        }

    }
    //设置一个公开的方法停止线程,调整标志位
    public void Stop(){
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();//丢入执行线程
        for (int i = 0; i < 1000; i++) {
            if(i == 990){
                testStop.Stop();//调用Stop(),切换标志位,使线程结束
                System.out.println("线程该结束了");
            }
            System.out.println("main()正在执行"+i);
        }
    }

}

4.2 礼让

Thread.yield()方法的作用:暂停当前正在执行的线程,并执行其他线程。

yield()让当前正在运行的线程回到可运行状态,以允许具有相同优先级的其他线程获得运行的机会。因此,使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。但是,实际中无法保证yield()

达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

结论:大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。

package com.ztece.ThreadDemo;

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

    public static void main(String[] args) {
        TestYield test = new TestYield();
        new Thread(test,"a").start();
        new Thread(test,"b").start();
    }
}



4.3 睡眠

可以利用sleep(),放大程序中的问题。

注意:

1、线程睡眠是帮助所有线程获得运行机会的最好方法。

2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

3、sleep()是静态方法,只能控制当前正在运行的线程。

package com.ztece.ThreadDemo;

import java.text.SimpleDateFormat;
import java.util.Date;

public  class TestSleep {
    //模拟倒计时
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while(true){
            Thread.sleep(1000);//每次睡眠一秒
            System.out.println(num--);
            if(num <= 0){
                break;
            }
        }

    }
    //打印当前系统时间
    public static void Time(){
        //获取系统当前时间
        Date time = new Date(System.currentTimeMillis());
        while(true){
            try{
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(time));
                //更新当前时间
                time = new Date(System.currentTimeMillis());
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Time();
    }
}

4.4 状态

package com.ztece.ThreadDemo;

public class TestState{

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程结束了");
        });
        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //观察线程启动后的状态
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state);

        //只要线程不终止,就一直输出状态
        while(state != Thread.State.TERMINATED){
            Thread.sleep(200);
            state = thread.getState();//一直在更新状态
            System.out.println(state);
        }
    }
}

4.5 优先级

优先级第意味着获得调度的概率比较低,并不是优先级第就不运行了,这个要看CPU的调度

先设置优先级,再启动

package com.ztece.ThreadDemo;

public class TestPrioriy implements Runnable{

    public static void main(String[] args) {
        //主线程默认的优先级
        System.out.println(Thread.currentThread().getName()+"的优先级是"+Thread.currentThread().getPriority());

        TestPrioriy testPrioriy = new TestPrioriy();

        Thread t1 = new Thread(testPrioriy);
        Thread t2 = new Thread(testPrioriy);
        Thread t3 = new Thread(testPrioriy);
        Thread t4 = new Thread(testPrioriy);
        Thread t5 = new Thread(testPrioriy);
        t1.start();//默认的优先级

        //先设置优先级,再启动
        t2.setPriority(2);
        t2.start();

        t3.setPriority(Thread.MAX_PRIORITY);
        t3.start();

        t4.setPriority(Thread.MIN_PRIORITY);
        t4.start();

    }
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName()+"的优先级是"+Thread.currentThread().getPriority());
    }
}

4.6 插队

package com.ztece.ThreadDemo;
//测试Join方法,可以想象为插队
public class TestJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("vvvvvvvvvip线程来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);//注意格式
        thread.start();
        for (int i = 0; i < 400; i++) {
            if(i == 200){
                thread.join();//插队来了
            }
            System.out.println("普通线程在执行"+i);
        }
    }
}

4.7 用户线程和内核线程

package com.ztece.ThreadDemo;

public class TestDeamon {
    public static void main(String[] args) {
        YouLife youLife = new YouLife();
        God god = new God();
        Thread thread = new Thread(god);
        //默认是false ,代表是用户线程,一般的线程都是用户线程
        thread.setDaemon(true);
        thread.start();

        new Thread(youLife).start();
    }
}

class YouLife implements Runnable{//注意,都要写Runnable接口的实现类
    @Override
    public void run(){
        for (int i = 0; i < 100000; i++) {
            System.out.println("开开心心每一天");
        }
        System.out.println("Goodbye World!!!");
    }
}

class God implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("God help you");
        }
    }
}

5.线程同步机制

关于线程的同步,一般有以下解决方法:

1. 在需要同步的方法的方法签名中加入synchronized关键字。

2. 使用synchronized块对需要进行同步的代码段进行同步。

3. 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。

package com.ztece.ThreadDemo.syn;
//不安全的取钱
//两个人一起去银行取同一个账户上的钱
/**
 * 同步代码块
 * 要锁住变化的东西 这个例子里边是 account
 */
public class unsafeBank {
    public static void main(String[] args) {
        Account account = new Account(500,"基金");
        //两个取钱线程
        Drawing boy = new Drawing(account,100,"你");
        Drawing girl = new Drawing(account,50,"她");

        boy.start();
        girl.start();
    }
}
//账户
class Account {
    int money;
    String name;

    public Account(int money,String name){
        this.money = money;
        this.name = name;
    }

}
//银行:模拟取款
class Drawing extends Thread{
    Account account;
    //取走的钱
    int drawingmoney;
    //留下的钱
    int nowmoney;

    public Drawing(Account account,int drawingmoney,String name){
        super(name);
        this.account = account;
        this.drawingmoney = drawingmoney;
    }
    //取钱
    @Override
    public void run(){
        //锁的对象是变化的量,需要增删改的对象
        synchronized (account){//获得account的锁,才能对account进行操作
            //这里变化的是account需要对account加锁
            //判断还有没有钱
            if(account.money-drawingmoney<0){
                System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
                return;
            }
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money = account.money - drawingmoney;
            nowmoney = nowmoney + drawingmoney;
            System.out.println(Thread.currentThread().getName()+"手上有"+nowmoney);
            System.out.println(account.name+"账户上的钱还有"+account.money);
        }
    }
}

package com.ztece.ThreadDemo.syn;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 同步方法
 * 在方法的声明中加上 synchronized
 */
public class unsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();
        new Thread(buyTicket,"我").start();
        new Thread(buyTicket,"你").start();
        new Thread(buyTicket,"黄牛").start();
    }
}
class BuyTicket implements Runnable{
    private int ticket = 10;//票数
    boolean flag = true;//标志位
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run(){
        //一直买
        while(flag){
            try{
                lock.lock();//显式的锁
                try {
                    buy();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }finally {
                lock.unlock();
            }

        }
    }
    //synchronized 同步方法,锁的是this
    //同步方法保证线程的安全
    private void buy() throws InterruptedException {
        //判断是否还有票
        if(ticket<=0){
            flag = false;
            return ;
        }
        //模拟延时,放大问题
        Thread.sleep(500);
        //买票
        System.out.println(Thread.currentThread().getName()+"买到了第"+ticket--+"张票");
    }
}

 


6.生产者消费者问题

6.1 管程法解决线程间通信的问题

管程法在生产者消费者模式中,其实就是建立一个缓冲区,让生产者将生产产品(数据)放入缓冲区,而消费者从缓冲区获取产品。生产者和消费者之间不直接进行通信。

package com.ztece.ThreadDemo.syn;

/**
 * 管程法解决线程间通信的问题
 */
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();

        new Productor(synContainer).start();
        new Consumer(synContainer).start();
    }
}
//生产者
class Productor extends Thread{
    SynContainer container;

    public Productor(SynContainer container) {
        this.container = container;
    }
    //生产
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            try {
                container.push(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产了第"+i+"只鸡");
        }
    }

}
//消费者
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            try {
                System.out.println("消费了第"+container.pop().id+"只鸡");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//商品
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}
//缓冲区
class SynContainer{
    //缓冲区大小
    Chicken[] chickens = new Chicken[10];
    //容器计数器
    int count = 0;

    //生产者放入产品
    public synchronized void push(Chicken chicken) throws InterruptedException {
        //如果缓冲区满了,就需要通知消费者取走产品
        if(count == chickens.length){
            //通知消费者取走商品
            this.wait();
        }
        //如果缓冲区没有满,继续放入产品
        chickens[count] = chicken;
        count++;

        //可以通知消费者消费了
        this.notifyAll();
    }
    //消费者取走产品
    public synchronized Chicken pop() throws InterruptedException {
        //看看有没有产品可以取走
        if(count == 0){
            //等待生产者生产,消费者等待
            this.wait();
        }
        //如果可以消费
        count--;
        Chicken chicken = chickens[count];

        //通知生产者生产
        this.notifyAll();

        return chicken;
    }

}

6.2 信号灯法解决线程间通信的问题

在生产者消费者模式中,信号灯,顾名思义,是用来在生产者与消费者之间传递信号的一个旗帜。

如在一个生产与消费模式中,当生产者或消费者线程完成自己的工作,等待另一个线程进行时,便会将信号值修改用以告诉另一者:我的事情做完了,该你了。

而另一者获取信号的变化后便会做出对应的行为。在这个过程中,信号值一直被反复更改,直到所有线程均执行完毕。

package com.ztece.ThreadDemo.syn;

public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}

//生产者(演员)
class Player extends Thread{
    TV tv;

    public Player(TV tv) {
        this.tv = tv;
    }
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            if(i%2 == 0){
                try {
                    this.tv.play("快乐大本营正在播放");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                try {
                    this.tv.play("抖音");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
//消费者(观众)
class Watcher extends Thread{
    TV tv;

    public Watcher(TV tv) {
        this.tv = tv;
    }
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            try {
                tv.watch();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
//产品(节目)
class TV{
    //演员表演,观众等待 T
    //观众观看,观众等待 F、
    String voice;//表演的节目
    boolean flag = true;
    //表演
    public synchronized void play(String voice) throws InterruptedException {
        if(!flag){
            this.wait();
        }
        System.out.println("演员表演了:"+voice);
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }
    //观看
    public synchronized void watch() throws InterruptedException {
        if(flag){
            this.wait();
        }
        System.out.println("观看了:"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }

}

6.3 线程池解决线程间通信的问题

为了避免重复的创建线程,线程池的出现可以让线程进行复用。通俗点讲,当有工作来,就会向线程池拿一个线程,当工作完成后,并不是直接关闭线程,而是将这个线程归还给线程池供其他任务使用。

package com.ztece.ThreadDemo.syn;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//测试线程池
public class TestPool {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //2.执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //3.关闭链接
        service.shutdown();
    }
}
class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
}
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值