Java高性能编程学习整理之多线程1

1. 什么是线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。

线程是独立调度和分派的基本单位。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。

一个进程可以有很多线程,每条线程并行执行不同的任务。

比如,我们运行的QQ程序就是一个进程,打开一个连天窗体,给好友发一个消息就是QQ进程中的一个线程在处理

 

2. 线程与进程的区别

 

根本区别:进程是操作系统资源分配的基本单位,而线程是任务调度和执行的基本单位

在操作系统中能同时运行多个进程(程序);而在同一个进程(程序)中有多个线程同时执行(同一个CPU,在每个时间片中只有一个线程执行,也就是说CPU通通一个时间点只能执行一个一个线程)

进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

 

3. 为什么用多线程

 

使用多线程就是为了充分利用CPU的资源,比如我们在QQ里面发送一个消息,首先我们要通过键盘输入发送的内容,CPU处理内容,发送给对方,整个过程中,CPU处理的时间很短,大部分的时间是你的输入和网络的开销,也就是说大部分的时间CPU就是在等待,那CPU的资源就会浪费掉。启用多线程就是为了一个线程不需要CPU的时候,让CPU去执行另一个线程的任务,让CPU尽可能的忙起来,压榨CPU资源,让CPU在一段时间内尽可能完成更多的任务。

 

4. 怎样创建多线程

 

实现多线程编程的方式有两种,一种是继承 Thread 类,另一种是实现 Runnable 接口。

4.1 继承 Thread 实现如下

public class MyTestThread extends Thread {
    @Override
    public void run()
    {
        System.out.println("我是测试线程");
    }

    public static void main(String[] args)
    {
        MyTestThread myTestThread = new MyTestThread();
        myTestThread.start();
    }
}

4.2 实现 Runnable 接口

    public static void main(String[] args)
    {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我是测试线程2");
            }
        }).start();
    }

4.3 线程池实现

public class ThreadPoolTest {
    public static void main(String[] args) throws Exception {
        ThreadPoolTest threadPoolTest = new ThreadPoolTest();
        threadPoolTest.threadPoolExecutorTest6();
    }
    private void threadPoolExecutorTest6() throws Exception {
        ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
        // 效果1: 提交后,2秒后开始第一次执行,之后每间隔1秒,固定执行一次(如果发现上次执行还未完毕,则等待完毕,完毕后立刻执行)。
        // 也就是说这个代码中是,3秒钟执行一次(计算方式:每次执行三秒,间隔时间1秒,执行结束后马上开始下一次执行,无需等待)
        threadPoolExecutor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务-1 被执行,现在时间:" + System.currentTimeMillis());
            }
        }, 2000, 1000, TimeUnit.MILLISECONDS);
    }
}

5. 线程状态

 

1. new: 尚未启动的线程状态

2  Runnable:可运行线程状态,等待CPU调度

3. Blocked:线程阻塞等待监视器锁定的线程状态。处于synchronize同步代码块或者方法中被阻塞。

4. Waiting: 等待线程的线程状态。下列不带超时的方法:object.wait 、Thread.join、LockSupport.park

5. Time Wait: 具体指定等待时间的等待线程的线程状态。下列带超时的方法:Thread.sleep,object.wait,Thread.join,LockSupport.parkNanos,LockSupport.ParkUntil

6. Terminated: 终止线程的线程状态。线程正常完成执行或者出现异常

public class ThreadTest3 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {// 将线程2移动到等待状态,1500后自动唤醒
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread2当前状态:" + Thread.currentThread().getState().toString());
                System.out.println("thread2 执行了");
            }
        });
        System.out.println("没调用start方法,thread2当前状态:" + thread2.getState().toString());
        thread2.start();
        System.out.println("调用start方法,thread2当前状态:" + thread2.getState().toString());
        Thread.sleep(200L); // 等待200毫秒,再看状态
        System.out.println("等待200毫秒,再看thread2当前状态:" + thread2.getState().toString());
        Thread.sleep(3000L); // 再等待3秒,让thread2执行完毕,再看状态
        System.out.println("等待3秒,再看thread2当前状态:" + thread2.getState().toString());
    }
}

