千山鸟飞绝,万径人踪灭 是一种境界

进程与线程

线程:要说线程,我们得先知道另一个该类,进程  线程要依赖于进程
进程:正在执行的程序,我们现在的电脑支持多进程
单核CPU 在某个时间点上,能执行几个进程,只能执行一个,我们的CPU会在多个进程间进行告诉的切换执行,你是感觉不出来的
多进程的意义:提高CPU的使用率
当你的进程开启之后,要执行很多任务,每一个任务,每一个要执行的任务, 我们就叫做线程
多线程的意义:提高程序的使用率
大家注意两个词汇的区别:并行和并发
前者是逻辑上同时发生,指在某一个时间段同时运行多个程序。
后者是物理上同时发生,指在某一个时间点同时运行多个程序。
注意:进程是拥有资源的基本单位,线程是CPU调度的基本单位
Java如何实现多线程?
        由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是呢 ? Java可以去调用C / C++ 写好的程序来实现多线程程序。由C / C++ 去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。

java 提供了一个类 Thread 通过这个类就可以实现多线程。Thred 线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
接下来可以分配并启动该子类的实例。

接下来就开一个线程出来:

创建一个MyThread继承Thread

public class MyThread extends Thread {
    //run() 是线程要执行的方法,方法里面的代码是让线程来执行的
    @Override
    public void run() {
        //就是需要线程来执行的代码
        //一般来说,一些耗时的操作,需要我们开启一个线程,在run()方法里面去操作
        //复制操作
        //模拟耗时操作
        for (int i = 0; i < 100; i++) {
            System.out.println(i);

        }
    }
}

开启线程:

public class ThreadTest {
    public static void main(String[] args) {
        //创建新执行线程有两种方法。
        //一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
        //接下来可以分配并启动该子类的实例
        //1.写一个类继承Thread类
        //2.重写Thread类中的run()方法
        //3.开启线程
        MyThread th = new MyThread();
        //如何开启线程
        //th.run(); //这个不是开启线程,这是你一个普通调用方法的操作,线程没有开启
        //怎么开启
       //public void start () 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
        th.start();//开启线程
        //th.start();重复开启线程会抛异常
        //可以再开启一个
        MyThread th2 = new MyThread();
        th2.start();
    }
}

注意:
进程是拥有资源的基本单位,线程是CPU调度的基本单位
多个线程具有随机性,都在抢占CPU的执行权(时间片),哪个线程抢到了CUP的执行权,CPU就会执行这个线程

 

public class MyThread extends Thread {
    private static int index = 100 ;
    @Override
    public void run() {

            try {
                while (index>=0){
                    Thread.sleep(50);//模拟网络延迟
                    String name = Thread.currentThread().getName();
                    System.out.println("窗口"+name+"电影票还剩下"+index--+"张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
public class Test {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        myThread.setName("一号");
        myThread1.setName("二号");
        myThread.start();
        myThread1.start();
    }
}

结果:

 会出现以上这种结果,出现-1。这是为什么么???

其实很容易理解,比如说现在一号窗口剩一张票了,网络延迟了一下,刚好二号窗口一看还有最后一张票就要出售,等二号窗口真正的出售的时候,其实一号窗口已经把最后一张票卖出了,所以二号窗口现在拿的是0票,所以等二号窗口真是出售的时候就是0票再卖一张,不就是-1票嘛。

所以并发运行就涉及到了线程安全问题,其实解决线程的问题,最基本的就是上锁,那么锁其实就是,假如我线程一现在执行上锁的这段代码的时候,其他线程就不能执行,就得等待,等线程一执行完,释放锁其他的就可以执行了。

那么我们对以上的代码加上锁,关键字(synchronized)。

只对MyTread类加锁

public class MyThread extends Thread {
    private static int index = 100 ;
    private static final Object obj = new Object() ;//给锁创建参数对象
    @Override
    public void run() {
            synchronized (obj){//上锁
                try {
                    while (index>=0){
                        Thread.sleep(50);//模拟网络延迟
                        String name = Thread.currentThread().getName();
                        System.out.println("窗口"+name+"电影票还剩下"+index--+"张");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

在这里,需要注意的是synchronized(对象),需要传个对象参数,切记,不能直接在括号里new一个对象,这样的话,相当于每次都创建一个新锁,就没用了。所以应该总共创建一把共享锁。

当然,创建锁并不是这一种,在JDK1.5之后,就提供了Lock锁。

把上面的synchronized锁我们换成Lock看一下。

public class MyThread extends Thread {
    private static int index = 100 ;
    private static final Lock lock = new ReentrantLock() ;//创建锁对象,同样只用一把锁
    @Override
    public void run() {
                lock.lock();//上锁
                try {
                    while (index>=0){
                        Thread.sleep(50);//模拟网络延迟
                        String name = Thread.currentThread().getName();
                        System.out.println("窗口"+name+"电影票还剩下"+index--+"张");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.lock();//释放锁

        }
    }

我们可以看得出,Lock锁,直接调用方法,进行加锁,释放锁。

死锁

        如果出现了同步嵌套,就容易产生死锁问题
        是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
        同步代码块的嵌套案例
        死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态

举例:    中国人和美国人一起吃饭
       
       中国人使用的筷子
       美国人使用的刀和叉

       中国人获取到了美国人的刀
       美国人获取到了中国人的一根筷子

那么,双方都不肯放手,就陷入一个僵局,都没得饭吃,死锁就是这样的。

先创建两个Object类型的锁

public abstract class ObjectUtils {
    public static final Object objA= new Object();
    public static final Object objB= new Object();
}

开启线程A,B

 

public class MyTest  {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread(true);
        MyThread myThread2 = new MyThread(false);
        myThread1.start();
        myThread2.start();
    }
}

继承Threa,重写的run方法

public class MyThread extends Thread {
    boolean b;
    public MyThread(boolean b) {
        this.b = b;
    }
    @Override
    public void run() {
        while (true) {
            if (b) {
                synchronized (ObjectUtils.objA) {
                    System.out.println("true,A进来了");
                    synchronized (ObjectUtils.objB) {
                        System.out.println("treu,B进来了");
                    }
                }
                synchronized (ObjectUtils.objB) {
                    System.out.println("false,B进来了");
                    synchronized (ObjectUtils.objA) {
                        System.out.println("false,A进来了");
                    }
                }

            }
        }

    }
}