并发基础
并发
简介:
一台电脑的性能优异基本都是看cpu,而cpu调用的最小单元是线程。当电脑只有一个cpu处理器时,单线程程序能够百分百利用cpu。但是当电脑拥有多cpu的时候,单线程的程序就会浪费很多的资源。而并发是多个线程同时进行,能够充分利用多cpu的资源,提高系统的吞吐量。
线程的基本概念:
线程表示一个单独的执行流,它有自己的程序执行计数器,有自己的栈。每一个线程的运行都会执行相应的功能,但是它也是有成本的。线程的有点和成本如下:
(1)充分的利用多cpu的计算能力,单线程只能利用一个CPU,使用多线程可以利用多CPU 的计算能力。
(2)充分利用硬盘资源,cpu和硬盘、网络是可以同时工作的,一个线程在等待网络IO的同时,另外一个 线程完全可以利用cpu做其他事情。对于多个独立的网络请求,完全可以使用多个线程同时请求。
线程基本属性和方法:
(1)属性:
每一个线程都有id和name,id是一个递增的整数,每创建一个线程就会加1。
name 的默认值是Thread-后跟一个编号。name可以通过setName(String name) 来设置一个更友好的名字,可以方便调试。
ThreadDemo threadDemo = new ThreadDemo();
Long id = threadDemo.getId();
String name = threadDemo.getName();
System.out.println(id +":"+ name);
(2)优先级:
线程有一个优先级的概念,在Java中,优先级从1到10,默认是5。这个优先级会映射到操作系统中线程的优先级。不过,操作系统不相同,不一定都是10个优先级,java不同优先级可能会被映射到操作系统相同的优先级。另外,优先级对操作系统而言主要是一种建议和提示,而非强制。简单地说,在编程中,不要过于依赖优先级。
优先级的方法:
方法 | 返回值 | 说明 |
---|---|---|
setPriority(int newPriority) | void | 设置优先级 |
getPriority() | int | 获取当前线程优先级 |
ThreadDemo threadDemo = new ThreadDemo();
int priority = threadDemo.getPriority();
System.out.println("优先级为: "+priority);
thread1.setPriority(10);
(3)状态:
线程都有相应的状态,同时也提供了获取状态的方法:
public State getState();
返回值是个枚举值,有如下值:
.NEW: 没有被调用时的状态就是NEW。
.RUNNABLE:调用start 启动线程在执行run方法没阻塞时状态为RUNNABLE。不过,RUNNABLE不代表cpu一定在执行该线程的代码,可能正在执行,也可能在等待操作系统分配时间片,只是它没有在等待其他条件
.BLOCKED:在等待队列等待锁,表示阻塞。
.WAITING:在条件队列等待被唤醒,表示阻塞。
.TIMED_WAITING:在条件队列等待超时,表示阻塞。
.TERMINATED:表示线程运行结束后的状态。
ThreadDemo threadDemo = new ThreadDemo();
Thread.State state = thread1.getState();
System.out.println(state);
(4)daemon线程:
daemon线程是其他线程的辅助线程。在它辅助的主线程退出的时候,它就没有存在的意义了。在我们执行只有main线程的时候,Java还会创建一个负责垃圾回收的线程,这个线程就是daemon线程,main结束后垃圾回收线程也会退出。
(5)sleep 方法:
方法名 | 返回值 | 说明 |
---|---|---|
sleep(int millis ) | void | 使当前线程睡眠指定时间 |
Thread.sleep(1000);
调用该方法会使得调用线程进入睡眠,睡眠期间会让出cpu,但是不会释放锁对象。
(6)yield 方法:
方法名 | 返回值 | 说明 |
---|---|---|
yield() | void | 使当前线程让出cpu |
调用该方法会使当前线程让出cpu。
Thread.yield();
(7) join 方法:
方法名 | 返回值 | 说明 |
---|---|---|
join() | void | 调用join的线程等待该线程结束 |
join(long millis) | void | 调用join的线程等待指定的时间在退出 |
ThreadDemo threadDemo1 = new ThreadDemo();
threadDemo1.start();
threadDemo1.join();
System.out.println("main 线程执行流");
上面的代码要threadDemo1的线程执行完之后才会执行main线程后面的代码
并发线程共享内存出现的问题:
竞态条件:
每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈。但是线程之间可以共享内存,他们可以访问和操作相同的对象。静态条件是当多个线程访问和操作同一个对象时,最终执行的结果和执行顺序有关,结果可能正确和可能不正确。例子如下:
package concurrent;
import java.util.ArrayList;
import java.util.List;
/**
*共享内存及可能存在的问题 竞态条件
* 每个线程表示一条单独的执行流,有自己的程序计数器,有自己的栈。
* 但是线程之间可以共享内存,他们可以访问和操作相同的对象。
*/
public class ConcurrentMemoryDemo{
private static int shared = 0;
private static void incrShared() {
shared++;
}
static class ChildThread extends Thread {
List<String> list;
public ChildThread(List<String> list) {
this.list = list;
}
@Override
public void run() {
incrShared();
list.add(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
//不同执行流可以访问和操作相同的变量。如shared和list。不同执行流可以执行相同的代码。
//当多条执行流执行相同的代码时,会出现竞态条件和内存可见性的问题。静态条件是当多个线程访问和
//操作同一个对象时,最终执行的结果和执行顺序有关,可能正确和可能不正确
List<String> list = new ArrayList<>();
Thread thread = new ChildThread(list);
Thread thread1 = new ChildThread(list);
thread.start();
thread.join();
thread1.start();
thread1.join();
System.out.println(shared);
System.out.println(list);
}
}
内存可见性:
多个线程可以共享访问和操作相同的变量。但一个线程对一个共享变量的修改,另外一个线程不一定马上就能看到,甚至永远也看不到。这是因为什么呢?因为在计算机系统里,除了内存,数据还会被缓存到在cpu的寄存器和各级缓存中。当访问一个变量时,可能是直接从寄存器和缓存中取的,而不一定从内存中取。当修改一个变量时,也可能先修改到缓存中,稍后才会同步到内存里。在多线程的程序中,尤其是多cpu的情况下,这就是一个严重的问题。一个线程对内存的修改,另外一个线程看不到。一是修改没有及时同步到内存。二是另外一个线程根本没去内存读。
package concurrent;
/**
* 内存可见性
*/
public class MemoryShow {
private static boolean shutdown = false;
static class HelloThread extends Thread {
@Override
public void run() {
while(!shutdown){
}
System.out.println("exit hello");
}
}
public static void main(String[] args) throws InterruptedException {
new HelloThread().start();
Thread.sleep(1000);
shutdown = true;
System.out.println("exit main");
}
}
解决上面的问题都是需要用到vollatile关键字和synchronized关键字或者显示锁
synchronized关键字:
synchronized 关键字可以用于修饰实例方法,代码块,静态方法。
synchronized的特征:
(1)可重入性:
可重入性指的是同一个执行线程,获得锁之后,再调用其他需要同样锁的代码时,可以直接调用。比如在一个Synchronized实例方法内,可以直接调用其他Synchronized实例方法。 可重入是通过记录锁的持有线程和持有数量来实现的,当调用被Synchronized保护的代码时,检查对象是否被锁, 如果是, 再检查是否被当前线程锁定,如果是增加持有数量。如果不是被当前线程锁定,加入等待队列。当释放锁的时候,减少持有数量,当数量变为0时才释放锁。
(2)内存可见性:
保证共享变量的内存可见性。在释放锁的时候,所有写入都会写到主内存中,获取的锁后都会从内存中取。保证内存可见性用Synchronized成本太高。有个轻量级的方式,就是用volatile。加入volatile之后,Java会在操作对应变量时插入特殊的指令,保证读写内存最新值,而非缓存的值。
(3) 死锁:
使用Synchronized或者其他锁时,都需要注意死锁。所谓死锁就是类似:有a、b两个线程,a持有A锁, 在等待B锁。而b持有B锁,在等待A锁。a和b陷入了互相等待,最后谁都执行不下去。解决死锁的方法是尽量避免在持有一个锁的时候去申请另外一个锁,确实需要多个锁,所有代码应该按相同的顺序去申请锁。比如都先约定申请A锁,在申请B锁。当然在复杂的项目是很难约定的。那这时就要用显示锁接口lock。它支持尝试获取锁, 和带时间限制的获取锁方法。使用这些方法可以在尝试获取不到锁的时候释放已持有的锁,然后再次尝试获取锁或者干脆放弃,以避免死锁的情况。如果还是出现死锁,java自带的 jstack命令会报告发现的死锁。
public class SynchronizedDemo {
private static void startLockA(){
Thread thread = new Thread(() -> {
synchronized (Counter.class){
try {
//休眠不释放锁
Thread.sleep(1000);
System.out.println("持有锁A线程休眠一秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (SynchronizedDemo.class){
System.out.println("申请锁B");
}
}
});
thread.start();
System.out.println(thread.getName());
}
private static void startLockB(){
Thread thread = new Thread(() -> {
synchronized (SynchronizedDemo.class){
try {
Thread.sleep(1000);
System.out.println("持有锁B线程休眠一秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Counter.class){
System.out.println("申请锁A");
}
}
});
thread.start();
System.out.println(thread.getName());
}
static class Counter {
private static int scount;
private int count;
//Synchronized关键字用于静态方法,静态方法Synchronized保护的时类对象。每一个对象都有锁和等待队列,类对象也不例外
public synchronized void incr(){
count++;
}
public synchronized int getCount(){
return count;
}
public static synchronized void inScr(){
scount++;
}
public static synchronized int getCountS(){
return scount;
}
//Synchronized 关键字锁住的 的同步代码块,代码块的锁对象可以时任意对象。
{
synchronized (this){
System.out.println("同步代码块");
}
}
}
static class CountThread extends Thread {
Counter counter;
public CountThread(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
for (int i = 0; i<1000;i++){
counter.incr();
}
}
}
public static void main(String[] args) throws InterruptedException {
//Synchronized 实例方法实际保护的是同一个对象的方法调用,确保同时只能一个线程执行。
//synchronized 实例方法保护的是当前实例对象,即this,this 对象有一个锁和一个等待队列
//锁只能被一个线程持有,其他试图获得同样锁的线程需要等待。
//执行synchronized实例方法大致如下:
//(1)尝试获得锁,如果获得锁,继续下一步。否则加入等待队列,阻塞并等待唤醒。
//(2)执行实例方法体代码。
//(3)释放锁,如果等待队列上有等待的线程,从中取一个唤醒。如果有多个等待的线程,唤醒哪一个是不一定的,不保证公平性。
//当前线程不能获得锁的时候,它会加入等待队列,线程的状态为BLOCKED。
//Synchronized方法不能防止非Synchronized方法被同时执行。所以,一般在保护变量时,需要在所有访问变量的方法
//上加Synchronized关键字。
/* int num = 1000;
Counter counter = new Counter();
Thread [] threads = new Thread[num];
for (int i = 0; i < num;i++){
threads[i] = new CountThread(counter);
threads[i].start();
threads[i].join();
}
System.out.println(counter.getCount());*/
startLockA();
startLockB();
}
}
同步容器及注意事项:
同步容器是容器的所有方法都加synchronized关键字,让所有方法调用都变成原子操作。同时也保证变量的内存可见性。但是客户在调用这些方法的时候是否绝对安全呢?不是的,还得注意一下问题:
(1)复合操作,比如先检查在更新。
(2)伪同步
(3)迭代
Collection的这些方法可以返回线程安全的同步容器:
方法 | 返回值 | 说明 |
---|---|---|
synchronizedCollection(Collection c) | Collection c | 将collection集合转为同步集合 |
synchronizedList | List | 将普通list集合转为同步list集合 |
synchronizedMap(Map<K,V>map) | Map<K,V> | 将普通Map转为同步map |
package concurrent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Collection中有一些方法,可以直接返回安全的同步容器,比如:
* public static <T> Collection<T> synchronizedCollection(Collection<T> c)
* public static <T> List<T> synchronizedList(List<T> list)
* public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
* 这些都是给容器所有方法加上synchronized来实现安全的。
* 同步容器及其注意事项:
* (1)复合操作,比如先检查在更新。
* (2)伪同步。
* (3)迭代。
*/
public class ConcurrentContainerDemo {
//复合操作
static class EnhancedMap<K,V> {
Map<K,V> map;
public EnhancedMap(Map<K,V> map){
this.map = Collections.synchronizedMap(map);
}
//这个方法不是同步安全的方法。本来map的方法都是安全的。但是现在这种复合操作,在多线程的时候,
//可能多个线程都执行完检查这一步,都发现没有键值,然后都会调用put,这样就破坏了这个方法保持的语义了
public V putIfAbsent(K key,V value){
V old = map.get(key);
if(old != null){
return old;
}
return map.put(key,value);
}
//伪同步:
//这里加上synchronized 就能实现同步吗?肯定是不能的,因为锁对象不同。这个方法的锁对象是EnhancedMap,
//而put的锁是Collections.synchronizedMap(map)返回的map对象
public synchronized V putIfAbsent1(K key,V value){
V old = map.get(key);
if(old != null){
return old;
}
return map.put(key,value);
}
//加入map对象锁,这个方法才是同步的,安全的
public V putIfAbsent12(K key,V value) {
synchronized (map) {
V old = map.get(key);
if (old != null) {
return old;
}
return map.put(key, value);
}
}
}
//迭代,对于同步容器虽然单个操作是安全的,但迭代不是。
private static void startModifyThread(final List<String> list){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i =0; i< 100; i++) {
list.add("新增"+(i+1));
try{
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
}
private static void startIterationThread(final List<String> list){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (list) {
for (String s : list) {
System.out.println(s);
}
}
}
}
});
thread.start();
}
public static void main(String[] args) {
//这样迭代会抛出同步异常,因为在遍历同步容器时容器发生了结构变化,就会抛出异常,需要解决这个问题,需在遍历时
//给整个容器对象加锁
final List<String> list = Collections.synchronizedList(new ArrayList<>());
startModifyThread(list);
startIterationThread(list);
//并发容器:CopyOnWriteArrayList。ConcurrentHashMap。ConcurrentLinkedQuee。ConcurrentSkipListSet
//这些容器类都是线程安全的,但都没有使用synchronized,没有迭代问题,直接支持一些复合操作,性能也更高
}
}
线程的基本协作机制:
多线程之间除了竞争访问同一资源外,也经常需要相互协作。在Java中线程之间的协作是通过wait和notify 来协作的。
线程的协作场景:
(1)生产者和消费者模式:常见的协作模式,生产者线程和消费者线程通过共享队列进行协作,生产者将数据或任务放到队列上,而消费者从队列上取数据或者任务,如果队列长度有限,在队列满的时候,生产者需要的等待,而队列为空时,消费者需要等待。
(2)同时开始:类似运动员比赛,听到信号枪时需要同时开始。在一些程序,尤其是仿真程序中,要求多个线程能同时开始。比如考试系统的发卷和收卷都是同时开始和同时结束。
(3)等待结束:主从协作模式也是常见的协作模式。主线程将任务分解为若干个任务,为每个子任务创建一个线程, 主线程在继续执行其他任务之前需要等待每个子任务执行完毕。
(4)集合点:比如并行迭代计算中,每个线程复杂一部分计算,然后在集合点等待其他线程完成,所有的线程到齐后交换数据和计算结果,在进行下一次迭代。
线程协作的方法:
都是Object的方法:
方法 | 返回值 | 说明 |
---|---|---|
wait() | void | 当前线程无限期的等待,除非被唤醒 |
wait(long timeout) | void | 当前线程等待指定的时间 |
notify () | void | 唤醒当前锁对象条件队列里的任意一个线程,然后将其在条件队列移除 |
notifyAll () | void | 唤醒当前锁对象条件队列里的所有等待线程,然后清空条件队列 |
wait 和notify的作用:
每个对象都有一个锁和等待队列,一个线程进入synchronized代码块后,尝试获得锁,获取不到就进入等待队列。其实除了等待队列,还有另外一个等待队列,表示条件队列,该队列用于线程间的协作。当调用wait就会把当前线程放到条件队列并阻塞,表示当前线程执行不下去了,需要等待一个条件,这个条件需要其他线程改变。同时调用wait时会释放锁对象。当别的线程执行 完任务,改变了条件。就调用notify 唤醒条件线程中的任意一个线程并在条件线程中删除,或者调用notifyAll唤醒条件队列的所有线程 并移除所有在条件队列的所有线程。notify 不会释放锁对象,因为锁对象是要当前持有锁对象的线程持有调用的数量为0时才会释放锁,简单来说就是当前线程synchronized保护的代码全部执行完才会释放锁。
注意事项:
wait()、wait(long timeout)和notify()、notifyAll();这四个方法必须在synchronized的代码块才能使用。因为线程的协作都是操作共享内存里的变量,而变量在多线程的操作时需要被synchronized保护的。
public class ConcurrentCooeration {
/**
* 生产者和消费者模式:
*/
static class ProconModels<E> {
private Queue<E> queue;
private int limit;
public ProconModels(int limit) {
this.limit = limit;
this.queue = new ArrayDeque<>(limit);
}
//添加方法
private synchronized void put(E e)throws InterruptedException {
while (queue.size() == limit){
wait();
}
queue.add(e);
notifyAll();
}
//获取方法
private synchronized E take()throws InterruptedException {
while (queue.isEmpty()){
wait();
}
E e = queue.poll();
notifyAll();
return e;
}
}
//生产者
static class Producer extends Thread{
private ProconModels<String> queue;
public Producer(ProconModels<String> queue) {
this.queue = queue;
}
@Override
public void run() {
int num =0;
try{
while(true){
String task = String.valueOf(num);
queue.put(task);
System.out.println("produce task:" + task);
num++;
Thread.sleep((int)(Math.random() *100));
}
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
//生产者
static class Consumer extends Thread {
private ProconModels<String> queue;
public Consumer(ProconModels<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
try {
String task = queue.take();
System.out.println("consumer handle task:" + task);
Thread.sleep((int)(Math.random() *100));
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
}
//同时开始:考试例子
static class FireFlag {
private volatile boolean startFlag = false;
private volatile boolean endFlag = false;
//上面加了轻量级的volatile关键字,java会插入特殊指令将最新的变量数据读写到内存
//下面的方法为啥还要加synchronized呢?因为wait和notifyAll要在synchronized保护的代码块下使用,
//也是为了保证共享变量在内存的可见性
private synchronized void waitTestStart() {
while(!startFlag){
try {
wait();
}catch (InterruptedException i){
i.printStackTrace();
}
}
System.out.println("学生统一开始考试!!");
}
private synchronized void waitTestend(){
try {
while(!endFlag){
wait();
}
}catch (InterruptedException i){
i.printStackTrace();
}
}
private synchronized void startFire(){
this.startFlag = true;
notifyAll();
}
private synchronized void endFire(){
this.endFlag = true;
notifyAll();
System.out.println("教师统一结束收卷");
}
}
static class Student extends Thread {
FireFlag fireFlag;
public Student(FireFlag fireFlag) {
this.fireFlag = fireFlag;
}
@Override
public void run() {
this.fireFlag.waitTestStart();
}
}
static class Teacher extends Thread {
FireFlag fireFlag;
public Teacher(FireFlag fireFlag) {
this.fireFlag = fireFlag;
}
@Override
public void run() {
this.fireFlag.waitTestend();
try {
Thread.sleep(5000);
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
/**
* 同时结束
*/
static class MyLatch {
private int count;
public MyLatch(int count) {
this.count = count;
}
public synchronized void await() throws InterruptedException {
while (count > 0){
wait();
}
}
public synchronized void countDown(){
count--;
while (count <= 0){
notifyAll();
return;
}
}
}
static class Worker extends Thread {
MyLatch myLatch;
public Worker(MyLatch myLatch) {
this.myLatch = myLatch;
}
@Override
public void run() {
try {
Thread.sleep(1000);
this.myLatch.countDown();
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
//集合点
static class AssemblePoint {
private int num;
public AssemblePoint(int num) {
this.num = num;
}
public synchronized void await() throws InterruptedException {
if(num > 0){
num--;
if(num < 0){
notifyAll();
}else{
while(num != 0){
wait();
}
}
}
}
}
static class Tourist extends Thread {
AssemblePoint assemblePoint;
public Tourist(AssemblePoint assemblePoint) {
this.assemblePoint = assemblePoint;
}
@Override
public void run() {
System.out.println("游客前往集合点");
try{
Thread.sleep(1000);
//集合
assemblePoint.await();
System.out.println("Arrived");
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//生产者和消费者模式:
ProconModels<String> queue = new ProconModels<>(10);
new Producer(queue).start();
new Consumer(queue).start();
//同时开始:
FireFlag fireFlag = new FireFlag();
Thread[] threads = new Thread[10];
for(int i = 0; i < 10; i++){
threads[i] = new Student(fireFlag);
threads[i].start();
}
Thread.sleep(1000);
fireFlag.startFire();
Teacher teacher = new Teacher(fireFlag);
teacher.start();
fireFlag.endFire();
//同时结束:
int num = 10;
MyLatch myLatch = new MyLatch(num);
Worker[] workers = new Worker[num];
for (int i = 0; i < num; i++){
workers[i] = new Worker(myLatch);
workers[i].start();
}
myLatch.await();
System.out.println("同时结束!");
//集合点:
AssemblePoint assemblePoint = new AssemblePoint(num);
Tourist[] tourists = new Tourist[num];
for (int i = 0; i < num; i++){
tourists[i] = new Tourist(assemblePoint);
tourists[i].start();
}
System.out.println("集合完毕");
}
}
线程的中断:
在现实生活中有时候同时进行的事情有时候是需要中断的,比如钓鱼比赛,当前钓起第一条鱼时,其它还在钓鱼的就也会被中断,表示比赛结束。当然在并发线程下,也有这些需求。
取消和关闭线程的场景:
(1)很多线程的运行模式是死循环,比如生产者和消费者模式。当我们要停止程序的时候,我们需要一种’优雅’的方法以关闭线程。
(2)从远程服务器下载文件,在在下载的时候会想取消该任务。
(3)从第三方服务器查询一个结果,我们希望在限定的时间内获得结果,如果得不到,我们会取消该任务。
4)抢火车票,会让多个朋友帮忙从多个渠道抢票,只要有一个渠道抢到了,我们会通知其他取消抢票。
中断方法:
方法 | 返回值 | 说明 |
---|---|---|
isInterrupted() | boolean | 返回对应线程的中断标志位是否位true |
interrupt() | void | 中断当前线程,设置中断标志位 |
interrupted() | boolean | 返回当前线程的中断标志位是否位true。但它有一个重要的副作用,就是会清空中断标志位,连续两次调用,第一次的 结果是true,那么第二次就会false |
线程对中断的反应:
interrupt()对线程的影响与线程的状态和进行的IO操作有关。我们主要考虑线程的状态,IO操作 的影响和具体的Io以及操作系统有关。
线程的状态:
- (1)RUNNABLE:线程在运行或具备运行条件,只是在等待操作系统调度。
- (2)WAITING /TIMED_WAITING: 线程在等待某个条件或超时。
- (3)BLOCKED:线程在等待锁,试图进入同步块。
- (4)NEW/TERMINATED:线程还未启动或已结束。
public class ThreadInterruptDemo {
//(1)RUNNABLE:如果线程在运行,且没有执行IO操作,interrupt()只是会设置线程的中断标志位,
//没有任何其他作用。线程应该在运行过程中合适的位置检查中断标志位。比如主体代码是循环体,
//可以在循环开始的进行检查,如下:
static class InterruptRunnableDemo extends Thread {
@Override
public void run() {
for (int i = 0; i<10; i++){
while(!this.isInterrupted()) {
System.out.println("中断RUNNABLE状态下的线程");
}
}
}
public void cancle(){
try {
sleep(1000);
this.interrupt();
}catch (InterruptedException i){
i.printStackTrace();
}
}
}
//(2)WAITING/TIMED_WAITING:线程调用join、wait、sleep方法会进入WAITING/TIMED_WAITING,
//在这个状态时,对线程调用interrupt()会使得该线程抛出InterruptedException。需要注意的
//是,抛出异常后,中断标志会被清空,而不是被设置。比如:
static class InterruptWaiting extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
}catch (InterruptedException i){
//打印出来的是false,InterruptionException 是一个收检查的异常,线程必须处理
// 。异常处理方式有两种:
//(1)不知道如何处理,直接向上传递该异常,这使得该方法成为一个可中断的方法了,但调用者需要进行处理。
//(2)有些时候,不能向上抛出异常。比如Thread的run方法。这时应该捕抓异常进行处理。处理后一般调用
//interrupt()设置中断标志位。使得其他代码有办法知道它发生了中断。
System.out.println(isInterrupted());
//清除中断的操作
System.out.println("清理操作");
//重设中断标志位
Thread.currentThread().interrupt();
}
System.out.println(isInterrupted());
}
}
//(3)BLOCKED:如果线程在等待锁,对线程对象调用interrupt()只是会设置线程的中断标志位,线程
//依然会处于BLOCKED状态,也就是说,interrupt()并不能使一个在等待锁的线程真正'中断'。如下:
static class BlockedInterrupt extends Thread {
private static Object lock = new Object();
@Override
public void run() {
synchronized (lock){
System.out.println(Thread.currentThread().getName());
while(!Thread.currentThread().isInterrupted()){
System.out.println("执行业务方法");
}
System.out.println("exit");
}
}
public static void test() throws InterruptedException {
synchronized (lock){
BlockedInterrupt blockedInterrupt = new BlockedInterrupt();
blockedInterrupt.start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName());
blockedInterrupt.interrupt();
blockedInterrupt.join();
}
}
}
//NEW、TERMINATE:如果线程尚未启动(NEW),或者已经结束(TERMINATE),则调用interrupt()对它没有任何效果
//中断标志位也不会被设置。
public static void main(String[] args) throws InterruptedException {
//Runnable状态下的中断:
InterruptRunnableDemo interruptRunnableDemo = new InterruptRunnableDemo();
interruptRunnableDemo.start();
interruptRunnableDemo.cancle();
//waiting、timed_waiting状态下的中断:
InterruptWaiting interruptWaiting = new InterruptWaiting();
interruptWaiting.start();
interruptWaiting.interrupt();
//BLOCKED 状态下的中断:
BlockedInterrupt.test();
System.out.println("jiesu");
}
}
总结:
以上都是线程的基本用法和原理,常用的方法是sleep(),join(),wait(),notify。前面两个是Thread 类的方法,后面两个是Object方法。并发需要注意的是竞争条件和内存可见性的问题,当多个线程对同一个变量操作时,执行的顺序会导致结果不正确。而且也会导致上一个线程修改了变量而别的线程却看不到修改的值,还是取的之前的值。这是因为电脑不仅是会把数据存到内存,还会放到cpu的寄存器上,也有可能是各级缓存中。同时取数据时不一定时从内存中取,这就导致了读写没有同步,造成内存可见性的问题。这些都可以通过使用synchroized关键字进行对变量加锁,使操作原子化,强制读写都是去内存中操作。虽然synchronized 可以解决这些问题,但是synchronized成本很高,同时也会造成死锁的问题。所以使用时需要注意,当然java提供了许多高性能的并发容器,没有了同步容器的复合操作、伪同步,迭代的问题,同时也极大的避免了死锁的 问题。这些并发容器由下一章节讲解,希望本文能给大家带来帮助。