线程

复习

Iterable;Collection;List;Set;Map;Iterator;ListIterator;
Vector;Stack;ArrayList;LinkedList;
HashSet;LinkedHashSet;TreeSet
HashMap;LinkedHashMap;TreeMap;IdentifyHashMap;WeakHashMap;Hashtable; Properties
Collections
InputStream:字节输入流,是所有的字节输入流的父类。抽象类。
OutputStream:字节输出流,是所有的字节输出流的父类,抽象类。
Reader:字符输入流,抽象类,所有的字符输入流的父类。
Writer:字符输出流,抽象类,所有的字符输出流的父类。
FileInputStream、FileOutputStream。
FileReader、FileWriter。字符流。使用父类的编码和解码功能实现字符到字节之间的相互的转换。使用的字符集为平台默认的。
BufferedInputStream BufferedOutputStream
BufferedReader :readLine BufferedWriter newLine()
序列化:将内存中对象转换为字节数据的过程。
反序列化:将自己数据还原为对象的过程。
进程:多个进程可以并发执行;进程是系统进行资源分配的最小单位,多个进程不共享资源。进程是线程的载体,一个进程至少要有一个线程。
线程:多个线程可以并发执行;线程不能独立存在,必须依赖于某一个进程。线程是进程中要执行的代码的一个片段。线程是系统进行调度执行的最小单位。
线程的概念:进程中的一个任务线。

第一节 多线程、java创建线程的方式

单线程程序的问题:一旦线程被阻塞,那么整个进程就被阻塞了,只有解除阻塞之后,程序才能执行。
多线程程序的优点:多线程的程序可以被cpu调度执行的概率更大。有一个线程被阻塞了,不会影响其他的线程。
java程序中的第一个线程,主线程main 是由jvm来创建。
java.lang.Thread:描述线程对象的。

1 创建自定义线程的三种方式

1.继承Thread类

/**
 *java中线程的实现方式
 * 1.继承线程类
 */