6. 线程通信

 

1)文件共享

两个线程把数据读取和写入到同一个文件中(例如test.txt文件),比如一个一个线程把数据写入到文件中,另一个文件从文件中读取内容。

2)网络共享

网络共享方式比较多,比如同时访问redis缓存等等

3)共享变量

设置公共变量,两个线程可以同时对该变量修改。比如设置个静态变量 static int count;线程就可以直接对这个count值做修改

7. 线程终止

 

1. 使用interrupt(推荐使用)

如果目标线程在调用Object Class的wait()、wait(long,int)方法、join()、join(long,int)

或者sleep(long,int)方法时被阻塞,nameinterrupt会生效,该线程的中断状态将被清除,抛出interruptException异常

如果目标线程是被I/O或者NIO的Channel锁阻塞,那样,I/O操作会被中断或者返回特殊异常值。达到终止线程的目的。

如果以上条件都不满足,则会设置此线程的中断状态

实例:

2. 通过标记位来实现

代码逻辑中,增加一个判断,用来控制线程执行的中止。

 

8. 线程安全

线程安全, 是指变量或方法( 这些变量或方法是多线程共享的) 可以在多线程的环境下被安全有效的访问。由此可见,线程安全是在多线程中调用公共变量(或者本地变量逃逸,这个还不清楚)造成的,是多线程的线程同步问题,单线程不会引起线程安全问题。如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。 

通常实现方法就是加锁和线程封闭,使用局部变量

8.1 加锁同步

// 锁 方法(静态/非静态),代码块(对象/类)
public class ObjectSyncDemo1 {

    static Object temp = new Object();

    public void test1() { // 方法上面:锁的对象 是 类的一个实例
        synchronized (this) { // 类锁(class对象,静态方法),实例锁(this,普通方法)
            try {
                System.out.println(Thread.currentThread() + " 我开始执行");
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + " 我执行结束");
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -> {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}

8.2 线程封闭

多线程访问共享可变数据时,设计到线程间数据同步的问题。并不是所有时候,都要用到共享数据,所有线程封闭概念就提出来了。

数据都被封闭在各自的县城中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术成为线程封闭。

线程封闭具体的体现有:ThreadLocal、局部变量

threadLocal

threadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有自己独立的一个变量,竞争条件彻底消除了,在并发模式下是绝对安全的变量。

用法:ThreadLocal<T> var = new ThreadLocal<T>();

会自动在每个线程上创建一个T的副本,副本之间彼此独立,互补影响,可以ThreadLocal存储一些参数,以便在线程中多个方法中使用,用来代替方法传参的做法。

可以理解为:JVM维护了一个Map<Thread,T>,每个线程要用这个T的时候,用当前的线程区Map里面取(仅作为一个概念理解,并非实际如此)

public class Demo {
	/** threadLocal变量,每个线程都有一个副本,互不干扰 */
	public static ThreadLocal<String> value = new ThreadLocal<>();

	/**
	 * threadlocal测试
	 * 
	 * @throws Exception
	 */
	public void threadLocalTest() throws Exception {

		// threadlocal线程封闭示例
		value.set("这是主线程设置的123"); // 主线程设置值
		String v = value.get();
		System.out.println("线程1执行之前,主线程取到的值:" + v);

		new Thread(new Runnable() {
			@Override
			public void run() {
				String v = value.get();
				System.out.println("线程1取到的值:" + v);
				// 设置 threadLocal
				value.set("这是线程1设置的456");

				v = value.get();
				System.out.println("重新设置之后,线程1取到的值:" + v);
				System.out.println("线程1执行结束");
			}
		}).start();

		Thread.sleep(5000L); // 等待所有线程执行结束

		v = value.get();
		System.out.println("线程1执行之后,主线程取到的值:" + v);

	}

	public static void main(String[] args) throws Exception {
		new Demo().threadLocalTest();
	}
}

局部变量

局部变量的固有属性之一就是封闭在线程中。

他们位于执行线程的栈中,其他线程无法访问这个栈。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值