一、进程与线程的概念

进程是内存中运行的一个应用程序,线程是进程中的一个执行单元。
一个程序可以有多个进程,一个进程可以有多个线程且至少有一个线程。

二、Java中创建线程的两种方式

  1. 定义Thread的子类,并重写该类的run方法
public class MyThread extends Thread{

   @Override
    public void run() {
        for (int i=0;i<20;i++){
            System.out.println("齐天大圣:"+i);
        }
    }
}
public class HelloThread {
    public static void main(String[] args) {
        // 创建子类实例,即创建了线程对象
        MyThread myThread = new MyThread();
        
        myThread.start();// 调用start()方法启动该线程

    }
}
  1. 定义Runnable接口的实现类,并重写该接口run()方法
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<10;i++){
        	// 输出内容:线程名称:i
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}
public class HelloThread {
    public static void main(String[] args) {
    	// 创建该接口实现类的实例
        MyRunnable mr = new MyRunnable();
		
		// 以此实例mr 作为Thread的target来创建Thread类的对象,
		// 注意!该对象tr才是真正的线程对象(其实这种创建方式也就实现了同一线程的资源共享!)
        Thread tr= new Thread(mr,"线程1");
        tr.start();

    }
}

三、两种创建线程的方式Thread和Runnable的区别

若一个类继承Thread,则不适合资源共享。若实现了Runnable,则很容易资源共享。(资源共享简单举例就是多个窗口卖100张票那个例子)
总结: 实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
    (其实,不管是继承Thread类还是实现Runnable接口来创建线程,最终都是通过Thread的对象API来控制线程的。)

四、线程安全

这里举最简单的卖票案例来说明
首先定义一个实现Runnable接口的类Ticket

public class Ticket implements Runnable{

    private int tickets=100;
	
	@Override
    public void run() {
        while (true){
            
                if (tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+" 正在卖第:"+tickets--+"张票");
                }
           
        }
    }
}

创建测试类,开始卖票

public class TestDemo {

    public static void main(String[] args) {
		// 创建线程任务对象
        Ticket ticket = new Ticket();
		
		// 创建3个窗口对象(Runnable的好处在此就体现出来了,3个线程可以同时共享这100张票,实现了资源共享)
        Thread t1 = new Thread(ticket,"窗口1");
        Thread t2 = new Thread(ticket,"窗口2");
        Thread t3 = new Thread(ticket,"窗口3");
		
		// 3个窗口开始卖票
        t1.start();
        t2.start();
        t3.start();

    }
}

输入结果:

Thread释放 java java的thread_Thread释放 java


此时出现了线程安全的问题,5出现2次,且票数出现了0和-1

这是因为当线程1在执行时,还没执行完,线程2或者3也执行开始,导致票数在不同的线程里出现了重复或者不存在。

解决方案:

  1. 利用java中提供的同步机制synchronized关键字来解决
public class Ticket implements Runnable{

    private int tickets=100;

    private  Object lock = new Object();
	
	@Override
    public void run() {
        while (true){
           
            synchronized (lock){ // lock 是一个锁对象,该对象可以是任意类型,但是多个对象要使用同一把锁才能起到效果
                if (tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+" 正在卖第:"+tickets--+"张票");
                }
            }

        }
    }
}

2.Lock锁:java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作, 同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

public class Ticket implements Runnable{

    private int tickets=100;

    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            lock.lock(); // 加同步锁
            
           if (tickets>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(Thread.currentThread().getName()+" 正在卖第:"+tickets--+"张票");
            }
          

            lock.unlock(); // 释放同步锁

        }
    }
}

五、 线程状态

Thread释放 java java的thread_创建线程_02


线程状态我们不需要非得去理解原理,只需简单知道线程在创建后会有这么几种状态即可。

Timed Waiting(计时等待),最常见的就是Thread.sleep(),sleep时间到期后线程会自然苏醒进入Runnable(可运行)状态。

Blocked(锁阻塞):和上面介绍的同步机制类似,线程1和线程2同时运行并使用了一把锁,当A拿到锁的时候B此时就进入Blocked状态,A释放锁后,B拿到锁B再进入Runnable状态。

Waiting(无限等待):简单来说就是一个线程A在无限期的等待另一个线程B执行唤醒线程A这一动作的状态。可通过下面代码进一步来理解此状态:

public class TestDemo2 {
    // 定义锁对象
    public static Object obj = new Object();

    public static void main(String[] args) {

            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        synchronized (obj){
                            try{
                                System.out.println(Thread.currentThread().getName()+"获取到锁对象,调用wait方法,进入waiting状态,释放锁对象");
                                obj.wait();
                                //obj.wait(5000); 等待5秒,5秒内若被唤醒就解除waiting状态,没有到5秒后自动解除waiting状态
                            }catch (InterruptedException e){
                              e.printStackTrace();
                            }

                            System.out.println(Thread.currentThread().getName()+"从waiting状态醒来,且获取到了锁对象,继续执行了");
                        }

                    }

                }
            },"线程A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                   try{
                            System.out.println(Thread.currentThread().getName()+"等待3秒钟");
                            Thread.sleep(3000);
                        }catch (InterruptedException e){
                            e.printStackTrace();
                   }

                   synchronized (obj){
                       System.out.println(Thread.currentThread().getName()+"获取到锁对象,调用notify方法,释放锁对象");
                       obj.notify();
                   }

                }

            }
        },"线程B").start();


    }
}

当多个线程协作时,比如A,B线程,如果A线程在Runnable(可运行)状态中调用了wait()方法那么A线程就进入 了Waiting(无限等待)状态,同时失去了同步锁。假如这个时候B线程获取到了同步锁,在运行状态中调用了 notify()方法,那么就会将无限等待的A线程唤醒。

注意是唤醒,如果获取到锁对象,那么A线程唤醒后就进入 Runnable(可运行)状态;如果没有获取锁对象,那么就进入到Blocked(锁阻塞状态)。

下图所示为几种线程状态的运行图解:

Thread释放 java java的thread_创建线程_03

六、线程池

为什么要有线程池这个概念呢?这是因为当服务器中并发的线程数量很多时,如果用传统的创建线程方法频繁的创建线程,会导致系统的效率大大降低,因为一个线程从创建到销毁都是需要时间的。

由此引入线程池,线程池就是一个可以容纳多个线程的容器,其中的线程可以反复使用,避免了频繁创建线程销毁线程的这些动作

线程池的工作原理,如下图所示:

Thread释放 java java的thread_System_04


合理运用线程池的好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

七、线程池的使用

Java里面线程池的顶级接口是 java.util.concurrent.Executor ,但是严格意义上讲 Executor 并不是一个线程 池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService 。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors 线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象

Thread释放 java java的thread_System_05