public class TestThread {
    public static void main(String[] args) {
        //一个任务是创建另外一个线程
        Thread myThread=new MyThread();
        //启动线程,thread等待CPU调度执行,在执行此句代码的时候CPU正在调动执行main线程
        myThread.start();
        //打印1-10000
        for (int i = 0; i <10000 ; i++) {
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
//线程是任务
class MyThread extends Thread{
    @Override
    //打印1-10000
    public void run() {
        for (int i = 0; i <10000 ; i++) {
            //得到当前线程对象的名字
            System.out.println(this.getName()+"-->"+i);
        }
    }
}

2. 实现Runnable接口

**
 * 线程的第二种实现方法:实现Runnable接口
 * 实现方法:定义任务类,在任务类中编写任务代码。然后交给Thread类的对象去执行
 */
public class TestRunnable {
    public static void main(String[] args) {
        Runnable myRunnnable=new MyRunnable();
        //创建thread线程对象
        Thread thread=new Thread(myRunnnable);
        thread.setName("韩梅梅");
        thread.start();
        //打印1-10000
        for (int i = 0; i <10000 ; i++) {
            //得到线程对象的名字
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }

    }
}
//任务类 需要实现java.lang.Runnable
class MyRunnable implements Runnable{
    @Override
    //任务类的任务主体代码 run方法
    public void run() {
        for (int i = 0; i <10000 ; i++) {
            //得到当前对象的名字
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
    }
}

3.练习

/**
 *两种方式实现火车售票
 */
public class TicketTest {
    public static void main(String[] args) {
        test1();
        //test2();

    }
    //使用第一种继承线程类的方式,模拟火车站售票系统
    static void test1(){
        SaleSystem saleSystem=new SaleSystem();
        Saler saler1=new Saler(saleSystem,"售票窗口1");
        Saler saler2=new Saler(saleSystem,"售票窗口2");
        Saler saler3=new Saler(saleSystem,"售票窗口3");
        Saler saler4=new Saler(saleSystem,"售票窗口4");
        Saler saler5=new Saler(saleSystem,"售票窗口5");
        saler1.start();
        saler2.start();
        saler3.start();
        saler4.start();
        saler5.start();


    }

    //使用第二种方式 显示  实现Runnable接口
    static void test2(){
        //唯一的任务
        Runnable saleRunnable=new SaleRunnable();

        Thread thread1=new Thread(saleRunnable,"售票窗口1");
        Thread thread2=new Thread(saleRunnable,"售票窗口2");
        Thread thread3=new Thread(saleRunnable,"售票窗口3");
        Thread thread4=new Thread(saleRunnable,"售票窗口4");
        Thread thread5=new Thread(saleRunnable,"售票窗口5");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread5.start();

    }
}
//售票系统
class SaleSystem{
    //余票
    private int ticketCount=100;
    //是否出票成功 true成功
    public boolean sale(int saleCount){
        if(ticketCount>=saleCount){
            ticketCount -=saleCount;
            System.out.println(Thread.currentThread().getName()+"售票成功,剩余票数:"+ticketCount);
            return  true;
        }
        System.out.println(Thread.currentThread().getName()+"售票失败,剩余票数:"+ticketCount);
        return  false;
    }
}
//售票员 线程
class Saler extends Thread{
    //持有唯一对象的引用
    SaleSystem saleSystem;
    public Saler(SaleSystem saleSystem,String name){
        super(name);
        this.saleSystem=saleSystem;
    }
    //售票的主体代码
    @Override
    public void run() {
        while(true){
            //每次售票一张
            boolean result=saleSystem.sale(1);
            //售票不成功,结束售票
            if(!result)return;
        }
    }
}
//定义任务类 买票
class SaleRunnable implements Runnable{
    //访问唯一的票务系统
    private SaleSystem  saleSystem=new SaleSystem();
    @Override
    public void run() {
        while(true){
            boolean result=saleSystem.sale(1);
            if(!result)return;
        }
    }
}

4 总结两种方式的优缺点
继承线程类:优点:比较容易理解,代码实现简单。缺点:多个线程对象共享同一个数据的实现相对复杂。
实现Runnable接口:优点:多个线程执行同一个任务,实现比较简单。也就是多个线程共享同一个数据。缺点:相对理解复杂,代码实现相对复杂。

第二节 实现线程的第三种方式

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * 线程的第三种实现方式:实现Callable接口
 */
public class TestCallable {
    public static void main(String[] args) throws Exception{
        MyCallable myCallable=new MyCallable();
        //FutureTask实现了Runnable接口,用来调度管理任务
        FutureTask  task=new FutureTask(myCallable);

        //创建线程对象
        Thread thread=new Thread(task);
        thread.start();

        System.out.println("线程获得的结果为:"+task.get());

        System.out.println("helloWorld");

        //用来获得任务的结果,阻塞式方法。线程任务的结果何时产生,该方法何时返回
        //task.get();
        //task.cancel(false);   //是否取消任务
        //task.isDone();      //任务是否干完
        //task.isCancelled();  //任务是否已经被取消。
    }
}
//定义任务类
class MyCallable implements Callable<Integer>{
    //泛型规定了方法的返回类型
    //call实现的主体就是线程的任务的主题部分
    @Override
    public Integer call() throws Exception {  //产生的异常可以选择抛出
        Thread.sleep(5000);   //这里会选择异常
        //产生一个0-99的随机数,然后自动装箱返回
        return (int)(Math.random()*100);//等价于Integer.valueOf((int)(Math.random()*100))
    }
}

1.第三种方式和其他的两种方式的区别:

Call 方法有返回类型,可以根据泛型指定,其他的两种方式,run方法返回类型为void。第三种方式可以任务执行完毕之后,得到一个你想得到的值。通过get去获得该结果。
run方法的实现中,如果存在有异常产生的内容,那么只能使用try-catch 来处理,不能通过throws 来处理。call 方法定义的时候就抛出了Exception。所以多了一种选择,如果call 方法中存在异常代码,可以不做任何的处理,也就是可以不使用try-catch 来捕获。

2.线程的生命周期

在这里插入图片描述

第三节 线程的常用方法

1. 线程的优先级

涉及到的方法和字段
int getPriority() 获得当前线程的优先级的
void setPriority(int p) 设置线程对象的优先级
Private int priority 该值的取值范围是【1-10】默认是5.如果通过setPriority 修改该值的时候,超过了范围,那么会抛出非法参数异常。
通过控制线程对象的优先级,实现cpu调度执行的概率的大小的控制。

/**
 *线程对象的优先级
 */
public class TestPriority {
    public static void main(String[] args) {
        PriorityThread priorityThread0=new PriorityThread();
        priorityThread0.setName("咖妃");
        PriorityThread priorityThread1=new PriorityThread();
        priorityThread1.setName("香妃");
        //先设置优先级后启动
        priorityThread0.setPriority(Thread.MIN_PRIORITY);
        priorityThread1.setPriority(Thread.MAX_PRIORITY);
        //启动
        priorityThread0.start();
        priorityThread1.start();
    }
}
//
class PriorityThread extends Thread{
    private int i;

    @Override
    public void run() {
        while(true){
            System.out.println(getName()+"\ti="+i++);
        }
    }
}

2 .join

作用:导致当前线程被阻塞。调用了join 方法的线程对象执行完毕之后,解除阻塞。
使用:必须先调用start方法,再调用join 方法。

**
 * join
 */
public class TestJoin {
    public static void main(String[] args) throws Exception{
        ChengThread chengThread=new ChengThread();
        chengThread.setName("程咬金");
        for (int i = 0; i < 10; i++) {
            if(i==5){
                //当前被cpu调度执行的线程是main线程。thread只是继续状态等待cpu调度。
                chengThread.start();
                //会导致当前线程被阻塞,main线程被阻塞,从运行状态,进入阻塞状态。
                //当调用jion方法的线程对象,执行完自己的任务执行,被阻塞是线程解除阻塞。
                // 从阻塞状态进入就绪状态,等待cpu的下次调度。
                chengThread.join();
                //join (long millis)带参数的方法:线程解除阻塞的条件有2个:
                // 一个是调用join 方法的线程执行完毕,一个是被插队的时间达到了参数指定的时间。
            }
            System.out.println(Thread.currentThread().getName()+"="+i);
        }
    }
}
class ChengThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"="+i);

        }
    }
}

