线程技术点:
-
线程的启动
-
如何使线程暂停
-
如何使线程停止
-
线程的优先级
-
线程安全相关问题
进程和线程的概念及多线程的优点
进程:比如我们电脑运行的 QQ.exe 程序,是操作系统管理的基本运行单元
线程:在进程中独立运行的子任务,比如 QQ.exe 进程中就有很多线程在运行,下载文件线程、发送消息线程、语音线程、视频线程等。
多线程优点:我们电脑可以同时操作不同的软件,边听着歌,敲着代码,查看 pdf 文档,浏览网页等,CPU 在这些任务之间不停的切换,切换非常快,所以我们就觉得他们是在同时运行的。
使用多线程
继承 THREAD 类
JDK 源码注释(Thread.java)如下:
One is to declare a class to be a subclass(子类) of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primeslarger than a stated value could be written as follows: //继承 Thread 类 class PrimeThread extends Thread { long minPrime; PrimeThread(long minPrime) { this.minPrime = minPrime; }//重写 Thread 类的 run 方法public void run() { |
实现 RUNNABLE 接口
JDK 源码注释(Thread.java)如下:
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following: //实现 Runnable 接口 class PrimeRun implements Runnable { long minPrime; PrimeRun(long minPrime) { this.minPrime = minPrime; }//重写 run 方法public void run() { |
currentThread() 方法
该方法返回代码段正在被哪个线程调用的信息。
isAlive() 方法,判断当前线程是否处于活动状态(已经启动但未终止)
sleep() 方法,在制定的毫秒数内让当前“正在执行的线程(this.currentThread() 返回的线程)”休眠(暂停执行)。
getId() 方法,获取线程的唯一标识
停止线程,可以使用 Thread.stop() 方法,但最好不要用,因为这个方法是不安全的,已经弃用作废了。
大多数停止一个线程是使用 Thread.interrupt() 方法
判断线程是否是停止状态
-
interrupted()
//测试当前线程是否已经中断了,这个线程的中断状态会被这个方法清除。 //换句话说,如果连续两次调用了这个方法,第二次调用的时候将会返回 false public static boolean interrupted() { return currentThread().isInterrupted(true); }
-
isInterrupted()
//测试线程是否已经中断了,线程的状态不会受这个方法的影响 //线程中断被忽略,因为线程处于中断下不处于活动状态的线程由此返回false的方法反映出来 public boolean isInterrupted() { return isInterrupted(false); } /*** Tests if some Thread has been interrupted. The interrupted state* is reset or not based on the value of ClearInterrupted that is* passed. */private native boolean isInterrupted(boolean ClearInterrupted);
在沉睡中停止
public class MyThread2 extends Thread{ @Overridepublic void run() { try{ System.out.println("run start"); Thread.sleep(20000); System.out.println("run end"); } catch (InterruptedException e) { System.out.println("run catch "+this.isInterrupted()); e.printStackTrace(); } } public static void main(String[] args) { try { MyThread2 t2 = new MyThread2(); t2.start(); Thread.sleep(200); t2.interrupt(); } catch (InterruptedException e) { System.out.println("main catch");e.printStackTrace(); } System.out.println("main end"); } } |
运行结果:
run startmain endrun catch falsejava.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.zhisheng.thread.thread1.MyThread2.run(MyThread2.java:12) |
从运行结果来看,如果在 sleep 状态下停止某一线程,会进入 catch 语句,并清除停止状态值,使之变成 false。
在停止中沉睡
public class MyThread3 extends Thread{ @Overridepublic void run() { try { System.out.println("run start"); Thread.sleep(20000); System.out.println("run end"); } catch (InterruptedException e) { System.out.println("run catch "+this.isInterrupted()); e.printStackTrace(); } } public static void main(String[] args) { MyThread3 t3 = new MyThread3(); t3.start(); t3.interrupt(); } } |
运行结果:
run startrun catch falsejava.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.zhisheng.thread.thread1.MyThread3.run(MyThread3.java:12) |
能停止的线程 —— 暴力停止,使用 stop() 方法停止线程
暂停线程
可使用 suspend 方法暂停线程,使用 resume() 方法恢复线程的执行。
SUSPEND 和 RESUME 方法的使用
public class MyThread4 extends Thread{ private int i; public int getI() { return i; } public void setI(int i) { this.i = i; } @Overridepublic void run() { while (true) {i++;} } public static void main(String[] args) throws InterruptedException { MyThread4 t4 = new MyThread4(); t4.start(); System.out.println("A----- " + System.currentTimeMillis() + " ---- " + t4.getI());Thread.sleep(2000);System.out.println("A----- " + System.currentTimeMillis() + " ---- " +t4.getI()); t4.suspend(); Thread.sleep(2000); t4.resume(); System.out.println("B----- " + System.currentTimeMillis() + " ---- " + t4.getI());Thread.sleep(2000);System.out.println("B----- " + System.currentTimeMillis() + " ---- " + t4.getI());}} |
从运行结果来看,线程的确能够暂停和恢复。
但是 suspend 和 resume 方法的缺点就是:不同步,因为线程的暂停导致数据的不同步。
yield 方法
/*** A hint to the scheduler that the current thread is willing to yield* its current use of a processor. The scheduler is free to ignore this* hint.** <p> Yield is a heuristic attempt to improve relative progression* between threads that would otherwise over-utilise a CPU. Its use* should be combined with detailed profiling and benchmarking to* ensure that it actually has the desired effect.** <p> It is rarely appropriate to use this method. It may be useful* for debugging or testing purposes, where it may help to reproduce* bugs due to race conditions. It may also be useful when designing* concurrency control constructs such as the ones in the* {@link java.util.concurrent.locks} package.*/ //暂停当前正在执行的线程对象,并执行其他线程。暂停的时间不确定。 public static native void yield(); |
public class MyThread5 extends Thread{ @Overridepublic void run() { double start = System.currentTimeMillis(); for (int i = 0; i < 200000; i++) { //yield(); //暂停的时间不确定 i++; } double end = System.currentTimeMillis(); System.out.println("time is "+(end - start));} public static void main(String[] args) { MyThread5 t5 = new MyThread5(); t5.start(); } } |
线程的优先级
设置优先级的方法:setPriority() 方法
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } } |
不一定优先级高的线程就先执行。
守护线程
当进程中不存在非守护线程了,则守护线程自动销毁。垃圾回收线程就是典型的守护线程,当进程中没有非守护线程了,则垃圾回收线程也就没有存在的必要了,自动销毁。
/*** Marks this thread as either a {@linkplain #isDaemon daemon} thread* or a user thread. The Java Virtual Machine exits when the only* threads running are all daemon threads.** <p> This method must be invoked before the thread is started.** @param on* if {@code true}, marks this thread as a daemon thread* @throws IllegalThreadStateException* if this thread is {@linkplain #isAlive alive}* @throws SecurityException* if {@link #checkAccess} determines that the current* thread cannot modify this thread*/public final void setDaemon(boolean on) {checkAccess();if (isAlive()) {throw new IllegalThreadStateException();}daemon = on;} |
第二章 —— 对象及变量的并发访问
技术点:
-
synchronized 对象监视器为 Object 时的使用
-
synchronized 对象监视器为 Class 时的使用
-
非线程安全是如何出现的
-
关键字 volatile 的主要作用
-
关键字 volatile 与 synchronized 的区别及使用情况
synchronized 同步方法
方法内的变量为线程安全,“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”了。
实例变量非线程安全
如果多线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。
在两个线程访问同一个对象中的同步方法时一定是线程安全的。
脏读
发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
如下例子就可以说明,如果不加 synchronized 关键字在 setValue 和 getValue 方法上,就会出现数据脏读。
class VarName{ private String userName = "A"; private String password = "AA"; synchronized public void setValue(String userName, String password) { try { this.userName = userName; Thread.sleep(500); this.password = password; System.out.println("setValue method Thread name is : " + Thread.currentThread().getName() + " userName = " + userName + "password = " + password); } catch (InterruptedException e) { e.printStackTrace(); } } //synchronized public void getValue() { System.out.println("getValue method Thread name is : " + Thread.currentThread().getName() + " userName = " + userName + " password = " + password); } } class Thread1 extends Thread{ private VarName varName; public Thread1(VarName varName) { this.varName = varName; } @Override public void run() { varName.setValue("B", "BB"); } } public class Test{ public static void main(String[] args) throws InterruptedException { VarName v = new VarName(); Thread1 thread1 = new Thread1(v); thread1.start(); Thread.sleep(200); //打印结果受睡眠时间的影响v.getValue(); } } |
SYNCHRONIZED 锁重入
关键字 synchronized 拥有锁重入的功能,也就是在使用 synchronized 时,当一个线程得到一个对象锁后,再次请求此对象锁是可以再次得到该对象的锁的。这也证明了在一个 synchronized 方法/块的内部调用本类的其他 synchronized 方法/块时,是永远可以得到锁的。
class Service{synchronized public void service1() {System.out.println("service 1");service2();}synchronized public void service2() {System.out.println("service 2");service3();}synchronized public void service3() {System.out.println("service 3");}}class Thread2 extends Thread{@Overridepublic void run() {Service s = new Service();s.service1();}}public class Test2{public static void main(String[] args) {Thread2 t2 = new Thread2();t2.start();}} |
运行结果:
service 1service 2service 3 |
同步不具有继承性
同步不可以继承。
synchronized 同步语句块
SYNCHRONIZED 代码块间的同步性
当一个线程访问 object 的一个 synchronized(this) 同步代码块时,其他线程对同一个 object 中所有其他 synchronized(this) 同步代码块的访问将被阻塞,这说明 synchronized 使用的 “对象监视器” 是一个。
将任意对象作为对象监视器
多个线程调用同一个对象中的不同名称的 synchronized 同步方法或者 synchronized(this) 同步代码块时,调用的效果就是按顺序执行,也就是同步的,阻塞的。
静态同步 SYNCHRONIZED 方法与 SYNCHRONIZED(CLASS) 代码块
关键字 synchronized 还可以应用在 static 静态方法上,如果这样写就是对当前的 *.java 文件对应的 Class 类进行加锁。而 synchronized 关键字加到非 static 静态方法上就是给对象加锁。
多线程的死锁
volatile 关键字
作用:使变量在多个线程间可见。
通过使用 volatile 关键字,强制的从公共内存中读取变量的值。使用 volatile 关键字增加了实例变量在多个线程之间的可见性,但 volatile 关键字最致命的缺点就是不支持原子性。
关键字 synchronized 和 volatile 比较:
-
关键字 volatile 是线程同步的轻量实现,所以 volatile 性能肯定要比 synchronized 要好,并且 volatile 只能修饰于变量,而 synchronized 可以修饰方法,以及代码块。
-
多线程访问 volatile 不会发生阻塞,而 synchronized 会出现阻塞。
-
volatile 能保证数据的可见性,但不能保证原子性;而 synchronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。
-
关键字 volatile 解决的是变量在多个线程之间的可见性;而 synchronized 关键字解决的是多个线程之间访问资源的同步性。