Java学习笔记16-----多线程(一)

第一部分:线程,进程的概念
1.代码执行的路径只有一条我们称之为单线程。
代码执行有多条路径执行的我们称之为多线程。
2.进程:线程要依赖于进程。进程就是正在执行的程序。
线程:当进程开启之后要执行很多任务,每一个要执行的任务我们称之为线程。
多进程的意义:提高CPU的利用率。
单核CPU在某个时间点上,只能执行一个进程。
多线程的意义:提高程序的使用率。
3.并行:是逻辑上的同时发生,某一个时间段内同时发生;
并发:是物理上的同时发生,某一个时间点同时发生。
4.进程是拥有资源的基本单位;线程是CUP调度的基本单位。
5.Java如何实现多线程?
因为线程是依赖于进程存在的,java要创建进程,需要依赖于系统功能创建,但是java 是不能直接调用系统功能的。但是C、C++是可以的,所以java去调用C、C++编写的类进而去创建进程。所以Java提供了一个类 Thread,通过这个类实现多线程。
6.创 建新执行线程有两种方法:
(一)、第一种方法:一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法;
步骤:(1).写一个类,继承Thread类,(2)重写Thread类中的run 方法,(3)开启线程start()方法。
注意事项:
1.Run方法里面的代码是线程要执行的代码。
2.一般来说,一些耗时的操作需要我们开启线程在run中操作。
3.重复开启线程会报错,再开启一个线程就要再new一个新的对象。
4.多个线程执行具有随机性,都在抢占CPU的执行权(时间片),哪个线程抢到了CPU的执行权,那CPU就执行谁。
代码实现:

public class MyThread1 extends Thread {
    //创建线程的第一种方法
    String name;
    @Override
    public void run(){
        //比较耗时的代码块(即线程要执行的代码块)
         name=this.getName();//获取线程的名字
        for(int i=0;i<100;i++){
            System.out.println(name+"----"+i);
        }
    }
}

public class MyThread1Test {
    public static void main(String[] args) {
        Thread th1 = new MyThread1();//创建线程对象
        Thread th2=new MyThread1();
        th2.start();
        th1.start();//启动线程
    }
}

Thread类中的一些方法:
(1)获取线程的名字:public final String getName();
(2)设置线程的名称:public final void setName(String name);
(3)获取当前线程:public static Thread curretThread();
(4)通过构造方法设置线程名字Thread(String name)。
代码实现:


public class MyThread2 extends Thread{
    String name;
    public MyThread2(){}
    public MyThread2(String name){
        this.name=name;
    }
    
    @Override
    public void run() {
        name=this.getName();
        for(int i=0;i<10;i++){
            System.out.println(name+"--"+i);
        }
    }
}

public class MyThread2Test {
    public static void main(String[] args) {
        Thread th1 = new MyThread2();//创建线程对象
        Thread th2=new MyThread2();
        Thread th3=new MyThread2("线程3");
        Thread thread=Thread.currentThread();
        String name=thread.getName();
        System.out.println(name);//main
        th1.setName("线程1");//给线程命名
        th2.setName("线程2");
        th2.start();
        th1.start();//启动线程
        th3.start();
    }
}

(5)CPU的调度模型:1.分时调度模型,即所有线程轮流使用CPU,平均分配CPU给线程。2.抢占式调度模型有优先级,优先级高的线程获取的时间片相对较多。
Java采用的是抢占式调度模式:
如何设置优先级:public final void setPriority(int newPriority);
获取线程优先级:public final int getPriority();线程默认优先级是5;
最高优先级MAX_PRIORITY是10
最低优先级MIN_PRIORITY是1
设置优先级时,最好是不要直接写数字,要定义一个常量,方便更改;
注意:有时候给线程设置了优先级,但是执行时并不一定是按设置好的优先级进行的,优先级高仅仅是代表这个线程被CPU执行的概率增大了,多线程执行具有随机性,所以最好多运行几次看结果;
(6)线程休眠:public static void sleep(long millis);millis为休眠的毫秒值;
(7)线程加入:该线程执行完毕其他线程才能执行。
join()方法;public final void join();
注意:要在线程启动之后才调用join()方法设置为加入线程。
代码演示:

public class MyThreadTest3 {
    public static void main(String[] args) throws InterruptedException{
        MyThread1 th1= new MyThread1();
        MyThread1 th2=new MyThread1();
        MyThread1 th3=new MyThread1();
        th1.setName("线程1");
        th2.setName("线程2");
        th3.setName("线程3");
        int prio=th1.getPriority();
        System.out.println(prio);//默认优先级为5
        th1.setPriority(Thread.MAX_PRIORITY);//设置成最大优先级10
        th2.setPriority(Thread.MIN_PRIORITY);//设置成最小优先级1
        th3.start();//启动线程3
        th3.join();//把线程3设置为加入线程,即线程3执行完毕才执行其他线程。
        th1.start();
        th2.start();
    }
}

