JavaSE——多线程(进程和线程概念,多线程安全问题,同步代码块,同步方法,Lock锁)

线程和进程的概念

  1. 进程
  • 概念:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有他自己的内存空间和系统资源
  • 多进程的意义:单进程计算机只能做一件事情,而我们现在的计算机都可以一边玩游戏,一边听音乐,我们常见的操作系统都是多进程操作系统,例如Windows,Linux等等
  • 思考:对于单核计算机来说,游戏进程和音乐进程是同时进行吗?当然不是,因为CPU在某个时间点上只能做一件事情,计算机是在游戏进程和音乐进程间做着频繁切换,且切换速度很快,所以我们感觉游戏和音乐在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率
  1. 线程
  • 概念:每个进程内部又可以执行多个任务,而这每个任务我们就可以看成是一个线程,是程序使用CPU的基本单位。所以进程是拥有资源的基本单位,线程是CPU调度的基本单位

  • 多线程的意义:多线程不是提高应用程序的执行速度,而是为了提高应用程序的使用率

    我们程序在运行过程当中,都是在强CPU的时间片(执行权),如果是多线程的程序,那么在强到CPU的执行权的概率应该比单线程程序强到的概率要大,那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率。但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机

并行和并发的区别

吃饭吃到一半,电话来了,一直等吃完了以后才去接,这就说明不支持并发也不支持并行。
吃饭吃到一半,电话来了,停下来接电话,接完之后继续吃饭,说明支持并发(不一定是同时的)
吃饭吃到一半,电话来了,一边打电话一边吃饭,说明支持并行

  1. 并发
    指应用能够交替执行不同的任务
  2. 并行
    指应用能够同时执行不同的任务

多线程程序实现

由于线程是依赖进程而存在,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。但是java是不能直接调用系统功能的,所以我们没有办法直接实现多线程程序,但是java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由java去调用这样的东西,然后提供一些类供我们使用。我恩就可以实现多线程程序了

  1. 方法一:继承Thread类
    例如:
package com.westmo3.demo3;
public class MyDemo1 {
    public static void main(String[] args) {
        System.out.println("主线程开始执行");
        MyThread thread = new MyThread();
        thread.start();//启动子线程
        System.out.println("主线程执行完毕");
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("子线程执行了");
    }
}

Thread类中基本方法的使用

public final String getName():获取线程名称
public final void setName(String name):设置线程名称
public static Thread currentThread():获取当前执行的线程
public final int getPriority() :获取线程的优先级(线程的默认优先级是5)
public final void setPriority(int newPriority):设置线程的优先级
public static void sleep(long millis) :线程休眠
public final void join() 意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行,注意事项: 在线程启动之后,在调用方法
public static void yield():	暂停当前正在执行的线程对象,并执行其他线程。
public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。 
public final void stop():停止线程的运行
public void interrupt():中断线程(这个翻译不太好),查看API可得当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞

案例演示

package com.westmo3.demo3;
public class MyDemo1 {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程开始执行");
        MyThread thread = new MyThread();
        thread.start();//启动子线程
        thread.sleep(2000);//让线程睡眠2s

        MyThread myThread = new MyThread("1号线程");//利用有参构造该线程起个名字
        MyThread myThread1 = new MyThread("2号线程");
        MyThread myThread2 = new MyThread("3号线程");
        myThread.start();
        myThread.join();//可以让多个线程顺序执行
        myThread1.start();
        myThread1.join();
        myThread2.start();
        myThread2.join();
        System.out.println("主线程执行完毕");
    }
}
class MyThread extends Thread{
    public MyThread() {
    }
    public MyThread(String s) {
        super(s);
    }
    @Override
    public void run() {
        //获取当前线程的线程名
        //System.out.println(this.getName()+"-子线程执行了");
        System.out.println(Thread.currentThread().getName()+"-子线程执行了");
    }
}

  1. 方法二:实现Runnable接口
package com.westmo3.demo3;
public class MyDemo2 {
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
        Thread thread1 = new Thread(myThread1);
        thread1.start();
    }
}
class MyThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"="+i);
        }
    }
}

  1. 方式三:实现Callable接口,相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
package com.westmo3.demo3;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyDemo3 {
    public static void main(String[] args) {
        thread thread = new thread();
        FutureTask<Integer> task = new FutureTask<Integer>(thread);
        Thread thread1 = new Thread(task);
        thread1.start();
    }
}
class thread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"="+i);
        }
        return 200;
    }
}