3 Thread.yield()

说明:这是Thread类的一个静态的方法。
作用:导致当前线程从运行状态,切换为就绪状态,让出cpu的控制权,给其他的线程使用。

**
 * yield
 */
public class TestYield {
    public static void main(String[] args) {
        YieldThread yieldThread0=new YieldThread();
        YieldThread yieldThread1=new YieldThread();
        yieldThread0.start();
        yieldThread1.start();
    }
}
class YieldThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"-->"+i);
            //直接调用父类的中的静态方法。
            //导致当前线程从运行到就绪状态
            yield();
        }
    }
}

4 Thread.sleep(long time)

说明:该方法是Thread类的静态方法,参数是一个毫秒时间。
作用:导致当前线程被阻塞,何时解除阻塞:当线程从运行状态进入到阻塞状态开始计时,当阻塞时间超过参数指定的时间,自动解除阻塞,进入就绪状态。

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.SimpleFormatter;

/**
 *
 */
public class TestSleep {
    public static void main(String[] args) throws Exception {
        //test1();
        test2();
    }
    //实现一个倒计时效果
    static void test1() throws Exception{
        System.out.println("READY");
        for (int i = 3; i >0 ; i--) {
            System.out.println(i);
            Thread.sleep(1000);
        }
        System.out.println("GO!");
    }
    //计时器效果 将系统当前时间打印到控制台。输出的格式为:dd:mm:ss
    //每输出一次,变化一秒
    static void test2() throws Exception{
        DateFormat dateFormat=new SimpleDateFormat("hh:mm:ss");
        Date date=new Date();
        while(true){
            long time=System.currentTimeMillis();
            String str=dateFormat.format(date);
            System.out.println(str);
            //时间后移一秒
            date.setSeconds(date.getSeconds()+1);
            long cost=System.currentTimeMillis()-time;
            //用于精确控制一个循环周期消耗的时间的
            Thread.sleep(1000-cost);
        }
    }
}

5 .setDaemon(boolean)

