多线程2

线程间通讯

多个线程在操作同一个资源,但操作动作不同

public class Demo {
    public static void main(String[] args) {
        Resource resource = new Resource();

        Input input = new Input(resource);
        Output output = new Output(resource);

        Thread t1 = new Thread(input);
        Thread t2 = new Thread(output);

        t1.start();
        t2.start();
    }
}

//资源
public class Resource {
    String name;
    String sex;
}

//输入资源的线程
public class Input implements Runnable{
    private Resource resource;

    public Input(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int x=0;
        while (true){
        //加锁为了防止打印出"aaa"匹配"女"或者"bbb"匹配"男"的情况
            synchronized (resource) {
                if (x == 0) {
                    resource.name = "aaa";
                    resource.sex = "男";
                } else {
                    resource.name = "bbb";
                    resource.sex = "女";
                }
                x = (x + 1) % 2;
            }
        }
    }
}

//输入资源的线程
public class Output implements Runnable{
    private Resource resource;

    public Output(Resource resource) {
        this.resource = resource;
    }


    @Override
    public void run() {
        while (true){
            synchronized (resource) {
                System.out.println(resource.name + "..." + resource.sex);
            }
        }
    }
}

打印结果发现,输出线程可能会一直获取到执行权,不停的输出同一个数据;输入线程也可能会一直获取到执行权,不停的存入数据。
目的:实现输入一个数据,就输出一个数据
我们可以加一个flag标记,在输入线程获得执行权时,先判断资源对象中是否有值,如果每值就输入数据,之后将flag改为true。反之,将Output线程唤醒,使Input线程进入等待状态。

public class Resource {
    String name;
    String sex;
    boolean flag=false;
}

public class Input implements Runnable{
    private Resource resource;

    public Input(Resource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        int x=0;
        while (true){
            synchronized (resource) {
                if (resource.flag){
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (x == 0) {
                    resource.name = "aaa";
                    resource.sex = "男";
                } else {
                    resource.name = "bbb";
                    resource.sex = "女";
                }
                x = (x + 1) % 2;
                resource.flag=true;
                resource.notify();
            }
        }
    }
}

public class Output implements Runnable{
    private Resource resource;

    public Output(Resource resource) {
        this.resource = resource;
    }


    @Override
    public void run() {
        while (true){
            synchronized (resource) {
                if (!resource.flag){
                    try {
                        resource.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(resource.name + "..." + resource.sex);
                resource.flag=false;
                resource.notify();
            }
        }
    }
}
//如果有多条输入输出线程,就将resource.notify();改为resource.notifyAll();
内存可见性问题

Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。

public class Demo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        Thread thread = new Thread(myRunnable);

        thread.start();

        while (true){
            if (myRunnable.getFlag()) {
                System.out.println("进入if语句");
                break;
            }
        }
    }
}

class MyRunnable implements Runnable{
    boolean flag=false;

    public boolean getFlag() {
        return flag;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.flag=true;
        System.out.println("flag的值已经被更改为"+flag);
    }
}

运行结果显示,只打印了"flag的值已经被更改为true",但是程序没有停止,代表着循环还在继续。
从逻辑上看,应该当flag值被更改为true后,进入if的判断语句,输出"进入if语句",随后结束程序
原因:当线程的工作内存中的flag值被更改为true后,未被更新到主内存中,主内存中的flag的值依旧为false。

我们可以将flag变量用volatile关键字修饰。
volatile关键字:当多个线程进行操作共享数据时,可以保证内存中的数据可见。
对于可见性,Java提供了volatile关键字来保证可见性。
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性,但是这样做效率较慢
volatile不具备互斥性,也不能保证变量的原子性。

变量的原子性:对于一个变量,比如账户余额,取出1000元,余额减少1000,存入1000元,余额增加1000,如果在取钱的时候,出现故障,比如停电之类,导致账户中的余额改变了,但是没有取出钱,就要将账户中的余额改为取钱操作之前的数目。一个事务要么完全被执行,要么完全不被执行,事务中的变量的改变也是如此,这就是变量的原子性。

CAS算法
public class Demo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

		//创建10个线程读取并改变i的值
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
    }
}

class MyRunnable implements Runnable{
    int i=0;

    public int getI() {
        return ++i;
    }

    @Override
    public void run() {
        while (true){
        //sleep操作是为了让线程停一会,方便出现问题的现象
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"修改后i的值为:"+getI());
        }
    }
}