案例分析引出线程安全问题

案例:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票

分析:根据题目我们知道,这三个窗口买票是同时进行的,我们可以用三个线程来表示这三个窗口

package com.westmo3.demo3;
public class MyDemo4 {
    public static void main(String[] args) {
        mythread th1 = new mythread();
        mythread th2 = new mythread();
        mythread th3 = new mythread();
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class mythread extends Thread{
    static int piao=100;//设置为共享变量,因为三个线程共同卖这100张
    @Override
    public void run() {
        while(true){
            if (piao>0) {
                try {
                    Thread.sleep(10);//用线程睡眠来演示网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(this.getName()+"正在出售"+(piao--)+"票");
            }
        }
    }
}

从代码运行可以看出,出现了同票,0票甚至负票的情况,这些问题其实就是多线程环境下的数据安全问题。当其中一个线程抢得执行权执行时,当这个线程还没有执行完时,另一个线程又强到了执行权,这就是问题发生的本质。

解决方法:把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

  1. 同步代码块方式
  • 格式:
synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
		死循环
		需要同步的代码;
	}
  • 弊端:它虽然解决了多线程的安全问题,但当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
  • 案例演示
package com.westmo3.demo3;
public class MyDemo {
    public static void main(String[] args) {
        mythread1 th1 = new mythread1();
        mythread1 th2 = new mythread1();
        mythread1 th3 = new mythread1();
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class mythread1 extends Thread{
    static int piao=100;//设置为共享变量,因为三个线程共同卖这100张
    static Object obj=new Object();//三个线程的锁对象必须是同一个
    @Override
    public void run() {
        while(true) {
            synchronized (obj) {//线程进入同步代码块,就持有了锁,那么 其他两个线程,就在同步代码块外面等着
                if (piao > 0) {
                    try {
                        Thread.sleep(20);//用线程睡眠来演示网络延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(this.getName() + "正在出售" + (piao--) + "票");
                }
            }
           // 这个线程出了同步代码块,就 会把锁对象释放掉,这时候三个线程再次争抢时间片
        }
    }
}

  1. 同步方法:就是把关键字加到方法上

同步方法的锁对象:是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象

package com.westmo3.demo3;
public class MydDemo5 {
    public static void main(String[] args) {
        Mythread mythread = new Mythread();
        Thread th1 = new Thread(mythread);
        Thread th2 = new Thread(mythread);
        Thread th3 = new Thread(mythread);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class Mythread implements Runnable{
static int piao=100;
int i=1;
    @Override
    public void run() {
        while(true) {
            if(i%2==0) {
                Buypiao();//同步方法
            }else {
                Buypiao1();//静态同步方法
            }
        }
    }
    public synchronized void Buypiao1() {//同步方法的锁对象是this
            if (piao>0) {
                try {
                    Thread.sleep(20);//用线程睡眠来演示网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
            }
    }
    public static synchronized void Buypiao() {//静态同步方法的锁对象是当前类的字节码对象
        if (piao>0) {
            try {
                Thread.sleep(20);//用线程睡眠来演示网络延迟
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
        }
    }
}

  1. Java中锁的知识
  • java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果线程B不释放这个锁,那么线程A将永远等待下去
  • java对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类和对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在,它只是用来用来帮助我们理解锁定实例方法和静态方法的区别

JDK1.5之后的Lock锁的使用

  1. 概述
    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对线Lock
  2. 使用:void lock()加锁,void unlock()释放锁
  3. 案例
package com.westmo3.demo3;

import java.util.concurrent.locks.ReentrantLock;

public class MyDemo5 {
    public static void main(String[] args) {
        MyThrad1 myThrad1 = new MyThrad1();
        Thread th1 = new Thread(myThrad1);
        Thread th2 = new Thread(myThrad1);
        Thread th3 = new Thread(myThrad1);
        th1.setName("窗口1");
        th2.setName("窗口2");
        th3.setName("窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}
class MyThrad1 implements Runnable{
static int piao=100;
static ReentrantLock reentrantLock=new ReentrantLock();
    @Override
    public void run() {
        while(true){
            reentrantLock.lock();
            if(piao>0){
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售"+(piao--)+"票");
            }
            reentrantLock.unlock();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值