**说明:**设置当前线程对象为守护线程或者是用户线程。如果参数为true,则为守护线程,否则为用户线程。默认为false,用户线程。
**线程的分类:**线程分为2类:
用户线程、前台线程:
守护线程、后台线程、幽灵线程:
**总结:**如果一个进程中所有的存活的线程都是守护线程了,那么该进程被jvm杀死。进程的生命周期只依赖于是否存在用户线程。和守护线程没有任何的关系。
守护线程的作用:一般是给用户线程提供服务的。
GC线程就是一个守护线程,负责给用户线程擦屁股的。而且GC线程被调度执行的概率非常低、优先级很低。
**注意:**必须先设置线程为守护线程,才能调用start方法启动。Main线程不能设置为守护线程,因为已经被jvm启动了。

/**
 *Daemon
 */
public class TestDaemon {
    public static void main(String[] args) {
        DaemonThread daemonThread=new DaemonThread();
        //设置DaemonThred为守护线程
        //daemonThread.setDaemon(true);
        daemonThread.start();

        for (int i = 0; i < 50; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
    }
}
class DaemonThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; ; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+"-->"+i);
        }
    }
}

6.interrupt() interrupted() isInterrupted()

说明:任何的线程都有一个属性,叫做线程中断标志位。boolean值。默认是false。
interrupt():Thread类的实例方法。设置线程中断标志位为 true。如果被设置的线程处于阻塞状态,则产生阻塞的原因的代码处会抛出异常。还会重置线程中断标志位为false。
isInterrupted() 返回 线程中断标志位的值。True or false。
interrupted():重置线程中断标志位的。调用之后,标志位为false。并返回之前的标志位的值。

import java.io.FileInputStream;

/**
 *interrupt() 
 */
public class TestInterrupt {
    public static void main(String[] args) throws Exception{
        //test1();
        test2();

    }
    // 缺点不能让线程在阻塞的状态下被中断,这个线程的run方法里面有一个Thread.sleep(1000),
    // 如果在休眠500毫秒的时候,这时候线程还没醒,如果在这时flag被设置成了false,这个时候线程不能立即中断
    //得等到程序醒来后中断
    static void test1() throws Exception{
        FlagThread flagThread=new FlagThread();
        flagThread.start();
        //5秒之后设置flag为false 中断thread
        Thread.sleep(500);  //5秒之后我就想让这个线程被中断;  //输出结果Thread-0 -->0
        //System.out.println(Thread.currentThread().getName());//main
        flagThread.setFlag(false);
    }

    static void test2() throws Exception{
        InterruptThread thread=new InterruptThread();
        thread.start();
        //2秒之后,thread对象也睡了2秒,还有2秒才醒来。正处于阻塞状态。
        Thread.sleep(2000);
        //设置中断标志位为true
        thread.interrupt();
    }
}
//最简单的中断线程的方式,通过一个boolean类型的变量实现中断
//问题:如果当前线程处于阻塞状态,那么不能立即中断该线程
//之后执行到循环的判断处,处于非阻塞状态的时候,才能中断
class FlagThread extends  Thread{
    private int i=0;
    private boolean flag=true;

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);   //每隔1000毫秒,输出一个i
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName()+"-->"+i++);
        }
    }
}
//使用线程中断标志位来中断被阻塞的线程
class InterruptThread extends Thread{
    private int i;

    @Override
    public void run() {
        //isInterrupted()默认为false
        while(!isInterrupted()){
            try {
                //当前对象调用了interrupt()方法,那么会抛出异常
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                //中断标志位被重置为了false
                //在此继续设置线程的中断标志位为true即可。
                interrupt();
            }

            System.out.println(getName()+"-->"+i++);
        }
    }
}

第四节线程安全

1.实现线程互斥的方式-同步代码块