(8)线程礼让:public static void yield();暂停当前正在执行的线程,并执行其它的线程。
理想中就是你执行一下,我执行一 下,交替执行,但是效果不明显。因为暂停的时间比较短暂,之后又开始抢占CPU的执行权,那么这个线程将和其它线程一起抢占,所以有可能接下来还是这个线程执行。
代码演示:

public class ThreadTest4 {
    public static void main(String[] args) {
        MyThread1 th1 = new MyThread1();
        MyThread1 th2 = new MyThread1();
        th1.setName("线程1");
        th2.setName("线程2");
        Thread.yield();//线程礼让
        th1.start();
        th2.start();

    }
}

(9)守护线程:public final void setDaemon(boolean on);所谓的守护线程就是当所守护的线程执行完毕之后,守护线程本身就死掉。就好比象棋一样,守护的将死了,则全盘就结束了,小兵随之全死。如下例子:张飞关羽设置成守护线程,守护刘备,为 了效果更明显,把刘备设置成加入线程,刘备执行完后则程序全部结束,张飞关羽线程并没有执行则随刘备的结束而结束。
程序代码:

public class MyThreadTest5 {
    public static void main(String[] args) {
        MyThread1 th1 = new MyThread1();
        MyThread1 th2 = new MyThread1();
        MyThread1 th3 = new MyThread1();
        th1.setName("张飞");
        th2.setName("关羽");
        th3.setName("刘备");
        th1.setDaemon(true);
        th2.setDaemon(true);
        th3.start();
        try {
            th3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        th1.start();
        th2.start();
    }
}

(10)线程中断停止运行:public final void stop();
打断线程的阻塞状态 public void interrupt();
线程休眠属于线程的阻塞状态,sleep(long time),wait()方法是阻塞状态;
代码演示:

public class MyThreadTest6 {
    public static void main(String[] args) throws InterruptedException {
        MyThread1 th1 = new MyThread1();
        th1.setName("线程1");
        th1.start();
       // th1.stop();//直接停止运行
        th1.sleep(5000);
        th1.interrupt();//打破睡眠的5秒钟,直接运行。
    }
}

(二)、创建新执行线程有的第二种方法:
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
这种方式比较灵活,实现一个接口的同时还可以继承其他的类。所以可多采用第二种方法。
代码实现:

public class MyRunnable implements Runnable{
    String name;
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            name=Thread.currentThread().getName();//采用当前线程获得名字
            System.out.println(name+"---"+i);
        }
    }
}

public class MyRunnableTest1 {
    public static void main(String[] args) {
        MyRunnable myRunnable1= new MyRunnable();
        MyRunnable myRunnable2=new MyRunnable();
        Thread th1 = new Thread(myRunnable1);
        Thread th2=new Thread(myRunnable2);
        th1.setName("线程1");
        th2.setName("线程2");
        th1.start();
        th2.start();
    }
}

第二部分:线程安全方面知识
1.案例演示:
需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。通过继承Thread类实现。
案例分析:3个售票窗口售票是同时进行的,故需要三个线程来控制三个窗口,三个窗口一共有100张票,即100为共享数据。程序代码如下:

public class MyThread1 extends Thread {
   private String name;
    public MyThread1(String name){
        this.name=name;
    }
     private  static int tickets=100;//定义成共享数据
    @Override
    public void run() {
        while(true){
            if(tickets>0){
                System.out.println(this.name+"正在售票第"+tickets--+"张票");
            }
            else{
                break;
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        MyThread1 th1 = new MyThread1("窗口1");
        MyThread1 th2 = new MyThread1("窗口2");
        MyThread1 th3 = new MyThread1("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

案例需求:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。通过实现Runnable接口实现。代码如下:

public class MyRunnable implements Runnable {
    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    private static int tickets = 100;//定义成共享数据

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
            } else {
                break;
            }
        }

    }
}

public class MyRunnableTest {
    public static void main(String[] args) {
       MyRunnable myRunnable1=new MyRunnable();
        MyRunnable myRunnable2=new MyRunnable();
        MyRunnable myRunnable3=new MyRunnable();
        Thread th1=new Thread(myRunnable1);
        Thread th2=new Thread(myRunnable2);
        Thread th3=new Thread(myRunnable3);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

但是在实际问题中所售两张票之间有延迟,在上述代码中加入延迟时间500毫秒,则会有相同的票数出现,或者零票负票出现,这些均不合情理,即出现了数据安全的问题。
2.出现数据安全问题的三个条件:(1).多线程环境(2).要有共享数据(3)有多条语句在操作共享数据。
问题:(1).会有相同数据出现的原因是因为原子性导致,CPU的一次执行是个原子性操作。(2).会有零票和负数票出现的原因是线程的随机性导致的。
3.解决数据安全问题:基本思想是让程序没有安全问题的环境。即把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行。可以采用同步代码块,将可能出现问题的代码包括起来。
同步代码块为:synchronized(锁对象){代码块}
锁对象:就是任意的一个对象,这个对象相当于一把锁,哪个线程先进来,就先持有这把锁,锁住,其他线程进不来,直到这个线程执行完,其它线程才进来。
注意:多个线程要共用一个锁对象,即需要把这个锁对象定义成静态成员变量,才能被所有线程共享。否则仍然无法锁住。同步代码块的锁对象属于互斥锁(所谓互斥锁,就是一次最多只能有一个线程持有这把锁)。
同步代码块的缺点:使用同步代码块耗费资源,效率不高。
加同步代码块的售票程序代码入下:

public class MyRunnable implements Runnable {
    private String name;

    private static int tickets = 100;//定义成共享数据
    private  static final Object obj=new Object();//定义共享锁对象
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
                }
            }
        }
    }
}

4.(1).同步代码块:synchronized(锁对象){代码块};
锁对象:是任意对象;
(2)同步方法: public synchronized void method();
锁对象:是this;
(3)静态同步方法: public static synchronized void method()
锁对象:当前类对应的字节码文件对象。例如:MyRunnable.class
代码如下:

public class MyRunnable1 implements Runnable{
    private String name;

    private static int tickets = 100;//定义成共享数据
    int n=0;
    private  static final Object obj=new Object();//定义共享锁对象
    @Override
    public void run() {
        while (true) {
            if(n%2==0){
                synchronized (MyRunnable1.class) {//如果else中调用的是方法,改为this
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
                    }
                }
            } else{
                sellTickets();
            }
            n++;
        }

    }

    private static synchronized void sellTickets() {
        synchronized (obj){
            if (tickets > 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在售票第" + tickets-- + "张票");
            }
        }
    }
}
5.Lock接口
public interface Lock
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
void lock()获取锁; void unlock()释放锁。
ReentrantLock是Lock的一个子类,一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
代码如下:
public class MyLock extends Thread{
    String name;
    private static Lock lock=new ReentrantLock();
    private static int tickets=100;
    public MyLock(String name){
    this.name=name;
}
    @Override
    public void run() {
        while(true){
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.name + "正在售票第" + tickets-- + "张票");
            }
            lock.unlock();
        }
    }
}

6死锁现象:多个线程,互相持有对方的锁,而不释放,造成线程处于等待的现象。如果同步代码块有嵌套现象,有可能出现死锁现象。
举例: 中国人和美国人一起吃饭,中国人使用的筷子,美国人使用的刀和叉 ,中国人获取到了美国人的刀,美国人获取到了中国人的一根筷子。则两个人都无法吃饭,处在等待的状态。
代码如下:线程1得到锁对象objA,线程2得到锁对象objB,但是线程1要想继续执行下去,必须再次得到锁对象objB,而线程2要想继续执行下去必须得到锁对象objA,但是由于程序没有执行完,线程1不能释放锁objA,线程2不能释放锁objB,互相僵持,就是所谓的死锁现象。

public class SiSuoThread extends Thread {
    public static final Object objA = new Object();
    public static final Object objB = new Object();
    boolean b;
    public SiSuoThread(boolean b) {
        this.b = b;
    }
    @Override
    public void run() {
        if (b) {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objA) {
                System.out.println("对象ObjA,true进来了");
                synchronized (objB) {
                    System.out.println("对象ObjB,true进来了");
                }
            }
        } else {
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objB) {
                System.out.println("对象ObjB,false进来了");
                synchronized (objA) {
                    System.out.println("对象ObjA,false进来了");
                }
            }
        }
    }
}

public class SiSuoDemoTest {
    public static void main(String[] args) {
        SiSuoThread th1 = new SiSuoThread(true);
        SiSuoThread th2 = new SiSuoThread(false);
        th1.start();
        th2.start();
    }
}

7.不同种类线程之间的通信(等待唤醒机制)。
Object类中提供了三个方法: wait();等待
notify()唤醒单个线程
notifyAll()唤醒所有线程
等待唤醒机制的作用:就是生产一个,消费一个,交换输出。不是生产出一大堆,再去消费。案例解析:资源类:Student
设置学生数据:SetThread(生产者)
获取学生数据:GetThread(消费者)
测试类:StudentDemo
生产线程:如果没有资源,我就生产,有了资源我就等待,通知消费线程去消费掉,然后消费线程消费 完,我再生产;
消费线程:如果有了资源我就消费,没有资源通知生产线程生产资源。
代码如下:

//获取资源
ublic class GetThread extends Thread{
    Student student;
    public GetThread(Student student){
        this.student=student;
    }

    @Override
    public void run() {
        //资源不存在,等待
        while(true){
            synchronized (student){
                if(!student.flag){
                    try {
                        student.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(student.name+"----"+student.age);
                student.flag=false;
                student.notify();
            }
        }
    }
}
//生产线程
public class SetThread extends Thread{
    Student student;
    public SetThread(Student student){
        this.student=student;
    }//有参构造保证传的参数是一致的
    int i=0;
    @Override
    public void run(){
        while(true){
            synchronized (student){
                //判断资源是否存在
                if(student.flag){//存在,等待
                    try {
                        student.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //不存在,生产资源
                if(i%2==0){
                    student.name="张三";
                    student.age=23;
                }else{
                    student.name="李四";
                    student.age=24;
                }
                //更改标记
                student.flag=true;
                student.notify();
                i++;
            }
        }
    }

}public class Student {
    public String name;
   public int age;
     boolean flag;//标记有无资源,true有资源,false无资源
}

public class StudentDemo {
    public static void main(String[] args) {
     Student student=new Student();
     SetThread setThread=new SetThread(student);
     GetThread getThread=new GetThread(student);
     setThread.start();
     getThread.start();
    }
}

Sleep()与wait()方法区别:
相同点:这两个方 法都能使线程处于阻塞状态;
区别:1.sleep()方法必须要一个时间;Wait()方法可以要时间,也可以不要时间;
2. .sleep()方法休眠后不释放锁;Wait()方法一旦等待就要立马释放锁,下次醒来也是从这里醒来。
(三)作业:(1)复制文件,每个线程复制一个文件,2个线程分别复制两个文件。文件1:初恋未满.mp3;文件2:大悲咒.mp3。

public class MyThread1 extends Thread{
    String srcpath="初恋未满.mp3";
    String aimpath="初恋未满copy.mp3";
    FileInputStream in;
    FileOutputStream out;
    @Override
    public void run() {
        try {
            in=new FileInputStream(srcpath);
            out=new FileOutputStream(aimpath);
            int len=0;
            byte[] bytes=new byte[1024];
            while ((len=in.read(bytes))!=-1) {
                out.write(bytes,0,len);
                out.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                out.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
public class MyThread1 extends Thread{
    String srcpath="初恋未满.mp3";
    String aimpath="初恋未满copy.mp3";
    FileInputStream in;
    FileOutputStream out;
    @Override
    public void run() {
        try {
            in=new FileInputStream(srcpath);
            out=new FileOutputStream(aimpath);
            int len=0;
            byte[] bytes=new byte[1024];
            while ((len=in.read(bytes))!=-1) {
                out.write(bytes,0,len);
                out.flush();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            try {
                out.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        MyThread1 th2 = new MyThread1();
        th1.start();
        th2.start();
    }
}

(2)3个 线程复制 同一个文件。
public class MyThread2 extends Thread {
    long startpoint;
    long endpoint;
    RandomAccessFile srcfile;
    RandomAccessFile aimfile;

    public MyThread2(long startpoint, long endpoint, String srcpath, String aimpath) throws FileNotFoundException {
        this.startpoint=startpoint;
        this.endpoint=endpoint;
        this.srcfile=new RandomAccessFile(srcpath,"rw");
        this.aimfile=new RandomAccessFile(aimpath,"rw");
    }

    @Override
    public void run() {

        try {
            aimfile.seek(startpoint);//确定文件开始指针
            srcfile.seek(startpoint);
            int len=0;
            byte[] bytes=new byte[10];
            while(startpoint<endpoint &&(len = srcfile.read(bytes))!=-1){
                startpoint+=len;
                aimfile.write(bytes,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                srcfile.close();
                aimfile.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }
}


public class MyTest2 {
    public static void main(String[] args) throws FileNotFoundException {
        String srcpath = "大悲咒.mp3";
        String aimpath = "大悲咒copy2.mp3";
        int threadNum=3;
        File file = new File(srcpath);
        long length = file.length();
        long averagelen = length / threadNum;
        for (int i = 0; i < threadNum; i++) {
            long startpoint = i * averagelen;
            long endpoint = (i + 1) * averagelen;
            MyThread2 th=new MyThread2(startpoint,endpoint,srcpath,aimpath);
            th.start();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值