运行结果发现,会出现两个线程打印出的i的值相同
原因:假设Thread-0获取到i的值为1,此时Thread-1抢夺执行权也获取到i的值为1。然后Thread-0再次抢回执行权,将i自增并打印改变后i的值2,此时Thread-1也将i自增并打印改变后i的值2,这就导致了值的重复。
我们可以用synchronized来解决,但是当一个线程进行操作时,其余九个线程只能等待,效率很慢。

使用CAS算法解决:
CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。CAS 是一种无锁的非阻塞算法的实现。CAS 包含了 3 个操作数:
需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时, CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。

java.util.concurrent.atomic 包下提供了一些原子操作的常用类

import java.util.concurrent.atomic.AtomicInteger;

public class Demo {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();

        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(myRunnable);
            thread.start();
        }
    }
}

class MyRunnable implements Runnable{
    AtomicInteger i=new AtomicInteger(0);

    public int getI() {
        return i.incrementAndGet();   //相当于++i
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"修改后i的值为:"+getI());
        }
    }
}

线程的状态

新建:线程被创建出来,线程对象被new
就绪:也被成为可执行状态,调用线程的start方法即可进入就绪状态,随时可能被CPU执行。线程具有CPU执行资格,但是不具备CPU的执行权
运行:具有CPU执行资格,也具有CPU执行权。线程只能从就绪状态进入运行状态
阻塞:不具备CPU的执行资格,也不具备CPU执行权。一般是调用了wait、sleep方法或者线程获取锁失败会进入该状态
死亡:main方法或run方法执行结束,或因其他异常线程结束了生命周期

线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
工具类个Executors
public static ExecutorService newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

ExecutorService类中的方法:

Future<?> submit(Runnable task)提交一个 Runnable 
任务用于执行,并返回一个表示该任务的 Future。
<T> Future<T> submit(Callable<T> task)
提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //获取线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //给线程池提交任务
        executorService.submit(new MyRunnable());
        Future<Integer> submit = executorService.submit(new MyCallable());
        //获取MyCallable类中call方法的返回值
        Integer integer = submit.get();
        System.out.println(integer);

        //关闭线程池
        executorService.shutdown();
    }
}

class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum=0;
        for (int i = 0; i <= 100; i++) {
            sum=sum+i;
        }
        return sum;
    }
}

class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("runnable run...");
    }
}
匿名内部类启动线程
public class Demo {
    public static void main(String[] args) {
        //使用匿名内部类创建线程
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(i+"..."+Thread.currentThread().getName()+"...Thread");
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(i+"..."+Thread.currentThread().getName()+"...Runnable");
                }
            }
        }).start();
    }
}
Timer和TimerTask类

Timer
一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行。 与每个 Timer 对象相对应的是单个后台线程,用于顺序地执行所有计时器任务。
TimerTask
由 Timer 安排为一次执行或重复执行的任务。

构造方法&方法

Timer
public Timer()
创建一个新计时器。相关的线程不作为守护程序运行。
public void schedule(TimerTask task,long delay)
安排在指定延迟后执行指定的任务。
public void schedule(TimerTask task,Date time)
安排在指定的时间执行指定的任务。如果此时间已过去,则安排立即执行该任务。
public void schedule(TimerTask task,long delay,long period)
安排指定的任务从指定的延迟后开始进行重复的固定延迟执行。
public void schedule(TimerTask task,Date firstTime,long period)
安排指定的任务在指定的时间开始进行重复的固定延迟执行。

TimerTask
public abstract void run()
此计时器任务要执行的操作。
public boolean cancel()
取消此计时器任务。

import java.util.Timer;
import java.util.TimerTask;

public class Demo {
    public static void main(String[] args) {
        //创建定时器
        Timer timer = new Timer();
        //创建任务
        MyTimerTask myTimerTask = new MyTimerTask(timer);

        //2秒后执行任务,之后每过1秒重复执行任务
        timer.schedule(myTimerTask,2000,1000);
        //取消定时器
        //timer.cancel();
        //取消定时任务
        //myTimerTask.cancel();
    }
}
class MyTimerTask extends TimerTask{
    Timer timer;

    public MyTimerTask(Timer timer) {
        this.timer=timer;
    }

    public MyTimerTask() {

    }

    @Override
    public void run() {
        System.out.println("run...");
    }
}

public class Demo {
    public static void main(String[] args) throws ParseException {
        //指定日期执行定时任务
        Timer timer = new Timer();
        String str="2020-02-19 15:17:00";
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date date = simpleDateFormat.parse(str);
        timer.schedule(new MyTimerTask(timer),date);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值