synchronized (this){//希望被互斥访问的代码}
执行过程:
1.线程-1访问该代码块。给this 添加了一个锁标识,表示被某个线程占用。开始访问同步代码块中的代码。
2.线程-2也要访问该代码块,发现this已经被锁了。在this上等待。
3.线程-1执行完同步代码块中的代码,出同步代码块同时去掉this上的锁标识。继续执行后续的代码。
4.线程-2发现this已经不被占用了,那么就对this添加锁标识,进入代码块执行里面的代码。
说明:this 称为同步监视器对象。要求是一个对象,或者是一个对象的引用。如果想实现线程之间的互斥访问,那么同步监视器对象必须只有惟一的一个对象。

/**
 * 线程安全问题:
 * 两个人操作同一个账户上的钱
 * 张三和张三媳妇同时区取钱
 */
public class TestAccount {
    public static void main(String[] args) {
        AccountRunnable accountRunnable0=new AccountRunnable();
        Thread thread1=new Thread(accountRunnable0,"张三");
        Thread thread2=new Thread(accountRunnable0,"张三媳妇");

        thread1.start();
        thread2.start();


    }
}
//账户类
class Account{
    //账户余额
    private int money=1500;
    //取钱的方法,返回是否取钱成功
    public boolean withDraw(int money){
       if(this.money>=money){
           //问题扩大
           try {
               Thread.sleep(10);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           this.money-=money;
           System.out.println(Thread.currentThread().getName()+"取钱成功!余额还剩:"+this.money);
           return true;
       }
        System.out.println(Thread.currentThread().getName()+"取钱失败!余额还剩:"+this.money);
        return false;
    }
}
//任务类,去同一个账户取钱
class AccountRunnable implements  Runnable{
    Account account=new Account();
    @Override
    public void run() {
        account.withDraw(1000);
    }
}

/*
执行结果:
张三媳妇取钱成功!余额还剩:-500
张三取钱成功!余额还剩:-500
*/

//解决方案
//出现这样的结果是多个线程同时访问withDraw()方法里面的if分支的代码块,如果实现线程之间的互斥访问就可以避免出现上面的问题了。如何一个代码块实现线程访问的限制呢?给代码块加个锁。实现线程互斥的方式:
1.同步代码块
        //这个this叫监视器对象
		synchronized (this){
            if(this.money>=money){
                //问题扩大
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.money-=money;
                System.out.println(Thread.currentThread().getName()+"取钱成功!余额还剩:"+this.money);
                return true;
            }
        }
2. 同步方法。本质上也是同步代码块,只是同步代码块的大小和方法体一样大,存在隐式的监视器对象。  
 	//取钱的方法  返回是否取钱成功
public synchronized boolean withDraw(int money) {
    if (this.money >= money) {
        //问题扩大
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        this.money -= money;
        System.out.println(Thread.currentThread().getName() + "-->取钱成功!余额为:" + this.money);
        return true;
    }
    System.out.println(Thread.currentThread().getName() + "-->取钱失败!余额为:" + this.money);
    return false;
}  

每日练习:

1:使用两种线程方式(继承线程类+实现Runnable接口),模拟龟兔赛跑。通过Thread.sleep(time)控制兔子睡觉的时间。模拟即可,不用必须保证乌龟胜利。

2:使用两种线程方式(继承线程类+实现Runnable接口)模拟火车站售票系统。

3:使用第三种线程实现方式实现功能:得到10个[0-10]的随机数。随机数使用ArrayList存储。最后返回 装有 10个随机数的ArrayList对象即可。并最后打印 ArrayList 中的随机数。

/**
 *自己写的很菜(没有参考价值)
 * 使用两种线程方式(继承线程类+实现Runnable接口),模拟龟兔赛跑。
 * 通过Thread.sleep(time)控制兔子睡觉的时间。模拟即可,不用必须保证乌龟胜利。
 */
public class RabbitAndTortoise {
    public static void main(String[] args) {
        Rabbit rabbit=new Rabbit();
        rabbit.setName("小白兔");
        Tortoise tortoise=new Tortoise();
        tortoise.setName("小乌龟");

        tortoise.start();
        rabbit.start();
    }
}
//创建小白兔类
class Rabbit extends Thread{
    private int distance=10000;
    private int speed=50;

    @Override
    public void run() {
        while(distance>0){
            distance-=speed;
            //当小白兔跑到一半的时候,小白兔睡着了1秒
            if(distance==500){
                try {
                    Thread.sleep(1000);
                    System.out.println("兔子在睡觉");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(getName()+"距离终点"+distance);
        }
    }
}
//创建乌龟类
class Tortoise extends Thread{
    private int distance=10000;
    private int speed=2;

    @Override
    public void run() {
        while(distance>0){
            distance-=speed;
            System.out.println(getName()+"距离终点"+distance);
        }

    }

//老板写的很好(值得参考)
/龟兔赛跑
//使用两种线程方式(继承线程类+实现Runnable接口),
//模拟龟兔赛跑。通过Thread.sleep(time)控制兔子睡觉的时间。
//模拟即可,不用必须保证乌龟胜利。
public class TestTortoiseAndRabbit {
    public static void main(String[] args) {
        Thread rabbit  = new Thread(new MatchTask(1));
        rabbit.setName("兔子");
        Thread tortoise = new Thread(new MatchTask(0));
        tortoise.setName("乌龟");

        rabbit.start();
        tortoise.start();
    }
}


//乌龟一秒跑一米
//兔子一秒10米
class MatchTask implements Runnable{
    public MatchTask(int id) {
        this.id = id;
    }

    //id==0 代表是乌龟, id == 1 代表是兔子
    private int id;
    //赛道的总长度
    public static final int DISTANCE = 1000;
    //跑过的距离
    private int length;
    //在跑道上消耗的时间
    private int time;

    @Override
    public void run() {
        if(id == 0){
            while(true){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                length ++;
                System.out.println(Thread.currentThread().getName()+"已经跑了长度:"+length+"\t米");
                time ++;
                if(length >= DISTANCE){
                    System.out.println(Thread.currentThread().getName()+"跑完全程,总共耗时:"+time);
                    break;
                }
            }
        }else if(id == 1){
            while(true){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                time ++;
                if(Math.random() < 0.9){//90% 概率睡觉
                    System.out.println(Thread.currentThread().getName()+"在睡觉!");
                    continue;
                }
                length += 10;
                System.out.println(Thread.currentThread().getName()+"已经跑了长度:"+length+"\t米");
                if(length >= DISTANCE){
                    System.out.println(Thread.currentThread().getName()+"跑完全程,总共耗时:"+time);
                    break;
                }
            }
        }
    }
}
/**
 *火车票购票系统
 */
public class SellTicket {
    public static void main(String[] args) {
        Saler saler=new Saler();
        //创建两个线程,也就是两个售票员
        Thread thread1=new Thread(saler,"售票窗口1");
        Thread thread2=new Thread(saler,"售票窗口2");
        //开启线程
        thread1.start();
        thread2.start();
    }
}

//售票系统
class TicketSystem{
    private  int tickets=100;//售票系统中一共有100张票

    public int getTickets() {
        return tickets;
    }

    //定义一个卖票方法,卖票成功返回true,失败返回false
     boolean sellTickets(int tickets){
        //为了解决线程安全问题
         synchronized (this){

             if(this.tickets>=tickets){  //如果系统中剩余票数大于本次所要卖的票数
                 this.tickets-=tickets;//系统中剩余票数等于原有票数减去本次卖的票数
                 System.out.println("剩余票数为:"+this.tickets);
                 return true;
             }
         }
        System.out.println("剩余票数为:"+this.tickets);
        return false;
    }
}

//一个售票任务
class Saler implements Runnable{
    TicketSystem ts=new TicketSystem();//共同访问一个售票系统
    @Override
    public void run() {
        while(ts.getTickets()>0){
            ts.sellTickets(10);//每次卖10张票
        }
    }
}

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 *使用第三种线程实现方式实现功能:得到10个[0-10]的随机数。随机数使用ArrayList存储。
 * 最后返回 装有 10个随机数的ArrayList对象即可。并最后打印 ArrayList 中的随机数。
 */
public class RandomNum {
    public static void main(String[] args) throws Exception {
        //创建一个容器,存放随机数
        ArrayList<Integer> al=new ArrayList<>();
        //创建一个任务对象
        MyCallable myCallable=new MyCallable();
        //循环创建10个线程
        for (int i = 0; i <10 ; i++) {
            FutureTask<Integer> task=new FutureTask(myCallable);
            Thread thread=new Thread(task);
            thread.start();
            //得到这个随机数
            Integer value=task.get();
            //将随机数放入容器中
            al.add(value);
        }
        //遍历容器
       Iterator<Integer> it=al.iterator();
        while(it.hasNext()){
            System.out.print(it.next()+"\t");
        }
  }
}
//定义一个任务,这个任务可以产生一个随机数
class MyCallable implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        return (int)(Math.random()*11);
    }
}

(转)CPU时间分片、多线程、并发和并行

1、CPU时间分片、多线程?
如果线程数不多于CPU核心数,会把各个线程都分配一个核心,不需分片,而当线程数多于CPU核心数时才会分片。

2、并发和并行的区别
并发:当有多个线程在操作时,如果系统只有一个CPU,把CPU运行时间划分成若干个时间片,分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。这种方式我们称之为并发(Concurrent)。并发=间隔发生

并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。 并行=同时进行

区别:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。

并行是同时做多件事情。

并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。

根据底层是否有多处理器,并发与并行是可以等效的,这并不是两个互斥的概念。

举个我们开发中会遇到的例子,我们说资源请求并发数达到了1万。这里的意思是有1万个请求同时过来了。但是这里很明显不可能真正的同时去处理这1万个请求的吧!

如果这台机器的处理器有4个核心,不考虑超线程,那么我们认为同时会有4个线程在跑。也就是说,并发访问数是1万,而底层真实的并行处理的请求数是4。

如果并发数小一些只有4的话,又或者你的机器牛逼有1万个核心,那并发在这里和并行一个效果。

也就是说,并发可以是虚拟的同时执行,也可以是真的同时执行。而并行的意思是真的同时执行。

结论是:并行是我们物理时空观下的同时执行,而并发则是操作系统用线程这个模型抽象之后站在线程的视角上看到的“同时”执行。

(转) Java synchronized到底锁住的是什么?

https://www.cnblogs.com/LearnAndGet/p/9365752.html
使用环境:多线程java程序中。

作用:在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。

使用:synchronized锁住的是括号里的对象,而不是代码。对于非static的synchronized方法,锁的就是对象本身也就是this。

通过以下两个案例说明:

//包含了synchronized 方法的类
public class SynchronizedMethod {
    public synchronized void test()
    {
        System.out.println("线程开始..");
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("线程结束..");
    }
}

//测试类
public class MyThread extends Thread{
    public void run(){
        SynchronizedMethod clazz = new SynchronizedMethod();
        clazz.test();
    }
    
    public static void main(String[] args) {
        for(int i=0;i<3;i++)
        {
            Thread thread = new MyThread();
            thread.start();
        }
    }

}


执行结果:
线程开始..
线程开始..
线程开始..
线程结束..
线程结束..
线程结束..

分析上述执行结果可知:

当main方法执行时,分别创建了三个MyThread对象,而这三个对象又各自创建了独立的 SynchronizedMethred类,虽然使用了test方法使用了synchronized方法修饰,但是synchronized锁住的是三个独立的对象,因为三个对象各自分别执行了test方法。

因此,可以修改代码,让三个线程使用同一个SynchronizedMethod对象:

 1 //修改测试类代码如下,使用同一对象调用test方法
 2 
 3 public class MyThread extends Thread{
 4 
 5     /**
 6      * Author:LearnAndGet
 7      */
 8     private SynchronizedMethod sync;
 9     public MyThread(SynchronizedMethod sync)
10     {
11         this.sync = sync;
12     }
13     
14     public void run(){
15         sync.test();
16     }
17 
18     
19     public static void main(String[] args) {
20         SynchronizedMethod sync = new SynchronizedMethod();
21         for(int i=0;i<3;i++)
22         {
23             MyThread thread = new MyThread(sync);
24             thread.start();
25         }
26     }
27 }


运行结果:
线程开始..
线程结束..
线程开始..
线程结束..
线程开始..
线程结束..

分析上述结果:由于每次新线程启动,使用的同一对象sync,因此synchronized生效了。

当然,更常用的方法是:使用schronized锁住这个类对应的Class对象:

public class SynchronizedMethod {
    public void test()
    {
        //将代码块使用synchronized锁住
        synchronized(SynchronizedMethod.class)
        {
            System.out.println("线程开始..");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("线程结束..");
        }
    }
}

使用上述代码后,即使每次创建不同的SynchronizedMethod对象,执行其test方法时,因为synchronized锁住了SynchronizedMethod类对应的class对象,所以每次只能有一个SynchronizedMethod的对象获取锁,直到该锁获得释放,其他SynchronizedMethod对象都无法执行其方法。

上述代码中,通过使用 synchronized(SynchronizedMethod.class)实现了全局锁的效果

除此之外,使用 static synchronized一起修饰方法时,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值